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