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.backend.common.CodegenUtil;
023    import org.jetbrains.jet.codegen.bridges.Bridge;
024    import org.jetbrains.jet.codegen.bridges.BridgesPackage;
025    import org.jetbrains.jet.lang.descriptors.*;
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.declaration.propertyTranslator.PropertyTranslatorPackage;
036    import org.jetbrains.k2js.translate.expression.ExpressionPackage;
037    import org.jetbrains.k2js.translate.general.AbstractTranslator;
038    import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
039    import org.jetbrains.k2js.translate.utils.JsAstUtils;
040    import org.jetbrains.k2js.translate.utils.UtilsPackage;
041    
042    import java.util.*;
043    
044    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*;
045    import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances;
046    import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
047    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
048    import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
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    import static org.jetbrains.k2js.translate.utils.UtilsPackage.*;
053    
054    /**
055     * Generates a definition of a single class.
056     */
057    public final class ClassTranslator extends AbstractTranslator {
058        @NotNull
059        private final JetClassOrObject classDeclaration;
060    
061        @NotNull
062        private final ClassDescriptor descriptor;
063    
064        @NotNull
065        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
066            return new ClassTranslator(classDeclaration, context).translate();
067        }
068    
069        @NotNull
070        public static JsExpression generateObjectLiteral(@NotNull JetObjectDeclaration objectDeclaration, @NotNull TranslationContext context) {
071            return new ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression();
072        }
073    
074        private ClassTranslator(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
075            super(context);
076            this.classDeclaration = classDeclaration;
077            this.descriptor = getClassDescriptor(context.bindingContext(), classDeclaration);
078        }
079    
080        @NotNull
081        private JsExpression translateObjectLiteralExpression() {
082            ClassDescriptor containingClass = getContainingClass(descriptor);
083            if (containingClass == null) {
084                return translate(context());
085            }
086    
087            return translateObjectInsideClass(context());
088        }
089    
090        @NotNull
091        public JsInvocation translate() {
092            return translate(context());
093        }
094    
095        @NotNull
096        public JsInvocation translate(@NotNull TranslationContext declarationContext) {
097            return new JsInvocation(context().namer().classCreateInvocation(descriptor), getClassCreateInvocationArguments(declarationContext));
098        }
099    
100        private boolean isTrait() {
101            return descriptor.getKind().equals(ClassKind.TRAIT);
102        }
103    
104        @NotNull
105        private List<JsExpression> getClassCreateInvocationArguments(@NotNull TranslationContext declarationContext) {
106            List<JsExpression> invocationArguments = new ArrayList<JsExpression>();
107    
108            List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
109            List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
110    
111            boolean isTopLevelDeclaration = context() == declarationContext;
112    
113            JsNameRef qualifiedReference = null;
114            if (isTopLevelDeclaration) {
115                DefinitionPlace definitionPlace = null;
116    
117                if (!descriptor.getKind().isSingleton() && !isAnonymousObject(descriptor)) {
118                    qualifiedReference = declarationContext.getQualifiedReference(descriptor);
119                    JsScope scope = context().getScopeForDescriptor(descriptor);
120                    definitionPlace = new DefinitionPlace((JsObjectScope) scope, qualifiedReference, staticProperties);
121                }
122    
123                declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace);
124            }
125    
126            declarationContext = fixContextForClassObjectAccessing(declarationContext);
127    
128            invocationArguments.add(getSuperclassReferences(declarationContext));
129            DelegationTranslator delegationTranslator = new DelegationTranslator(classDeclaration, context());
130            if (!isTrait()) {
131                JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(delegationTranslator);
132                invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
133            }
134    
135            translatePropertiesAsConstructorParameters(declarationContext, properties);
136            DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
137            bodyVisitor.traverseContainer(classDeclaration, declarationContext);
138            delegationTranslator.generateDelegated(properties);
139    
140            if (KotlinBuiltIns.getInstance().isData(descriptor)) {
141                new JsDataClassGenerator(classDeclaration, declarationContext, properties).generate();
142            }
143    
144            if (isEnumClass(descriptor)) {
145                JsObjectLiteral enumEntries = new JsObjectLiteral(bodyVisitor.getEnumEntryList(), true);
146                JsFunction function = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), enumEntries);
147                invocationArguments.add(function);
148            }
149    
150            generatedBridgeMethods(properties);
151    
152            boolean hasStaticProperties = !staticProperties.isEmpty();
153            if (!properties.isEmpty() || hasStaticProperties) {
154                if (properties.isEmpty()) {
155                    invocationArguments.add(JsLiteral.NULL);
156                }
157                else {
158                    if (qualifiedReference != null) {
159                        // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
160                        invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
161                    }
162                    invocationArguments.add(new JsObjectLiteral(properties, true));
163                }
164            }
165            if (hasStaticProperties) {
166                invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
167                invocationArguments.add(new JsObjectLiteral(staticProperties, true));
168            }
169    
170            return invocationArguments;
171        }
172    
173        private TranslationContext fixContextForClassObjectAccessing(TranslationContext declarationContext) {
174            // In Kotlin we can access to class object members without qualifier just by name, but we should translate it to access with FQ name.
175            // So create alias for class object receiver parameter.
176            ClassDescriptor classObjectDescriptor = descriptor.getClassObjectDescriptor();
177            if (classObjectDescriptor != null) {
178                JsExpression referenceToClass = translateAsFQReference(classObjectDescriptor.getContainingDeclaration(), declarationContext);
179                JsExpression classObjectAccessor = Namer.getClassObjectAccessor(referenceToClass);
180                ReceiverParameterDescriptor classObjectReceiver = getReceiverParameterForDeclaration(classObjectDescriptor);
181                declarationContext.aliasingContext().registerAlias(classObjectReceiver, classObjectAccessor);
182            }
183    
184            // Overlap alias of class object receiver for accessing from containing class(see previous if block),
185            // because inside class object we should use simple name for access.
186            if (descriptor.getKind() == ClassKind.CLASS_OBJECT) {
187                declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS);
188            }
189    
190            return declarationContext;
191        }
192    
193        private JsExpression getSuperclassReferences(@NotNull TranslationContext declarationContext) {
194            List<JsExpression> superClassReferences = getSupertypesNameReferences();
195            if (superClassReferences.isEmpty()) {
196                return JsLiteral.NULL;
197            } else {
198                return simpleReturnFunction(declarationContext.scope(), new JsArrayLiteral(superClassReferences));
199            }
200        }
201    
202        @NotNull
203        private List<JsExpression> getSupertypesNameReferences() {
204            List<JetType> supertypes = getSupertypesWithoutFakes(descriptor);
205            if (supertypes.isEmpty()) {
206                return Collections.emptyList();
207            }
208            if (supertypes.size() == 1) {
209                JetType type = supertypes.get(0);
210                ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type);
211                return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor));
212            }
213    
214            Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>();
215            for (JetType type : supertypes) {
216                supertypeConstructors.add(type.getConstructor());
217            }
218            List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(),
219                                                                                                           new HashMap<TypeConstructor, Set<JetType>>(),
220                                                                                                           new HashSet<TypeConstructor>());
221            List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
222            for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
223                if (supertypeConstructors.contains(typeConstructor)) {
224                    ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor);
225                    supertypesRefs.add(getClassReference(supertypeDescriptor));
226                }
227            }
228            return supertypesRefs;
229        }
230    
231        @NotNull
232        private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
233            return context().getQualifiedReference(superClassDescriptor);
234        }
235    
236        private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
237                @NotNull List<JsPropertyInitializer> result) {
238            for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
239                PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
240                if (descriptor != null) {
241                    PropertyTranslatorPackage.translateAccessors(descriptor, result, classDeclarationContext);
242                }
243            }
244        }
245    
246        @NotNull
247        private JsExpression translateObjectInsideClass(@NotNull TranslationContext outerClassContext) {
248            JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock(), "initializer for " + descriptor.getName().asString());
249            TranslationContext funContext = outerClassContext.newFunctionBodyWithUsageTracker(fun, descriptor);
250    
251            fun.getBody().getStatements().add(new JsReturn(translate(funContext)));
252    
253            return ExpressionPackage.withCapturedParameters(fun, funContext, outerClassContext, descriptor);
254        }
255    
256        private void generatedBridgeMethods(@NotNull List<JsPropertyInitializer> properties) {
257            if (isTrait()) return;
258    
259            generateBridgesToTraitImpl(properties);
260    
261            generateOtherBridges(properties);
262        }
263    
264        private void generateBridgesToTraitImpl(List<JsPropertyInitializer> properties) {
265            for(Map.Entry<FunctionDescriptor, FunctionDescriptor> entry : CodegenUtil.getTraitMethods(descriptor).entrySet()) {
266                if (!areNamesEqual(entry.getKey(), entry.getValue())) {
267                    properties.add(generateDelegateCall(entry.getValue(), entry.getKey(), JsLiteral.THIS, context()));
268                }
269            }
270        }
271    
272        private void generateOtherBridges(List<JsPropertyInitializer> properties) {
273            for (DeclarationDescriptor memberDescriptor : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) {
274                if (memberDescriptor instanceof FunctionDescriptor) {
275                    FunctionDescriptor functionDescriptor = (FunctionDescriptor) memberDescriptor;
276                    Set<Bridge<FunctionDescriptor>> bridgesToGenerate =
277                            BridgesPackage.generateBridgesForFunctionDescriptor(functionDescriptor, UtilsPackage.<FunctionDescriptor>getID());
278    
279                    for (Bridge<FunctionDescriptor> bridge : bridgesToGenerate) {
280                        generateBridge(bridge, properties);
281                    }
282                }
283            }
284        }
285    
286        private void generateBridge(
287                @NotNull Bridge<FunctionDescriptor> bridge,
288                @NotNull List<JsPropertyInitializer> properties
289        ) {
290            FunctionDescriptor fromDescriptor = bridge.getFrom();
291            FunctionDescriptor toDescriptor = bridge.getTo();
292            if (areNamesEqual(fromDescriptor, toDescriptor)) return;
293    
294            if (fromDescriptor.getKind().isReal() &&
295                fromDescriptor.getModality() != Modality.ABSTRACT &&
296                !toDescriptor.getKind().isReal()) return;
297    
298            properties.add(generateDelegateCall(fromDescriptor, toDescriptor, JsLiteral.THIS, context()));
299        }
300    
301        private boolean areNamesEqual(@NotNull FunctionDescriptor first, @NotNull FunctionDescriptor second) {
302            JsName firstName = context().getNameForDescriptor(first);
303            JsName secondName = context().getNameForDescriptor(second);
304            return firstName.getIdent().equals(secondName.getIdent());
305        }
306    }