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    
017    package org.jetbrains.k2js.translate.declaration;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import gnu.trove.THashMap;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
023    import org.jetbrains.jet.lang.psi.JetFile;
024    import org.jetbrains.jet.lang.resolve.BindingContext;
025    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
026    import org.jetbrains.k2js.translate.context.Namer;
027    import org.jetbrains.k2js.translate.context.TranslationContext;
028    import org.jetbrains.k2js.translate.general.AbstractTranslator;
029    import org.jetbrains.k2js.translate.utils.JsAstUtils;
030    
031    import java.util.*;
032    
033    import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
034    
035    public 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    }