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.openapi.util.NotNullLazyValue;
021    import com.intellij.openapi.util.Trinity;
022    import com.intellij.util.SmartList;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
026    import org.jetbrains.jet.lang.descriptors.ClassKind;
027    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
028    import org.jetbrains.jet.lang.psi.JetClassOrObject;
029    import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
030    import org.jetbrains.jet.lang.psi.JetParameter;
031    import org.jetbrains.jet.lang.types.JetType;
032    import org.jetbrains.k2js.translate.LabelGenerator;
033    import org.jetbrains.k2js.translate.context.Namer;
034    import org.jetbrains.k2js.translate.context.TranslationContext;
035    import org.jetbrains.k2js.translate.general.AbstractTranslator;
036    import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
037    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
038    import org.jetbrains.k2js.translate.utils.JsAstUtils;
039    
040    import java.util.ArrayList;
041    import java.util.Collection;
042    import java.util.Collections;
043    import java.util.List;
044    
045    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
046    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isNotAny;
047    import static org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator.createPlace;
048    import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createPropertyInitializer;
049    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
050    import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
051    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
052    import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
053    import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction;
054    
055    /**
056     * Generates a definition of a single class.
057     */
058    public final class ClassTranslator extends AbstractTranslator {
059        @NotNull
060        private final JetClassOrObject classDeclaration;
061    
062        @NotNull
063        private final ClassDescriptor descriptor;
064    
065        @Nullable
066        private final ClassAliasingMap aliasingMap;
067    
068        @NotNull
069        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
070            return new ClassTranslator(classDeclaration, null, context).translate();
071        }
072    
073        @NotNull
074        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration,
075                @NotNull ClassDescriptor descriptor,
076                @NotNull TranslationContext context) {
077            return new ClassTranslator(classDeclaration, descriptor, null, context).translate();
078        }
079    
080        @NotNull
081        public static JsExpression generateObjectLiteral(
082                @NotNull JetObjectDeclaration objectDeclaration,
083                @NotNull TranslationContext context
084        ) {
085            return new ClassTranslator(objectDeclaration, null, context).translateObjectLiteralExpression();
086        }
087    
088        @NotNull
089        public static JsExpression generateObjectLiteral(
090                @NotNull JetObjectDeclaration objectDeclaration,
091                @NotNull ClassDescriptor descriptor,
092                @NotNull TranslationContext context
093        ) {
094            return new ClassTranslator(objectDeclaration, descriptor, null, context).translateObjectLiteralExpression();
095        }
096    
097        ClassTranslator(
098                @NotNull JetClassOrObject classDeclaration,
099                @Nullable ClassAliasingMap aliasingMap,
100                @NotNull TranslationContext context
101        ) {
102            this(classDeclaration, getClassDescriptor(context.bindingContext(), classDeclaration), aliasingMap, context);
103        }
104    
105        ClassTranslator(@NotNull JetClassOrObject classDeclaration,
106                @NotNull ClassDescriptor descriptor,
107                @Nullable ClassAliasingMap aliasingMap,
108                @NotNull TranslationContext context) {
109            super(context);
110            this.aliasingMap = aliasingMap;
111            this.descriptor = descriptor;
112            this.classDeclaration = classDeclaration;
113        }
114    
115        @NotNull
116        private JsExpression translateObjectLiteralExpression() {
117            ClassDescriptor containingClass = getContainingClass(descriptor);
118            if (containingClass == null) {
119                return translate(context());
120            }
121            return context().literalFunctionTranslator().translate(containingClass, context(), classDeclaration, descriptor, this);
122        }
123    
124        @NotNull
125        public JsInvocation translate() {
126            return translate(context());
127        }
128    
129        @NotNull
130        public JsInvocation translate(@NotNull TranslationContext declarationContext) {
131            JsInvocation createInvocation = context().namer().classCreateInvocation(descriptor);
132            translate(createInvocation, declarationContext);
133            return createInvocation;
134        }
135    
136        private void translate(@NotNull JsInvocation createInvocation, @NotNull TranslationContext context) {
137            addSuperclassReferences(createInvocation);
138            addClassOwnDeclarations(createInvocation.getArguments(), context);
139        }
140    
141        private boolean isTrait() {
142            return descriptor.getKind().equals(ClassKind.TRAIT);
143        }
144    
145        private void addClassOwnDeclarations(@NotNull List<JsExpression> invocationArguments, @NotNull TranslationContext declarationContext) {
146            final List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
147    
148            final List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
149            boolean isTopLevelDeclaration = context() == declarationContext;
150            final JsNameRef qualifiedReference;
151            if (!isTopLevelDeclaration) {
152                qualifiedReference = null;
153            }
154            else if (descriptor.getKind().isObject()) {
155                qualifiedReference = null;
156                declarationContext.literalFunctionTranslator().setDefinitionPlace(
157                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
158                            @Override
159                            @NotNull
160                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
161                                return createPlace(properties, context().getThisObject(descriptor));
162                            }
163                        });
164            }
165            else {
166                qualifiedReference = declarationContext.getQualifiedReference(descriptor);
167                declarationContext.literalFunctionTranslator().setDefinitionPlace(
168                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
169                            @Override
170                            @NotNull
171                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
172                                return createPlace(staticProperties, qualifiedReference);
173                            }
174                        });
175            }
176    
177            if (!isTrait()) {
178                JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod();
179                if (context().isEcma5()) {
180                    invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
181                }
182                else {
183                    properties.add(new JsPropertyInitializer(Namer.initializeMethodReference(), initializer));
184                }
185            }
186    
187            translatePropertiesAsConstructorParameters(declarationContext, properties);
188            DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
189            bodyVisitor.traverseContainer(classDeclaration, declarationContext);
190            mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext);
191    
192            if (isTopLevelDeclaration) {
193                declarationContext.literalFunctionTranslator().setDefinitionPlace(null);
194            }
195    
196            boolean hasStaticProperties = !staticProperties.isEmpty();
197            if (!properties.isEmpty() || hasStaticProperties) {
198                if (properties.isEmpty()) {
199                    invocationArguments.add(JsLiteral.NULL);
200                }
201                else {
202                    if (qualifiedReference != null) {
203                        // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
204                        invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
205                    }
206                    invocationArguments.add(new JsObjectLiteral(properties, true));
207                }
208            }
209            if (hasStaticProperties) {
210                invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
211                invocationArguments.add(new JsObjectLiteral(staticProperties, true));
212            }
213        }
214    
215        private void mayBeAddEnumEntry(@NotNull List<JsPropertyInitializer> enumEntryList,
216                @NotNull List<JsPropertyInitializer> staticProperties,
217                @NotNull TranslationContext declarationContext
218        ) {
219            if (descriptor.getKind() == ClassKind.ENUM_CLASS) {
220                JsInvocation invocation = context().namer().enumEntriesObjectCreateInvocation();
221                invocation.getArguments().add(new JsObjectLiteral(enumEntryList, true));
222    
223                JsFunction fun = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), invocation);
224                staticProperties.add(createPropertyInitializer(Namer.getNamedForClassObjectInitializer(), fun, declarationContext));
225            } else {
226                assert enumEntryList.isEmpty(): "Only enum class may have enum entry. Class kind is: " + descriptor.getKind();
227            }
228        }
229    
230        private void addSuperclassReferences(@NotNull JsInvocation jsClassDeclaration) {
231            List<JsExpression> superClassReferences = getSupertypesNameReferences();
232            if (superClassReferences.isEmpty()) {
233                if (!isTrait() || context().isEcma5()) {
234                    jsClassDeclaration.getArguments().add(JsLiteral.NULL);
235                }
236                return;
237            }
238    
239            List<JsExpression> expressions;
240            if (superClassReferences.size() > 1) {
241                JsArrayLiteral arrayLiteral = new JsArrayLiteral();
242                jsClassDeclaration.getArguments().add(arrayLiteral);
243                expressions = arrayLiteral.getExpressions();
244            }
245            else {
246                expressions = jsClassDeclaration.getArguments();
247            }
248    
249            for (JsExpression superClassReference : superClassReferences) {
250                expressions.add(superClassReference);
251            }
252        }
253    
254        @NotNull
255        private List<JsExpression> getSupertypesNameReferences() {
256            Collection<JetType> supertypes = descriptor.getTypeConstructor().getSupertypes();
257            if (supertypes.isEmpty()) {
258                return Collections.emptyList();
259            }
260    
261            JsExpression base = null;
262            List<JsExpression> list = null;
263            for (JetType type : supertypes) {
264                ClassDescriptor result = getClassDescriptorForType(type);
265                if (isNotAny(result) && !AnnotationsUtils.isNativeObject(result)) {
266                    switch (result.getKind()) {
267                        case CLASS:
268                            base = getClassReference(result);
269                            break;
270                        case TRAIT:
271                            if (list == null) {
272                                list = new ArrayList<JsExpression>();
273                            }
274                            list.add(getClassReference(result));
275                            break;
276                        case ENUM_CLASS:
277                            base = getClassReference(result);
278                            break;
279    
280                        default:
281                            throw new UnsupportedOperationException("unsupported super class kind " + result.getKind().name());
282                    }
283                }
284            }
285    
286            if (list == null) {
287                return base == null ? Collections.<JsExpression>emptyList() : Collections.singletonList(base);
288            }
289            else if (base != null) {
290                list.add(0, base);
291            }
292    
293            return list;
294        }
295    
296        @NotNull
297        private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
298            // aliasing here is needed for the declaration generation step
299            if (aliasingMap != null) {
300                JsNameRef name = aliasingMap.get(superClassDescriptor, descriptor);
301                if (name != null) {
302                    return name;
303                }
304            }
305    
306            // from library
307            return context().getQualifiedReference(superClassDescriptor);
308        }
309    
310        private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
311                @NotNull List<JsPropertyInitializer> result) {
312            for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
313                PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
314                if (descriptor != null) {
315                    PropertyTranslator.translateAccessors(descriptor, result, classDeclarationContext);
316                }
317            }
318        }
319    }