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