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    }