001    /*
002     * Copyright 2010-2015 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.kotlin.js.translate.initializer;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.intellij.util.SmartList;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
025    import org.jetbrains.kotlin.js.translate.context.Namer;
026    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
027    import org.jetbrains.kotlin.js.translate.declaration.DelegationTranslator;
028    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
029    import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator;
030    import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.AstUtilsKt;
031    import org.jetbrains.kotlin.lexer.KtTokens;
032    import org.jetbrains.kotlin.psi.*;
033    import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
034    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
035    import org.jetbrains.kotlin.types.KotlinType;
036    
037    import java.util.ArrayList;
038    import java.util.Collections;
039    import java.util.List;
040    
041    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.*;
042    import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
043    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
044    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType;
045    
046    public final class ClassInitializerTranslator extends AbstractTranslator {
047        @NotNull
048        private final KtClassOrObject classDeclaration;
049        @NotNull
050        private final List<JsStatement> initializerStatements = new SmartList<JsStatement>();
051        private final JsFunction initFunction;
052        private final TranslationContext context;
053    
054        public ClassInitializerTranslator(
055                @NotNull KtClassOrObject classDeclaration,
056                @NotNull TranslationContext context
057        ) {
058            super(context);
059            this.classDeclaration = classDeclaration;
060            this.initFunction = createInitFunction(classDeclaration, context);
061            this.context = context.contextWithScope(initFunction);
062        }
063    
064        @NotNull
065        @Override
066        protected TranslationContext context() {
067            return context;
068        }
069    
070        @NotNull
071        private static JsFunction createInitFunction(KtClassOrObject declaration, TranslationContext context) {
072            //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
073            ClassDescriptor classDescriptor = getClassDescriptor(context.bindingContext(), declaration);
074            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
075    
076            if (primaryConstructor != null) {
077                return context.getFunctionObject(primaryConstructor);
078            }
079            else {
080                return new JsFunction(context.scope(), new JsBlock(), "fake constructor for " + classDescriptor.getName().asString());
081            }
082        }
083    
084        @NotNull
085        public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) {
086            ClassDescriptor classDescriptor = getClassDescriptor(bindingContext(), classDeclaration);
087            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
088    
089            if (primaryConstructor != null) {
090                initFunction.getBody().getStatements().addAll(setDefaultValueForArguments(primaryConstructor, context()));
091    
092                //NOTE: while we translate constructor parameters we also add property initializer statements
093                // for properties declared as constructor parameters
094                initFunction.getParameters().addAll(translatePrimaryConstructorParameters());
095    
096                mayBeAddCallToSuperMethod(initFunction);
097            }
098    
099            delegationTranslator.addInitCode(initializerStatements);
100            new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context());
101    
102            List<JsStatement> statements = initFunction.getBody().getStatements();
103    
104            for (JsStatement statement : initializerStatements) {
105                if (statement instanceof JsBlock) {
106                    statements.addAll(((JsBlock) statement).getStatements());
107                }
108                else {
109                    statements.add(statement);
110                }
111            }
112    
113            return initFunction;
114        }
115    
116        @NotNull
117        public JsExpression generateEnumEntryInstanceCreation(@NotNull KotlinType enumClassType) {
118            ResolvedCall<FunctionDescriptor> superCall = getSuperCall();
119    
120            if (superCall == null) {
121                ClassDescriptor classDescriptor = getClassDescriptorForType(enumClassType);
122                JsNameRef reference = context().getQualifiedReference(classDescriptor);
123                return new JsNew(reference);
124            }
125    
126            return CallTranslator.translate(context(), superCall);
127        }
128    
129        private void mayBeAddCallToSuperMethod(JsFunction initializer) {
130            if (classDeclaration.hasModifier(KtTokens.ENUM_KEYWORD)) {
131                addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
132                return;
133            }
134            if (hasAncestorClass(bindingContext(), classDeclaration)) {
135                ResolvedCall<FunctionDescriptor> superCall = getSuperCall();
136                if (superCall == null) return;
137    
138                if (classDeclaration instanceof KtEnumEntry) {
139                    JsExpression expression = CallTranslator.translate(context(), superCall, null);
140                    JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, JsLiteral.THIS);
141                    initializerStatements.add(0, fixedInvocation.makeStmt());
142                }
143                else {
144                    List<JsExpression> arguments = CallArgumentTranslator.translate(superCall, null, context()).getTranslateArguments();
145                    addCallToSuperMethod(arguments, initializer);
146                }
147            }
148        }
149    
150        private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) {
151            JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
152            initializer.setName(ref);
153            JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(ref)));
154            call.getArguments().add(JsLiteral.THIS);
155            call.getArguments().addAll(arguments);
156            initializerStatements.add(0, call.makeStmt());
157        }
158    
159        @Nullable
160        private ResolvedCall<FunctionDescriptor> getSuperCall() {
161            for (KtDelegationSpecifier specifier : classDeclaration.getDelegationSpecifiers()) {
162                if (specifier instanceof KtDelegatorToSuperCall) {
163                    KtDelegatorToSuperCall superCall = (KtDelegatorToSuperCall) specifier;
164                    //noinspection unchecked
165                    return (ResolvedCall<FunctionDescriptor>) CallUtilKt.getResolvedCallWithAssert(superCall, bindingContext());
166                }
167            }
168            return null;
169        }
170    
171        @NotNull
172        List<JsParameter> translatePrimaryConstructorParameters() {
173            List<KtParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
174            List<JsParameter> result = new ArrayList<JsParameter>();
175            for (KtParameter jetParameter : parameterList) {
176                result.add(translateParameter(jetParameter));
177            }
178            return result;
179        }
180    
181        @NotNull
182        private JsParameter translateParameter(@NotNull KtParameter jetParameter) {
183            DeclarationDescriptor parameterDescriptor =
184                    getDescriptorForElement(bindingContext(), jetParameter);
185            JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
186            JsParameter jsParameter = new JsParameter(parameterName);
187            mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
188            return jsParameter;
189        }
190    
191        private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
192                @NotNull KtParameter jetParameter) {
193            PropertyDescriptor propertyDescriptor =
194                    getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
195            if (propertyDescriptor == null) {
196                return;
197            }
198            JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
199            addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
200        }
201    
202        private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
203            initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
204        }
205    }