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.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
024    import org.jetbrains.jet.lang.psi.JetFile;
025    import org.jetbrains.jet.lang.resolve.BindingContext;
026    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
027    import org.jetbrains.k2js.translate.context.Namer;
028    import org.jetbrains.k2js.translate.context.TranslationContext;
029    import org.jetbrains.k2js.translate.general.AbstractTranslator;
030    import org.jetbrains.k2js.translate.utils.JsAstUtils;
031    
032    import java.util.*;
033    
034    import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
035    
036    public final class NamespaceDeclarationTranslator extends AbstractTranslator {
037        private final Iterable<JetFile> files;
038        private final Map<NamespaceDescriptor,NamespaceTranslator> descriptorToTranslator =
039                new LinkedHashMap<NamespaceDescriptor, NamespaceTranslator>();
040    
041        public static List<JsStatement> translateFiles(@NotNull Collection<JetFile> files, @NotNull TranslationContext context) {
042            return new NamespaceDeclarationTranslator(files, context).translate();
043        }
044    
045        private NamespaceDeclarationTranslator(@NotNull Iterable<JetFile> files, @NotNull TranslationContext context) {
046            super(context);
047    
048            this.files = files;
049        }
050    
051        @NotNull
052        private List<JsStatement> translate() {
053            // predictable order
054            Map<NamespaceDescriptor, List<JsExpression>> descriptorToDefineInvocation = new THashMap<NamespaceDescriptor, List<JsExpression>>();
055            JsObjectLiteral rootNamespaceDefinition = null;
056    
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, descriptorToDefineInvocation, 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            context().classDeclarationTranslator().generateDeclarations();
087            for (NamespaceTranslator translator : descriptorToTranslator.values()) {
088                translator.add(descriptorToDefineInvocation, result);
089            }
090    
091            vars.addIfHasInitializer(context().classDeclarationTranslator().getDeclaration());
092            vars.addIfHasInitializer(getDeclaration(rootNamespaceDefinition));
093            return result;
094        }
095    
096        private JsObjectLiteral getRootPackage(Map<NamespaceDescriptor, List<JsExpression>> descriptorToDefineInvocation,
097                NamespaceDescriptor descriptor) {
098            NamespaceDescriptor rootNamespace = descriptor;
099            while (DescriptorUtils.isTopLevelDeclaration(rootNamespace)) {
100                rootNamespace = (NamespaceDescriptor) rootNamespace.getContainingDeclaration();
101            }
102    
103            JsObjectLiteral rootNamespaceDefinition = new JsObjectLiteral(true);
104            descriptorToDefineInvocation.put(rootNamespace, createDefineInvocation(rootNamespace, null, rootNamespaceDefinition, context()));
105            return rootNamespaceDefinition;
106        }
107    
108        static List<JsExpression> createDefineInvocation(
109                @NotNull NamespaceDescriptor descriptor,
110                @Nullable JsExpression initializer,
111                @NotNull JsObjectLiteral members,
112                @NotNull TranslationContext context
113        ) {
114            if (context.isEcma5()) {
115                return Arrays.asList(initializer == null ? JsLiteral.NULL : initializer,
116                                     new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, context.getQualifiedReference(descriptor)),
117                                     members);
118            }
119            else {
120                return Collections.<JsExpression>singletonList(members);
121            }
122        }
123    
124        private JsVar getDeclaration(@NotNull JsObjectLiteral rootNamespaceDefinition) {
125            JsExpression packageMapValue;
126            if (context().isEcma5()) {
127                packageMapValue = new JsInvocation(JsAstUtils.CREATE_OBJECT, JsLiteral.NULL,
128                                                   new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, Namer.getRootNamespaceName()),
129                                                   rootNamespaceDefinition);
130            }
131            else {
132                packageMapValue = rootNamespaceDefinition;
133            }
134            return new JsVar(context().scope().declareName(Namer.getRootNamespaceName()), packageMapValue);
135        }
136    }