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.BindingContext;
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 = context().bindingContext().get(BindingContext.RESOLVED_CALL, superCall.getCalleeExpression());
128            assert call != null : "ResolvedCall for superCall must be not null";
129            return CallArgumentTranslator.translate(call, null, context()).getTranslateArguments();
130        }
131    
132        @Nullable
133        private JetDelegatorToSuperCall getSuperCall() {
134            JetDelegatorToSuperCall result = null;
135            for (JetDelegationSpecifier specifier : classDeclaration.getDelegationSpecifiers()) {
136                if (specifier instanceof JetDelegatorToSuperCall) {
137                    result = (JetDelegatorToSuperCall) specifier;
138                }
139            }
140            return result;
141        }
142    
143        @NotNull
144        List<JsParameter> translatePrimaryConstructorParameters() {
145            List<JetParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
146            List<JsParameter> result = new ArrayList<JsParameter>();
147            for (JetParameter jetParameter : parameterList) {
148                result.add(translateParameter(jetParameter));
149            }
150            return result;
151        }
152    
153        @NotNull
154        private JsParameter translateParameter(@NotNull JetParameter jetParameter) {
155            DeclarationDescriptor parameterDescriptor =
156                    getDescriptorForElement(bindingContext(), jetParameter);
157            JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
158            JsParameter jsParameter = new JsParameter(parameterName);
159            mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
160            return jsParameter;
161        }
162    
163        private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
164                @NotNull JetParameter jetParameter) {
165            PropertyDescriptor propertyDescriptor =
166                    getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
167            if (propertyDescriptor == null) {
168                return;
169            }
170            JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
171            addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
172        }
173    
174        private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
175            initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
176        }
177    }