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.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
024    import org.jetbrains.jet.lang.descriptors.ClassKind;
025    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
026    import org.jetbrains.jet.lang.psi.JetClassOrObject;
027    import org.jetbrains.jet.lang.psi.JetObjectLiteralExpression;
028    import org.jetbrains.jet.lang.psi.JetParameter;
029    import org.jetbrains.jet.lang.types.JetType;
030    import org.jetbrains.k2js.translate.context.Namer;
031    import org.jetbrains.k2js.translate.context.TranslationContext;
032    import org.jetbrains.k2js.translate.general.AbstractTranslator;
033    import org.jetbrains.k2js.translate.general.Translation;
034    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
035    
036    import java.util.ArrayList;
037    import java.util.Collection;
038    import java.util.Collections;
039    import java.util.List;
040    
041    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
042    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isNotAny;
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
044    import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
045    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
046    import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
047    import static org.jetbrains.k2js.translate.utils.TranslationUtils.getQualifiedReference;
048    
049    /**
050     * Generates a definition of a single class.
051     */
052    public 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    }