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
017package org.jetbrains.k2js.translate.declaration;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import com.intellij.util.SmartList;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
024import org.jetbrains.jet.lang.descriptors.ClassKind;
025import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
026import org.jetbrains.jet.lang.psi.JetClassOrObject;
027import org.jetbrains.jet.lang.psi.JetObjectLiteralExpression;
028import org.jetbrains.jet.lang.psi.JetParameter;
029import org.jetbrains.jet.lang.types.JetType;
030import org.jetbrains.k2js.translate.context.Namer;
031import org.jetbrains.k2js.translate.context.TranslationContext;
032import org.jetbrains.k2js.translate.general.AbstractTranslator;
033import org.jetbrains.k2js.translate.general.Translation;
034import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
035
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.List;
040
041import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
042import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isNotAny;
043import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
044import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
045import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
046import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
047import static org.jetbrains.k2js.translate.utils.TranslationUtils.getQualifiedReference;
048
049/**
050 * Generates a definition of a single class.
051 */
052public final class ClassTranslator extends AbstractTranslator {
053    @NotNull
054    private final DeclarationBodyVisitor declarationBodyVisitor = new DeclarationBodyVisitor();
055
056    @NotNull
057    private final JetClassOrObject classDeclaration;
058
059    @NotNull
060    private final ClassDescriptor descriptor;
061
062    @Nullable
063    private final ClassAliasingMap aliasingMap;
064
065    @NotNull
066    public static JsExpression generateClassCreation(@NotNull JetClassOrObject classDeclaration,
067            @NotNull ClassAliasingMap aliasingMap,
068            @NotNull TranslationContext context) {
069        return new ClassTranslator(classDeclaration, aliasingMap, context).translateClassOrObjectCreation(context);
070    }
071
072    @NotNull
073    public static JsExpression generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
074        return new ClassTranslator(classDeclaration, null, context).translateClassOrObjectCreation(context);
075    }
076
077    @NotNull
078    public static JsExpression generateClassCreation(@NotNull JetClassOrObject classDeclaration,
079            @NotNull ClassDescriptor descriptor,
080            @NotNull TranslationContext context) {
081        return new ClassTranslator(classDeclaration, descriptor, null, context).translateClassOrObjectCreation(context);
082    }
083
084    @NotNull
085    public static JsExpression generateObjectLiteral(@NotNull JetObjectLiteralExpression objectLiteralExpression,
086            @NotNull TranslationContext context) {
087        return new ClassTranslator(objectLiteralExpression.getObjectDeclaration(), null, context).translateObjectLiteralExpression();
088    }
089
090    ClassTranslator(@NotNull JetClassOrObject classDeclaration,
091            @Nullable ClassAliasingMap aliasingMap,
092            @NotNull TranslationContext context) {
093        this(classDeclaration, getClassDescriptor(context.bindingContext(), classDeclaration), aliasingMap, context);
094    }
095
096    ClassTranslator(@NotNull JetClassOrObject classDeclaration,
097            @NotNull ClassDescriptor descriptor,
098            @Nullable ClassAliasingMap aliasingMap,
099            @NotNull TranslationContext context) {
100        super(context);
101        this.aliasingMap = aliasingMap;
102        this.descriptor = descriptor;
103        this.classDeclaration = classDeclaration;
104    }
105
106    @NotNull
107    private JsExpression translateObjectLiteralExpression() {
108        ClassDescriptor containingClass = getContainingClass(descriptor);
109        if (containingClass == null) {
110            return translateClassOrObjectCreation(context());
111        }
112        return translateAsObjectCreationExpressionWithEnclosingThisSaved(containingClass);
113    }
114
115    @NotNull
116    private JsExpression translateAsObjectCreationExpressionWithEnclosingThisSaved(@NotNull ClassDescriptor containingClass) {
117        return context().literalFunctionTranslator().translate(containingClass, classDeclaration, descriptor, this);
118    }
119
120    @NotNull
121    public JsExpression translateClassOrObjectCreation(@NotNull TranslationContext declarationContext) {
122        JsInvocation createInvocation = context().namer().classCreateInvocation(descriptor);
123        translateClassOrObjectCreation(createInvocation, declarationContext);
124        return createInvocation;
125    }
126
127    public void translateClassOrObjectCreation(@NotNull JsInvocation createInvocation) {
128        translateClassOrObjectCreation(createInvocation, context());
129    }
130
131    private void translateClassOrObjectCreation(@NotNull JsInvocation createInvocation, @NotNull TranslationContext context) {
132        addSuperclassReferences(createInvocation);
133        addClassOwnDeclarations(createInvocation, context);
134    }
135
136    private boolean isTrait() {
137        return descriptor.getKind().equals(ClassKind.TRAIT);
138    }
139
140    private void addClassOwnDeclarations(@NotNull JsInvocation jsClassDeclaration, @NotNull TranslationContext classDeclarationContext) {
141        JsObjectLiteral properties = new JsObjectLiteral(true);
142        List<JsPropertyInitializer> propertyList = properties.getPropertyInitializers();
143        if (!isTrait()) {
144            JsFunction initializer = Translation.generateClassInitializerMethod(classDeclaration, classDeclarationContext);
145            if (context().isEcma5()) {
146                jsClassDeclaration.getArguments().add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
147            }
148            else {
149                propertyList.add(new JsPropertyInitializer(Namer.initializeMethodReference(), initializer));
150            }
151        }
152
153        propertyList.addAll(translatePropertiesAsConstructorParameters(classDeclarationContext));
154        propertyList.addAll(declarationBodyVisitor.traverseClass(classDeclaration, classDeclarationContext));
155
156        if (!propertyList.isEmpty() || !context().isEcma5()) {
157            jsClassDeclaration.getArguments().add(properties);
158        }
159    }
160
161    private void addSuperclassReferences(@NotNull JsInvocation jsClassDeclaration) {
162        List<JsExpression> superClassReferences = getSupertypesNameReferences();
163        List<JsExpression> expressions = jsClassDeclaration.getArguments();
164        if (context().isEcma5()) {
165            if (superClassReferences.isEmpty()) {
166                jsClassDeclaration.getArguments().add(JsLiteral.NULL);
167                return;
168            }
169            else if (superClassReferences.size() > 1) {
170                JsArrayLiteral arrayLiteral = new JsArrayLiteral();
171                jsClassDeclaration.getArguments().add(arrayLiteral);
172                expressions = arrayLiteral.getExpressions();
173            }
174        }
175
176        for (JsExpression superClassReference : superClassReferences) {
177            expressions.add(superClassReference);
178        }
179    }
180
181    @NotNull
182    private List<JsExpression> getSupertypesNameReferences() {
183        Collection<JetType> supertypes = descriptor.getTypeConstructor().getSupertypes();
184        if (supertypes.isEmpty()) {
185            return Collections.emptyList();
186        }
187
188        JsExpression base = null;
189        List<JsExpression> list = null;
190        for (JetType type : supertypes) {
191            ClassDescriptor result = getClassDescriptorForType(type);
192            if (isNotAny(result) && !AnnotationsUtils.isNativeObject(result)) {
193                switch (result.getKind()) {
194                    case CLASS:
195                        base = getClassReference(result);
196                        break;
197                    case TRAIT:
198                        if (list == null) {
199                            list = new ArrayList<JsExpression>();
200                        }
201                        list.add(getClassReference(result));
202                        break;
203
204                    default:
205                        throw new UnsupportedOperationException("unsupported super class kind " + result.getKind().name());
206                }
207            }
208        }
209
210        if (list == null) {
211            return base == null ? Collections.<JsExpression>emptyList() : Collections.singletonList(base);
212        }
213        else if (base != null) {
214            list.add(0, base);
215        }
216
217        return list;
218    }
219
220    @NotNull
221    private JsExpression getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
222        // aliasing here is needed for the declaration generation step
223        if (aliasingMap != null) {
224            JsNameRef name = aliasingMap.get(superClassDescriptor, descriptor);
225            if (name != null) {
226                return name;
227            }
228        }
229
230        // from library
231        return getQualifiedReference(context(), superClassDescriptor);
232    }
233
234    @NotNull
235    private List<JsPropertyInitializer> translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext) {
236        List<JetParameter> parameters = getPrimaryConstructorParameters(classDeclaration);
237        if (parameters.isEmpty()) {
238            return Collections.emptyList();
239        }
240
241        List<JsPropertyInitializer> result = new SmartList<JsPropertyInitializer>();
242        for (JetParameter parameter : parameters) {
243            PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
244            if (descriptor != null) {
245                PropertyTranslator.translateAccessors(descriptor, result, classDeclarationContext);
246            }
247        }
248        return result;
249    }
250}