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.openapi.util.NotNullLazyValue;
021    import com.intellij.openapi.util.Trinity;
022    import com.intellij.util.SmartList;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
026    import org.jetbrains.jet.lang.descriptors.ClassKind;
027    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
028    import org.jetbrains.jet.lang.psi.JetClassOrObject;
029    import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
030    import org.jetbrains.jet.lang.psi.JetParameter;
031    import org.jetbrains.jet.lang.types.JetType;
032    import org.jetbrains.jet.lang.types.TypeConstructor;
033    import org.jetbrains.k2js.translate.LabelGenerator;
034    import org.jetbrains.k2js.translate.context.TranslationContext;
035    import org.jetbrains.k2js.translate.general.AbstractTranslator;
036    import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
037    import org.jetbrains.k2js.translate.utils.JsAstUtils;
038    
039    import java.util.*;
040    
041    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*;
042    import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances;
043    import static org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator.createPlace;
044    import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createClassObjectInitializer;
045    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
046    import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
047    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
048    import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
049    import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction;
050    
051    /**
052     * Generates a definition of a single class.
053     */
054    public final class ClassTranslator extends AbstractTranslator {
055        @NotNull
056        private final JetClassOrObject classDeclaration;
057    
058        @NotNull
059        private final ClassDescriptor descriptor;
060    
061        @Nullable
062        private final ClassAliasingMap aliasingMap;
063    
064        @NotNull
065        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
066            return new ClassTranslator(classDeclaration, null, context).translate();
067        }
068    
069        @NotNull
070        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration,
071                @NotNull ClassDescriptor descriptor,
072                @NotNull TranslationContext context) {
073            return new ClassTranslator(classDeclaration, descriptor, null, context).translate();
074        }
075    
076        @NotNull
077        public static JsExpression generateObjectLiteral(
078                @NotNull JetObjectDeclaration objectDeclaration,
079                @NotNull TranslationContext context
080        ) {
081            return new ClassTranslator(objectDeclaration, null, context).translateObjectLiteralExpression();
082        }
083    
084        @NotNull
085        public static JsExpression generateObjectLiteral(
086                @NotNull JetObjectDeclaration objectDeclaration,
087                @NotNull ClassDescriptor descriptor,
088                @NotNull TranslationContext context
089        ) {
090            return new ClassTranslator(objectDeclaration, descriptor, null, context).translateObjectLiteralExpression();
091        }
092    
093        ClassTranslator(
094                @NotNull JetClassOrObject classDeclaration,
095                @Nullable ClassAliasingMap aliasingMap,
096                @NotNull TranslationContext context
097        ) {
098            this(classDeclaration, getClassDescriptor(context.bindingContext(), classDeclaration), aliasingMap, context);
099        }
100    
101        ClassTranslator(@NotNull JetClassOrObject classDeclaration,
102                @NotNull ClassDescriptor descriptor,
103                @Nullable ClassAliasingMap aliasingMap,
104                @NotNull TranslationContext context) {
105            super(context);
106            this.aliasingMap = aliasingMap;
107            this.descriptor = descriptor;
108            this.classDeclaration = classDeclaration;
109        }
110    
111        @NotNull
112        private JsExpression translateObjectLiteralExpression() {
113            ClassDescriptor containingClass = getContainingClass(descriptor);
114            if (containingClass == null) {
115                return translate(context());
116            }
117            return context().literalFunctionTranslator().translate(containingClass, context(), classDeclaration, descriptor, this);
118        }
119    
120        @NotNull
121        public JsInvocation translate() {
122            return translate(context());
123        }
124    
125        @NotNull
126        public JsInvocation translate(@NotNull TranslationContext declarationContext) {
127            JsInvocation createInvocation = context().namer().classCreateInvocation(descriptor);
128            translate(createInvocation, declarationContext);
129            return createInvocation;
130        }
131    
132        private void translate(@NotNull JsInvocation createInvocation, @NotNull TranslationContext context) {
133            addSuperclassReferences(createInvocation);
134            addClassOwnDeclarations(createInvocation.getArguments(), context);
135        }
136    
137        private boolean isTrait() {
138            return descriptor.getKind().equals(ClassKind.TRAIT);
139        }
140    
141        private void addClassOwnDeclarations(@NotNull List<JsExpression> invocationArguments, @NotNull TranslationContext declarationContext) {
142            final List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
143    
144            final List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
145            boolean isTopLevelDeclaration = context() == declarationContext;
146            final JsNameRef qualifiedReference;
147            if (!isTopLevelDeclaration) {
148                qualifiedReference = null;
149            }
150            else if (descriptor.getKind().isObject()) {
151                qualifiedReference = null;
152                declarationContext.literalFunctionTranslator().setDefinitionPlace(
153                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
154                            @Override
155                            @NotNull
156                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
157                                return createPlace(properties, context().getThisObject(descriptor));
158                            }
159                        });
160            }
161            else {
162                qualifiedReference = declarationContext.getQualifiedReference(descriptor);
163                declarationContext.literalFunctionTranslator().setDefinitionPlace(
164                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
165                            @Override
166                            @NotNull
167                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
168                                return createPlace(staticProperties, qualifiedReference);
169                            }
170                        });
171            }
172    
173            if (!isTrait()) {
174                JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod();
175                invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
176            }
177    
178            translatePropertiesAsConstructorParameters(declarationContext, properties);
179            DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
180            bodyVisitor.traverseContainer(classDeclaration, declarationContext);
181            mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext);
182    
183            if (isTopLevelDeclaration) {
184                declarationContext.literalFunctionTranslator().setDefinitionPlace(null);
185            }
186    
187            boolean hasStaticProperties = !staticProperties.isEmpty();
188            if (!properties.isEmpty() || hasStaticProperties) {
189                if (properties.isEmpty()) {
190                    invocationArguments.add(JsLiteral.NULL);
191                }
192                else {
193                    if (qualifiedReference != null) {
194                        // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
195                        invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
196                    }
197                    invocationArguments.add(new JsObjectLiteral(properties, true));
198                }
199            }
200            if (hasStaticProperties) {
201                invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
202                invocationArguments.add(new JsObjectLiteral(staticProperties, true));
203            }
204        }
205    
206        private void mayBeAddEnumEntry(@NotNull List<JsPropertyInitializer> enumEntryList,
207                @NotNull List<JsPropertyInitializer> staticProperties,
208                @NotNull TranslationContext declarationContext
209        ) {
210            if (descriptor.getKind() == ClassKind.ENUM_CLASS) {
211                JsInvocation invocation = context().namer().enumEntriesObjectCreateInvocation();
212                invocation.getArguments().add(new JsObjectLiteral(enumEntryList, true));
213    
214                JsFunction fun = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), invocation);
215                staticProperties.add(createClassObjectInitializer(fun, declarationContext));
216            } else {
217                assert enumEntryList.isEmpty(): "Only enum class may have enum entry. Class kind is: " + descriptor.getKind();
218            }
219        }
220    
221        private void addSuperclassReferences(@NotNull JsInvocation jsClassDeclaration) {
222            List<JsExpression> superClassReferences = getSupertypesNameReferences();
223            if (superClassReferences.isEmpty()) {
224                jsClassDeclaration.getArguments().add(JsLiteral.NULL);
225                return;
226            }
227    
228            List<JsExpression> expressions;
229            if (superClassReferences.size() > 1) {
230                JsArrayLiteral arrayLiteral = new JsArrayLiteral();
231                jsClassDeclaration.getArguments().add(arrayLiteral);
232                expressions = arrayLiteral.getExpressions();
233            }
234            else {
235                expressions = jsClassDeclaration.getArguments();
236            }
237    
238            for (JsExpression superClassReference : superClassReferences) {
239                expressions.add(superClassReference);
240            }
241        }
242    
243        @NotNull
244        private List<JsExpression> getSupertypesNameReferences() {
245            Collection<JetType> supertypes = descriptor.getTypeConstructor().getSupertypes();
246            if (supertypes.isEmpty()) {
247                return Collections.emptyList();
248            }
249            if (supertypes.size() == 1) {
250                JetType type = supertypes.iterator().next();
251                ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type);
252                if (isAny(supertypeDescriptor)) {
253                    return Collections.emptyList();
254                }
255                return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor));
256            }
257    
258            Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>();
259            for (JetType type : supertypes) {
260                supertypeConstructors.add(type.getConstructor());
261            }
262            List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(),
263                                                                                                           new HashMap<TypeConstructor, Set<JetType>>(),
264                                                                                                           new HashSet<TypeConstructor>());
265            List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
266            for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
267                if (supertypeConstructors.contains(typeConstructor)) {
268                    ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor);
269                    if (!isAny(supertypeDescriptor)) {
270                        supertypesRefs.add(getClassReference(supertypeDescriptor));
271                    }
272                }
273            }
274            return supertypesRefs;
275        }
276    
277        @NotNull
278        private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
279            // aliasing here is needed for the declaration generation step
280            if (aliasingMap != null) {
281                JsNameRef name = aliasingMap.get(superClassDescriptor, descriptor);
282                if (name != null) {
283                    return name;
284                }
285            }
286    
287            // from library
288            return context().getQualifiedReference(superClassDescriptor);
289        }
290    
291        private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
292                @NotNull List<JsPropertyInitializer> result) {
293            for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
294                PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
295                if (descriptor != null) {
296                    PropertyTranslator.translateAccessors(descriptor, result, classDeclarationContext);
297                }
298            }
299        }
300    }