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.jet.lang.descriptors.ClassDescriptor;
025    import org.jetbrains.jet.lang.descriptors.ClassKind;
026    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
027    import org.jetbrains.jet.lang.psi.JetClassOrObject;
028    import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
029    import org.jetbrains.jet.lang.psi.JetParameter;
030    import org.jetbrains.jet.lang.types.JetType;
031    import org.jetbrains.jet.lang.types.TypeConstructor;
032    import org.jetbrains.k2js.translate.LabelGenerator;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.general.AbstractTranslator;
035    import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
036    import org.jetbrains.k2js.translate.utils.JsAstUtils;
037    
038    import java.util.*;
039    
040    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
041    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForTypeConstructor;
042    import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances;
043    import static org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator.createPlace;
044    import static org.jetbrains.k2js.translate.initializer.InitializerUtils.createClassObjectInitializer;
045    import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
046    import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
047    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getContainingClass;
048    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes;
049    import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
050    import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction;
051    
052    /**
053     * Generates a definition of a single class.
054     */
055    public final class ClassTranslator extends AbstractTranslator {
056        @NotNull
057        private final JetClassOrObject classDeclaration;
058    
059        @NotNull
060        private final ClassDescriptor descriptor;
061    
062        @NotNull
063        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
064            return new ClassTranslator(classDeclaration, context).translate();
065        }
066    
067        @NotNull
068        public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration,
069                @NotNull ClassDescriptor descriptor,
070                @NotNull TranslationContext context) {
071            return new ClassTranslator(classDeclaration, descriptor, context).translate();
072        }
073    
074        @NotNull
075        public static JsExpression generateObjectLiteral(
076                @NotNull JetObjectDeclaration objectDeclaration,
077                @NotNull TranslationContext context
078        ) {
079            return new ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression();
080        }
081    
082        @NotNull
083        public static JsExpression generateObjectLiteral(
084                @NotNull JetObjectDeclaration objectDeclaration,
085                @NotNull ClassDescriptor descriptor,
086                @NotNull TranslationContext context
087        ) {
088            return new ClassTranslator(objectDeclaration, descriptor, context).translateObjectLiteralExpression();
089        }
090    
091        ClassTranslator(
092                @NotNull JetClassOrObject classDeclaration,
093                @NotNull TranslationContext context
094        ) {
095            this(classDeclaration, getClassDescriptor(context.bindingContext(), classDeclaration), context);
096        }
097    
098        ClassTranslator(
099                @NotNull JetClassOrObject classDeclaration,
100                @NotNull ClassDescriptor descriptor,
101                @NotNull TranslationContext context
102        ) {
103            super(context);
104            this.descriptor = descriptor;
105            this.classDeclaration = classDeclaration;
106        }
107    
108        @NotNull
109        private JsExpression translateObjectLiteralExpression() {
110            ClassDescriptor containingClass = getContainingClass(descriptor);
111            if (containingClass == null) {
112                return translate(context());
113            }
114            return context().literalFunctionTranslator().translate(containingClass, context(), classDeclaration, descriptor, this);
115        }
116    
117        @NotNull
118        public JsInvocation translate() {
119            return translate(context());
120        }
121    
122        @NotNull
123        public JsInvocation translate(@NotNull TranslationContext declarationContext) {
124            return context().namer().classCreateInvocation(descriptor, getClassCreateInvocationArguments(declarationContext));
125        }
126    
127        private boolean isTrait() {
128            return descriptor.getKind().equals(ClassKind.TRAIT);
129        }
130    
131        private List<JsExpression> getClassCreateInvocationArguments(@NotNull TranslationContext declarationContext) {
132            List<JsExpression> invocationArguments = new ArrayList<JsExpression>();
133    
134            final List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
135            final List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
136            boolean isTopLevelDeclaration = context() == declarationContext;
137            final JsNameRef qualifiedReference;
138            if (!isTopLevelDeclaration) {
139                qualifiedReference = null;
140            }
141            else if (descriptor.getKind().isSingleton()) {
142                qualifiedReference = null;
143                declarationContext.literalFunctionTranslator().setDefinitionPlace(
144                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
145                            @Override
146                            @NotNull
147                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
148                                return createPlace(properties, context().getThisObject(descriptor));
149                            }
150                        });
151            }
152            else {
153                qualifiedReference = declarationContext.getQualifiedReference(descriptor);
154                declarationContext.literalFunctionTranslator().setDefinitionPlace(
155                        new NotNullLazyValue<Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression>>() {
156                            @Override
157                            @NotNull
158                            public Trinity<List<JsPropertyInitializer>, LabelGenerator, JsExpression> compute() {
159                                return createPlace(staticProperties, qualifiedReference);
160                            }
161                        });
162            }
163    
164            invocationArguments.add(getSuperclassReferences(declarationContext));
165            if (!isTrait()) {
166                JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod();
167                invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
168            }
169    
170            translatePropertiesAsConstructorParameters(declarationContext, properties);
171            DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
172            bodyVisitor.traverseContainer(classDeclaration, declarationContext);
173            mayBeAddEnumEntry(bodyVisitor.getEnumEntryList(), staticProperties, declarationContext);
174    
175            if (isTopLevelDeclaration) {
176                declarationContext.literalFunctionTranslator().setDefinitionPlace(null);
177            }
178    
179            boolean hasStaticProperties = !staticProperties.isEmpty();
180            if (!properties.isEmpty() || hasStaticProperties) {
181                if (properties.isEmpty()) {
182                    invocationArguments.add(JsLiteral.NULL);
183                }
184                else {
185                    if (qualifiedReference != null) {
186                        // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
187                        invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
188                    }
189                    invocationArguments.add(new JsObjectLiteral(properties, true));
190                }
191            }
192            if (hasStaticProperties) {
193                invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
194                invocationArguments.add(new JsObjectLiteral(staticProperties, true));
195            }
196            return invocationArguments;
197        }
198    
199        private void mayBeAddEnumEntry(@NotNull List<JsPropertyInitializer> enumEntryList,
200                @NotNull List<JsPropertyInitializer> staticProperties,
201                @NotNull TranslationContext declarationContext
202        ) {
203            if (descriptor.getKind() == ClassKind.ENUM_CLASS) {
204                JsInvocation invocation = context().namer().enumEntriesObjectCreateInvocation();
205                invocation.getArguments().add(new JsObjectLiteral(enumEntryList, true));
206    
207                JsFunction fun = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), invocation);
208                staticProperties.add(createClassObjectInitializer(fun, declarationContext));
209            } else {
210                assert enumEntryList.isEmpty(): "Only enum class may have enum entry. Class kind is: " + descriptor.getKind();
211            }
212        }
213    
214        private JsExpression getSuperclassReferences(@NotNull TranslationContext declarationContext) {
215            List<JsExpression> superClassReferences = getSupertypesNameReferences();
216            if (superClassReferences.isEmpty()) {
217                return JsLiteral.NULL;
218            } else {
219                return simpleReturnFunction(declarationContext.scope(), new JsArrayLiteral(superClassReferences));
220            }
221        }
222    
223        @NotNull
224        private List<JsExpression> getSupertypesNameReferences() {
225            List<JetType> supertypes = getSupertypesWithoutFakes(descriptor);
226            if (supertypes.isEmpty()) {
227                return Collections.emptyList();
228            }
229            if (supertypes.size() == 1) {
230                JetType type = supertypes.get(0);
231                ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type);
232                return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor));
233            }
234    
235            Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>();
236            for (JetType type : supertypes) {
237                supertypeConstructors.add(type.getConstructor());
238            }
239            List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(),
240                                                                                                           new HashMap<TypeConstructor, Set<JetType>>(),
241                                                                                                           new HashSet<TypeConstructor>());
242            List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
243            for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
244                if (supertypeConstructors.contains(typeConstructor)) {
245                    ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor);
246                    supertypesRefs.add(getClassReference(supertypeDescriptor));
247                }
248            }
249            return supertypesRefs;
250        }
251    
252        @NotNull
253        private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
254            return context().getQualifiedReference(superClassDescriptor);
255        }
256    
257        private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
258                @NotNull List<JsPropertyInitializer> result) {
259            for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
260                PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
261                if (descriptor != null) {
262                    PropertyTranslator.translateAccessors(descriptor, result, classDeclarationContext);
263                }
264            }
265        }
266    }