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
017package org.jetbrains.k2js.translate.initializer;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import org.jetbrains.annotations.NotNull;
021import org.jetbrains.annotations.Nullable;
022import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
023import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
024import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025import org.jetbrains.jet.lang.psi.JetClassOrObject;
026import org.jetbrains.jet.lang.psi.JetDelegationSpecifier;
027import org.jetbrains.jet.lang.psi.JetDelegatorToSuperCall;
028import org.jetbrains.jet.lang.psi.JetParameter;
029import org.jetbrains.k2js.translate.context.Namer;
030import org.jetbrains.k2js.translate.context.TranslationContext;
031import org.jetbrains.k2js.translate.general.AbstractTranslator;
032
033import java.util.ArrayList;
034import java.util.List;
035
036import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
037import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
038import static org.jetbrains.k2js.translate.utils.JsAstUtils.setParameters;
039import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
040import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateArgumentList;
041
042public 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}