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 com.intellij.util.SmartList;
021    import gnu.trove.THashMap;
022    import gnu.trove.THashSet;
023    import gnu.trove.TObjectObjectProcedure;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
027    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
028    import org.jetbrains.jet.lang.descriptors.Modality;
029    import org.jetbrains.jet.lang.psi.JetClass;
030    import org.jetbrains.jet.lang.psi.JetClassOrObject;
031    import org.jetbrains.jet.lang.types.JetType;
032    import org.jetbrains.jet.utils.DFS;
033    import org.jetbrains.k2js.translate.context.Namer;
034    import org.jetbrains.k2js.translate.context.TranslationContext;
035    import org.jetbrains.k2js.translate.general.AbstractTranslator;
036    
037    import java.util.Collection;
038    import java.util.Iterator;
039    import java.util.LinkedList;
040    import java.util.List;
041    
042    import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
043    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
044    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
045    
046    /**
047     * Generates a big block where are all the classes(objects representing them) are created.
048     */
049    public final class ClassDeclarationTranslator extends AbstractTranslator {
050        private final THashSet<String> nameClashGuard = new THashSet<String>();
051    
052        @NotNull
053        private final THashMap<ClassDescriptor, OpenClassInfo> openClassDescriptorToItem = new THashMap<ClassDescriptor, OpenClassInfo>();
054    
055        private final LinkedList<OpenClassInfo> openList = new LinkedList<OpenClassInfo>();
056        @NotNull
057        private final ClassDescriptorToLabel classDescriptorToLabel = new ClassDescriptorToLabel();
058    
059        private final THashMap<ClassDescriptor, JsNameRef> openClassDescriptorToQualifiedLabel = new THashMap<ClassDescriptor, JsNameRef>();
060    
061        private final ClassAliasingMap classDescriptorToQualifiedLabel = new ClassAliasingMap() {
062            @NotNull
063            @Override
064            public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
065                JsNameRef ref = openClassDescriptorToQualifiedLabel.get(descriptor);
066                if (ref != null) {
067                    return ref;
068                }
069    
070                // will be resolved later
071                ref = new JsNameRef("<unresolved class>");
072                openClassDescriptorToQualifiedLabel.put(descriptor, ref);
073                return ref;
074            }
075        };
076    
077        @NotNull
078        private final JsFunction dummyFunction;
079    
080        private final JsNameRef declarationsObjectRef;
081        private final JsVar classesVar;
082    
083        public ClassDeclarationTranslator(@NotNull TranslationContext context) {
084            super(context);
085    
086            dummyFunction = new JsFunction(context.scope());
087            JsName declarationsObject = context().scope().declareName(Namer.nameForClassesVariable());
088            classesVar = new JsVars.JsVar(declarationsObject);
089            declarationsObjectRef = declarationsObject.makeRef();
090        }
091    
092        private final class ClassDescriptorToLabel implements ClassAliasingMap {
093            @Nullable
094            @Override
095            public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
096                OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
097                // class declared in library
098                if (item == null) {
099                    return null;
100                }
101    
102                return item.label;
103            }
104        }
105    
106        private static class OpenClassInfo {
107            private final ClassDescriptor descriptor;
108            private final JetClass declaration;
109            private final JsNameRef label;
110            private boolean referencedFromOpenClass = false;
111    
112            private OpenClassInfo(JetClass declaration, ClassDescriptor descriptor, JsNameRef label) {
113                this.descriptor = descriptor;
114                this.declaration = declaration;
115                this.label = label;
116            }
117        }
118    
119        @NotNull
120        public JsVars.JsVar getDeclaration() {
121            return classesVar;
122        }
123    
124        public void generateDeclarations() {
125            List<JsVar> vars = new SmartList<JsVar>();
126            List<JsPropertyInitializer> propertyInitializers = new SmartList<JsPropertyInitializer>();
127    
128            generateOpenClassDeclarations(vars, propertyInitializers);
129            fixUnresolvedClassReferences();
130    
131            if (vars.isEmpty()) {
132                if (!propertyInitializers.isEmpty()) {
133                    classesVar.setInitExpression(new JsObjectLiteral(propertyInitializers, true));
134                }
135                return;
136            }
137    
138            dummyFunction.setBody(new JsBlock(new JsVars(vars, true), new JsReturn(new JsObjectLiteral(propertyInitializers))));
139            classesVar.setInitExpression(new JsInvocation(dummyFunction));
140        }
141    
142        private void generateOpenClassDeclarations(@NotNull List<JsVar> vars, @NotNull List<JsPropertyInitializer> propertyInitializers) {
143            // first pass: set up list order
144            LinkedList<OpenClassInfo> sortedOpenClasses =
145                    (LinkedList<OpenClassInfo>) DFS.topologicalOrder(openList, new DFS.Neighbors<OpenClassInfo>() {
146                        @NotNull
147                        @Override
148                        public Iterable<OpenClassInfo> getNeighbors(OpenClassInfo current) {
149                            LinkedList<OpenClassInfo> parents = new LinkedList<OpenClassInfo>();
150                            ClassDescriptor classDescriptor = getClassDescriptor(context().bindingContext(), current.declaration);
151                            Collection<JetType> superTypes = classDescriptor.getTypeConstructor().getSupertypes();
152    
153                            for (JetType type : superTypes) {
154                                ClassDescriptor descriptor = getClassDescriptorForType(type);
155                                OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
156                                if (item == null) {
157                                    continue;
158                                }
159    
160                                item.referencedFromOpenClass = true;
161                                parents.add(item);
162                            }
163    
164                            return parents;
165                        }
166                    });
167    
168            assert sortedOpenClasses.size() == openList.size();
169    
170            // second pass: generate
171            Iterator<OpenClassInfo> it = sortedOpenClasses.descendingIterator();
172            while (it.hasNext()) {
173                OpenClassInfo item = it.next();
174                JsExpression translatedDeclaration =
175                        new ClassTranslator(item.declaration, item.descriptor, classDescriptorToLabel, context()).translate();
176    
177                JsExpression value;
178                if (item.referencedFromOpenClass) {
179                    vars.add(new JsVar(item.label.getName(), translatedDeclaration));
180                    value = item.label;
181                }
182                else {
183                    value = translatedDeclaration;
184                }
185    
186                propertyInitializers.add(new JsPropertyInitializer(item.label, value));
187            }
188        }
189    
190        private void fixUnresolvedClassReferences() {
191            openClassDescriptorToQualifiedLabel.forEachEntry(new TObjectObjectProcedure<ClassDescriptor, JsNameRef>() {
192                @Override
193                public boolean execute(ClassDescriptor descriptor, JsNameRef ref) {
194                    if (ref.getName() == null) {
195                        // from library
196                        ref.resolve(context().getNameForDescriptor(descriptor));
197                        ref.setQualifier(context().getQualifierForDescriptor(descriptor));
198                    }
199                    return true;
200                }
201            });
202        }
203    
204        private String createNameForClass(ClassDescriptor descriptor) {
205            String suggestedName = descriptor.getName().asString();
206            String name = suggestedName;
207            DeclarationDescriptor containing = descriptor;
208            while (!nameClashGuard.add(name)) {
209                containing = containing.getContainingDeclaration();
210                assert containing != null;
211                name = suggestedName + '_' + containing.getName().asString();
212            }
213            return name;
214        }
215    
216        @Nullable
217        public JsNameRef getQualifiedReference(ClassDescriptor descriptor) {
218            if (descriptor.getModality() != Modality.FINAL) {
219                //noinspection ConstantConditions
220                return classDescriptorToQualifiedLabel.get(descriptor, null);
221            }
222            return null;
223        }
224    
225        @Nullable
226        public JsPropertyInitializer translate(@NotNull JetClassOrObject declaration, TranslationContext context) {
227            ClassDescriptor descriptor = getClassDescriptor(context().bindingContext(), declaration);
228            JsExpression value;
229            if (descriptor.getModality() == Modality.FINAL) {
230                value = new ClassTranslator(declaration, classDescriptorToQualifiedLabel, context).translate();
231            }
232            else {
233                String label = createNameForClass(descriptor);
234                JsName name = dummyFunction.getScope().declareName(label);
235                JsNameRef qualifiedLabel = openClassDescriptorToQualifiedLabel.get(descriptor);
236                if (qualifiedLabel == null) {
237                    qualifiedLabel = new JsNameRef(name);
238                    openClassDescriptorToQualifiedLabel.put(descriptor, qualifiedLabel);
239                }
240                else {
241                    qualifiedLabel.resolve(name);
242                }
243                qualifiedLabel.setQualifier(declarationsObjectRef);
244    
245                OpenClassInfo item = new OpenClassInfo((JetClass) declaration, descriptor, name.makeRef());
246    
247                openList.add(item);
248                openClassDescriptorToItem.put(descriptor, item);
249    
250                value = qualifiedLabel;
251    
252                // not public api classes referenced to internal var _c
253                if (!descriptor.getVisibility().isPublicAPI()) {
254                    return null;
255                }
256            }
257    
258            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), value);
259        }
260    }