001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.k2js.translate.declaration;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import gnu.trove.THashMap;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
023import org.jetbrains.jet.lang.psi.JetFile;
024import org.jetbrains.jet.lang.resolve.BindingContext;
025import org.jetbrains.jet.lang.resolve.DescriptorUtils;
026import org.jetbrains.k2js.translate.context.Namer;
027import org.jetbrains.k2js.translate.context.TranslationContext;
028import org.jetbrains.k2js.translate.general.AbstractTranslator;
029import org.jetbrains.k2js.translate.utils.JsAstUtils;
030
031import java.util.*;
032
033import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
034
035public final class NamespaceDeclarationTranslator extends AbstractTranslator {
036    private final Iterable<JetFile> files;
037    private final Map<NamespaceDescriptor,NamespaceTranslator> descriptorToTranslator =
038            new LinkedHashMap<NamespaceDescriptor, NamespaceTranslator>();
039
040    public static List<JsStatement> translateFiles(@NotNull Collection<JetFile> files, @NotNull TranslationContext context) {
041        return new NamespaceDeclarationTranslator(files, context).translate();
042    }
043
044    private NamespaceDeclarationTranslator(@NotNull Iterable<JetFile> files, @NotNull TranslationContext context) {
045        super(context);
046
047        this.files = files;
048    }
049
050    @NotNull
051    private List<JsStatement> translate() {
052        // predictable order
053        Map<NamespaceDescriptor, List<JsExpression>> descriptorToDefineInvocation = new THashMap<NamespaceDescriptor, List<JsExpression>>();
054        JsObjectLiteral rootNamespaceDefinition = null;
055
056        ClassDeclarationTranslator classDeclarationTranslator = new ClassDeclarationTranslator(context());
057        for (JetFile file : files) {
058            NamespaceDescriptor descriptor = context().bindingContext().get(BindingContext.FILE_TO_NAMESPACE, file);
059            assert descriptor != null;
060            NamespaceTranslator translator = descriptorToTranslator.get(descriptor);
061            if (translator == null) {
062                if (rootNamespaceDefinition == null) {
063                    rootNamespaceDefinition = getRootPackage(descriptorToDefineInvocation, descriptor);
064                }
065                translator = new NamespaceTranslator(descriptor, classDeclarationTranslator, context());
066                descriptorToTranslator.put(descriptor, translator);
067            }
068
069            translator.translate(file);
070        }
071
072        if (rootNamespaceDefinition == null) {
073            return Collections.emptyList();
074        }
075
076        JsVars vars = new JsVars(true);
077        List<JsStatement> result;
078        if (context().isEcma5()) {
079            result = Collections.<JsStatement>singletonList(vars);
080        }
081        else {
082            result = new ArrayList<JsStatement>();
083            result.add(vars);
084        }
085
086        classDeclarationTranslator.generateDeclarations();
087        for (NamespaceTranslator translator : descriptorToTranslator.values()) {
088            translator.add(descriptorToDefineInvocation, result);
089        }
090
091        vars.addIfHasInitializer(context().literalFunctionTranslator().getDeclaration());
092        vars.addIfHasInitializer(classDeclarationTranslator.getDeclaration());
093        vars.addIfHasInitializer(getDeclaration(rootNamespaceDefinition));
094        return result;
095    }
096
097    private JsObjectLiteral getRootPackage(Map<NamespaceDescriptor, List<JsExpression>> descriptorToDefineInvocation,
098            NamespaceDescriptor descriptor) {
099        NamespaceDescriptor rootNamespace = descriptor;
100        while (DescriptorUtils.isTopLevelDeclaration(rootNamespace)) {
101            rootNamespace = (NamespaceDescriptor) rootNamespace.getContainingDeclaration();
102        }
103
104        List<JsExpression> args;
105        JsObjectLiteral rootNamespaceDefinition = new JsObjectLiteral(true);
106        if (context().isEcma5()) {
107            args = Arrays.<JsExpression>asList(JsLiteral.NULL, rootNamespaceDefinition);
108        }
109        else {
110            args = Collections.<JsExpression>singletonList(rootNamespaceDefinition);
111        }
112
113        descriptorToDefineInvocation.put(rootNamespace, args);
114        return rootNamespaceDefinition;
115    }
116
117    private JsVar getDeclaration(@NotNull JsObjectLiteral rootNamespaceDefinition) {
118        JsExpression packageMapValue;
119        if (context().isEcma5()) {
120            packageMapValue = new JsInvocation(JsAstUtils.CREATE_OBJECT, JsLiteral.NULL, rootNamespaceDefinition);
121        }
122        else {
123            packageMapValue = rootNamespaceDefinition;
124        }
125        return new JsVar(context().scope().declareName(Namer.getRootNamespaceName()), packageMapValue);
126    }
127}