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 }