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