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