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