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