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 }