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 com.intellij.openapi.util.Pair;
021import com.intellij.util.SmartList;
022import gnu.trove.THashMap;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.annotations.Nullable;
025import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
026import org.jetbrains.jet.lang.descriptors.Modality;
027import org.jetbrains.jet.lang.psi.JetClass;
028import org.jetbrains.jet.lang.psi.JetClassOrObject;
029import org.jetbrains.jet.lang.types.JetType;
030import org.jetbrains.jet.utils.DFS;
031import org.jetbrains.k2js.translate.LabelGenerator;
032import org.jetbrains.k2js.translate.context.Namer;
033import org.jetbrains.k2js.translate.context.TranslationContext;
034import org.jetbrains.k2js.translate.general.AbstractTranslator;
035import org.jetbrains.k2js.translate.initializer.InitializerUtils;
036
037import java.util.*;
038
039import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
040import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
041import static org.jetbrains.k2js.translate.general.Translation.translateClassDeclaration;
042import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
043
044/**
045 * Generates a big block where are all the classes(objects representing them) are created.
046 */
047public final class ClassDeclarationTranslator extends AbstractTranslator {
048    private final LabelGenerator localLabelGenerator = new LabelGenerator('c');
049
050    @NotNull
051    private final THashMap<ClassDescriptor, OpenClassInfo> openClassDescriptorToItem = new THashMap<ClassDescriptor, OpenClassInfo>();
052
053    private final LinkedList<OpenClassInfo> openList = new LinkedList<OpenClassInfo>();
054    private final List<Pair<JetClassOrObject, JsInvocation>> finalList = new ArrayList<Pair<JetClassOrObject, JsInvocation>>();
055
056    @NotNull
057    private final ClassDescriptorToLabel classDescriptorToLabel = new ClassDescriptorToLabel();
058
059    @NotNull
060    private final JsFunction dummyFunction;
061
062    private final JsNameRef declarationsObjectRef;
063    private final JsVar classesVar;
064
065    public ClassDeclarationTranslator(@NotNull TranslationContext context) {
066        super(context);
067
068        dummyFunction = new JsFunction(context.scope());
069        JsName declarationsObject = context().scope().declareName(Namer.nameForClassesVariable());
070        classesVar = new JsVars.JsVar(declarationsObject);
071        declarationsObjectRef = declarationsObject.makeRef();
072    }
073
074    private final class ClassDescriptorToLabel implements ClassAliasingMap {
075        @Override
076        @Nullable
077        public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
078            OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
079            // class declared in library
080            if (item == null) {
081                return null;
082            }
083
084            return item.label;
085        }
086    }
087
088    private static class OpenClassInfo {
089        private final JetClass declaration;
090        private final JsNameRef label;
091        private final JsNameRef qualifiedLabel;
092
093        private OpenClassInfo(JetClass declaration, JsNameRef label, JsNameRef qualifiedLabel) {
094            this.declaration = declaration;
095            this.label = label;
096            this.qualifiedLabel = qualifiedLabel;
097        }
098    }
099
100    @NotNull
101    public JsVars.JsVar getDeclaration() {
102        return classesVar;
103    }
104
105    public void generateDeclarations() {
106        List<JsVar> vars = new SmartList<JsVar>();
107        List<JsPropertyInitializer> propertyInitializers = new SmartList<JsPropertyInitializer>();
108
109        generateOpenClassDeclarations(vars, propertyInitializers);
110        generateFinalClassDeclarations();
111
112        if (vars.isEmpty()) {
113            if (!propertyInitializers.isEmpty()) {
114                classesVar.setInitExpression(new JsObjectLiteral(propertyInitializers));
115            }
116            return;
117        }
118
119        dummyFunction.setBody(new JsBlock(new JsVars(vars, true), new JsReturn(new JsObjectLiteral(propertyInitializers))));
120        classesVar.setInitExpression(new JsInvocation(dummyFunction));
121    }
122
123    private void generateOpenClassDeclarations(@NotNull List<JsVar> vars, @NotNull List<JsPropertyInitializer> propertyInitializers) {
124        // first pass: set up list order
125        LinkedList<OpenClassInfo> sortedOpenClasses =
126                (LinkedList<OpenClassInfo>) DFS.topologicalOrder(openList, new DFS.Neighbors<OpenClassInfo>() {
127                    @NotNull
128                    @Override
129                    public Iterable<OpenClassInfo> getNeighbors(OpenClassInfo current) {
130                        LinkedList<OpenClassInfo> parents = new LinkedList<OpenClassInfo>();
131                        ClassDescriptor classDescriptor = getClassDescriptor(context().bindingContext(), current.declaration);
132                        Collection<JetType> superTypes = classDescriptor.getTypeConstructor().getSupertypes();
133
134                        for (JetType type : superTypes) {
135                            ClassDescriptor descriptor = getClassDescriptorForType(type);
136                            OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
137                            if (item == null) {
138                                continue;
139                            }
140
141                            parents.add(item);
142                        }
143
144                        return parents;
145                    }
146                });
147
148        assert sortedOpenClasses.size() == openList.size();
149
150        // second pass: generate
151        Iterator<OpenClassInfo> it = sortedOpenClasses.descendingIterator();
152        while (it.hasNext()) {
153            OpenClassInfo item = it.next();
154            JsExpression translatedDeclaration = translateClassDeclaration(item.declaration, classDescriptorToLabel, context());
155            generate(item, propertyInitializers, translatedDeclaration, vars);
156        }
157    }
158
159    private void generateFinalClassDeclarations() {
160        ClassAliasingMap aliasingMap = new ClassAliasingMap() {
161            @Override
162            public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
163                OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
164                return item == null ? null : item.qualifiedLabel;
165            }
166        };
167
168        for (Pair<JetClassOrObject, JsInvocation> item : finalList) {
169            new ClassTranslator(item.first, aliasingMap, context()).translateClassOrObjectCreation(item.second);
170        }
171    }
172
173    private static void generate(@NotNull OpenClassInfo item,
174            @NotNull List<JsPropertyInitializer> propertyInitializers,
175            @NotNull JsExpression definition,
176            @NotNull List<JsVar> vars) {
177        JsExpression value;
178        if (item.label.getName() == null) {
179            value = definition;
180        }
181        else {
182            assert item.label.getName() != null;
183            vars.add(new JsVar(item.label.getName(), definition));
184            value = item.label;
185        }
186
187        propertyInitializers.add(new JsPropertyInitializer(item.label, value));
188    }
189
190    @NotNull
191    public JsPropertyInitializer translate(@NotNull JetClassOrObject declaration) {
192        ClassDescriptor descriptor = getClassDescriptor(context().bindingContext(), declaration);
193        JsExpression value;
194        if (descriptor.getModality() == Modality.FINAL) {
195            JsInvocation invocation = context().namer().classCreateInvocation(descriptor);
196            finalList.add(new Pair<JetClassOrObject, JsInvocation>(declaration, invocation));
197            value = invocation;
198        }
199        else {
200            String label = localLabelGenerator.generate();
201            JsNameRef labelRef = dummyFunction.getScope().declareName(label).makeRef();
202            OpenClassInfo
203                    item = new OpenClassInfo((JetClass) declaration, labelRef, new JsNameRef(labelRef.getIdent(), declarationsObjectRef));
204            openList.add(item);
205            openClassDescriptorToItem.put(descriptor, item);
206
207            value = item.qualifiedLabel;
208        }
209
210        return InitializerUtils.createPropertyInitializer(descriptor, value, context());
211    }
212}