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.declaration.DelegationTranslator;
037    import org.jetbrains.k2js.translate.general.AbstractTranslator;
038    import org.jetbrains.k2js.translate.reference.CallArgumentTranslator;
039    
040    import java.util.ArrayList;
041    import java.util.Collections;
042    import java.util.List;
043    
044    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
045    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
046    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
047    import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
048    
049    public final class ClassInitializerTranslator extends AbstractTranslator {
050        @NotNull
051        private final JetClassOrObject classDeclaration;
052        @NotNull
053        private final List<JsStatement> initializerStatements = new SmartList<JsStatement>();
054    
055        public ClassInitializerTranslator(
056                @NotNull JetClassOrObject classDeclaration,
057                @NotNull TranslationContext context
058        ) {
059            // Note: it's important we use scope for class descriptor because anonymous function used in property initializers
060            // belong to the properties themselves
061            super(context.newDeclaration(getConstructor(context.bindingContext(), classDeclaration), null));
062            this.classDeclaration = classDeclaration;
063        }
064    
065        @NotNull
066        public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) {
067            //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
068            ConstructorDescriptor primaryConstructor = getConstructor(bindingContext(), classDeclaration);
069            JsFunction result = context().getFunctionObject(primaryConstructor);
070            //NOTE: while we translate constructor parameters we also add property initializer statements
071            // for properties declared as constructor parameters
072            result.getParameters().addAll(translatePrimaryConstructorParameters());
073            mayBeAddCallToSuperMethod(result);
074            delegationTranslator.addInitCode(initializerStatements);
075            new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context());
076    
077            List<JsStatement> statements = result.getBody().getStatements();
078            statements.addAll(setDefaultValueForArguments(primaryConstructor, context()));
079            for (JsStatement statement : initializerStatements) {
080                if (statement instanceof JsBlock) {
081                    statements.addAll(((JsBlock) statement).getStatements());
082                }
083                else {
084                    statements.add(statement);
085                }
086            }
087    
088            return result;
089        }
090    
091        @NotNull
092        public JsExpression generateEnumEntryInstanceCreation(@NotNull JetType enumClassType) {
093            JetDelegatorToSuperCall superCall = getSuperCall();
094            List<JsExpression> arguments;
095            if (superCall != null) {
096                arguments = translateArguments(superCall);
097            } else {
098                arguments = Collections.emptyList();
099            }
100            JsNameRef reference = context().getQualifiedReference(getClassDescriptorForType(enumClassType));
101            return new JsNew(reference, arguments);
102        }
103    
104        private void mayBeAddCallToSuperMethod(JsFunction initializer) {
105            if (classDeclaration.hasModifier(JetTokens.ENUM_KEYWORD)) {
106                addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
107                return;
108            }
109            if (hasAncestorClass(bindingContext(), classDeclaration)) {
110                JetDelegatorToSuperCall superCall = getSuperCall();
111                if (superCall == null) {
112                    return;
113                }
114                addCallToSuperMethod(translateArguments(superCall), initializer);
115            }
116        }
117    
118        private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) {
119            JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
120            initializer.setName(ref);
121            JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(ref)));
122            call.getArguments().add(JsLiteral.THIS);
123            call.getArguments().addAll(arguments);
124            initializerStatements.add(0, call.makeStmt());
125        }
126    
127        @NotNull
128        private List<JsExpression> translateArguments(@NotNull JetDelegatorToSuperCall superCall) {
129            ResolvedCall<?> call = CallUtilPackage.getResolvedCallWithAssert(superCall, context().bindingContext());
130            return CallArgumentTranslator.translate(call, null, context()).getTranslateArguments();
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    }