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