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