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