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