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