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