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.initializer;
018
019 import com.google.dart.compiler.backend.js.ast.*;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
023 import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
024 import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025 import org.jetbrains.jet.lang.psi.JetClassOrObject;
026 import org.jetbrains.jet.lang.psi.JetDelegationSpecifier;
027 import org.jetbrains.jet.lang.psi.JetDelegatorToSuperCall;
028 import org.jetbrains.jet.lang.psi.JetParameter;
029 import org.jetbrains.k2js.translate.context.Namer;
030 import org.jetbrains.k2js.translate.context.TranslationContext;
031 import org.jetbrains.k2js.translate.general.AbstractTranslator;
032
033 import java.util.ArrayList;
034 import java.util.List;
035
036 import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
037 import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
038 import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
039 import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
040 import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateArgumentList;
041
042 public final class ClassInitializerTranslator extends AbstractTranslator {
043 @NotNull
044 private final JetClassOrObject classDeclaration;
045 @NotNull
046 private final List<JsStatement> initializerStatements = new ArrayList<JsStatement>();
047
048 public ClassInitializerTranslator(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
049 // Note: it's important we use scope for class descriptor because anonymous function used in property initializers
050 // belong to the properties themselves
051 super(context.newDeclaration(getConstructor(context.bindingContext(), classDeclaration)));
052 this.classDeclaration = classDeclaration;
053 }
054
055 @NotNull
056 public JsFunction generateInitializeMethod() {
057 //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
058 ConstructorDescriptor primaryConstructor = getConstructor(bindingContext(), classDeclaration);
059 JsFunction result = context().getFunctionObject(primaryConstructor);
060 //NOTE: while we translate constructor parameters we also add property initializer statements
061 // for properties declared as constructor parameters
062 setParameters(result, translatePrimaryConstructorParameters());
063 mayBeAddCallToSuperMethod(result);
064 (new InitializerVisitor(initializerStatements)).traverseClass(classDeclaration, context());
065 result.getBody().getStatements().addAll(initializerStatements);
066 return result;
067 }
068
069 private void mayBeAddCallToSuperMethod(JsFunction initializer) {
070 if (hasAncestorClass(bindingContext(), classDeclaration)) {
071 JetDelegatorToSuperCall superCall = getSuperCall();
072 if (superCall == null) return;
073 addCallToSuperMethod(superCall, initializer);
074 }
075 }
076
077 private void addCallToSuperMethod(@NotNull JetDelegatorToSuperCall superCall, JsFunction initializer) {
078 List<JsExpression> arguments = translateArguments(superCall);
079
080 //TODO: can be problematic to maintain
081 if (context().isEcma5()) {
082 JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
083 initializer.setName(ref);
084 JsInvocation call = new JsInvocation(new JsNameRef("call", new JsNameRef("baseInitializer", ref.makeRef())));
085 call.getArguments().add(JsLiteral.THIS);
086 call.getArguments().addAll(arguments);
087 initializerStatements.add(call.makeStmt());
088 }
089 else {
090 JsName superMethodName = context().scope().declareName(Namer.superMethodName());
091 initializerStatements.add(convertToStatement(new JsInvocation(new JsNameRef(superMethodName, JsLiteral.THIS), arguments)));
092 }
093 }
094
095 @NotNull
096 private List<JsExpression> translateArguments(@NotNull JetDelegatorToSuperCall superCall) {
097 return translateArgumentList(context(), superCall.getValueArguments());
098 }
099
100 @Nullable
101 private JetDelegatorToSuperCall getSuperCall() {
102 JetDelegatorToSuperCall result = null;
103 for (JetDelegationSpecifier specifier : classDeclaration.getDelegationSpecifiers()) {
104 if (specifier instanceof JetDelegatorToSuperCall) {
105 result = (JetDelegatorToSuperCall) specifier;
106 }
107 }
108 return result;
109 }
110
111 @NotNull
112 List<JsParameter> translatePrimaryConstructorParameters() {
113 List<JetParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
114 List<JsParameter> result = new ArrayList<JsParameter>();
115 for (JetParameter jetParameter : parameterList) {
116 result.add(translateParameter(jetParameter));
117 }
118 return result;
119 }
120
121 @NotNull
122 private JsParameter translateParameter(@NotNull JetParameter jetParameter) {
123 DeclarationDescriptor parameterDescriptor =
124 getDescriptorForElement(bindingContext(), jetParameter);
125 JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
126 JsParameter jsParameter = new JsParameter(parameterName);
127 mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
128 return jsParameter;
129 }
130
131 private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
132 @NotNull JetParameter jetParameter) {
133 PropertyDescriptor propertyDescriptor =
134 getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
135 if (propertyDescriptor == null) {
136 return;
137 }
138 JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
139 addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
140 }
141
142 private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
143 initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
144 }
145 }