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 }