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.declaration;
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.*;
024    import org.jetbrains.jet.lang.psi.JetProperty;
025    import org.jetbrains.jet.lang.psi.JetPropertyAccessor;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
028    import org.jetbrains.k2js.translate.callTranslator.CallTranslator;
029    import org.jetbrains.k2js.translate.context.TranslationContext;
030    import org.jetbrains.k2js.translate.expression.FunctionTranslator;
031    import org.jetbrains.k2js.translate.general.AbstractTranslator;
032    import org.jetbrains.k2js.translate.general.Translation;
033    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
034    import org.jetbrains.k2js.translate.utils.TranslationUtils;
035    
036    import java.util.List;
037    
038    import static org.jetbrains.k2js.translate.context.Namer.getDelegateNameRef;
039    import static org.jetbrains.k2js.translate.utils.TranslationUtils.*;
040    
041    /**
042     * Translates single property /w accessors.
043     */
044    public final class PropertyTranslator extends AbstractTranslator {
045        @NotNull
046        private final PropertyDescriptor descriptor;
047        @Nullable
048        private final JetProperty declaration;
049    
050        public static void translateAccessors(@NotNull PropertyDescriptor descriptor, @NotNull List<JsPropertyInitializer> result, @NotNull TranslationContext context) {
051            translateAccessors(descriptor, null, result, context);
052        }
053    
054        public static void translateAccessors(@NotNull PropertyDescriptor descriptor,
055                @Nullable JetProperty declaration,
056                @NotNull List<JsPropertyInitializer> result,
057                @NotNull TranslationContext context) {
058            if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
059                new PropertyTranslator(descriptor, declaration, context).translate(result);
060            }
061        }
062    
063        private PropertyTranslator(@NotNull PropertyDescriptor descriptor, @Nullable JetProperty declaration, @NotNull TranslationContext context) {
064            super(context);
065    
066            this.descriptor = descriptor;
067            this.declaration = declaration;
068        }
069    
070        private void translate(@NotNull List<JsPropertyInitializer> result) {
071            List<JsPropertyInitializer> to;
072            if (!JsDescriptorUtils.isExtension(descriptor)) {
073                to = new SmartList<JsPropertyInitializer>();
074                result.add(new JsPropertyInitializer(context().getNameForDescriptor(descriptor).makeRef(), new JsObjectLiteral(to, true)));
075            }
076            else {
077                to = result;
078            }
079    
080            to.add(generateGetter());
081            if (descriptor.isVar()) {
082                to.add(generateSetter());
083            }
084        }
085    
086        private JsPropertyInitializer generateGetter() {
087            if (hasCustomGetter()) {
088                return translateCustomAccessor(getCustomGetterDeclaration());
089            }
090            else {
091                return generateDefaultGetter();
092            }
093        }
094    
095        private JsPropertyInitializer generateSetter() {
096            if (hasCustomSetter()) {
097                return translateCustomAccessor(getCustomSetterDeclaration());
098            }
099            else {
100                return generateDefaultSetter();
101            }
102        }
103    
104        private boolean hasCustomGetter() {
105            return declaration != null && declaration.getGetter() != null && getCustomGetterDeclaration().getBodyExpression() != null;
106        }
107    
108        private boolean hasCustomSetter() {
109            return declaration != null && declaration.getSetter() != null && getCustomSetterDeclaration().getBodyExpression() != null;
110        }
111    
112        @NotNull
113        private JetPropertyAccessor getCustomGetterDeclaration() {
114            assert declaration != null;
115            JetPropertyAccessor getterDeclaration = declaration.getGetter();
116            assert getterDeclaration != null;
117            return getterDeclaration;
118        }
119    
120        @NotNull
121        private JetPropertyAccessor getCustomSetterDeclaration() {
122            assert declaration != null;
123            JetPropertyAccessor setter = declaration.getSetter();
124            assert setter != null;
125            return setter;
126        }
127    
128        @NotNull
129        private JsPropertyInitializer generateDefaultGetter() {
130            PropertyGetterDescriptor getterDescriptor = descriptor.getGetter();
131            assert getterDescriptor != null : "Getter descriptor should not be null";
132            return generateDefaultAccessor(getterDescriptor, generateDefaultGetterFunction(getterDescriptor));
133        }
134    
135        private String getPropertyName() {
136            return descriptor.getName().asString();
137        }
138    
139        @NotNull
140        private JsFunction generateDefaultGetterFunction(@NotNull PropertyGetterDescriptor getterDescriptor) {
141            JsExpression value;
142            ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor);
143            if (delegatedCall != null) {
144                value = CallTranslator.instance$.translate(context(), delegatedCall, getDelegateNameRef(getPropertyName()));
145            } else {
146                value = backingFieldReference(context(), this.descriptor);
147            }
148            return simpleReturnFunction(context().getScopeForDescriptor(getterDescriptor.getContainingDeclaration()), value);
149        }
150    
151        @NotNull
152        private JsPropertyInitializer generateDefaultSetter() {
153            PropertySetterDescriptor setterDescriptor = descriptor.getSetter();
154            assert setterDescriptor != null : "Setter descriptor should not be null";
155            return generateDefaultAccessor(setterDescriptor, generateDefaultSetterFunction(setterDescriptor));
156        }
157    
158        @NotNull
159        private JsFunction generateDefaultSetterFunction(@NotNull PropertySetterDescriptor setterDescriptor) {
160            JsFunction fun = new JsFunction(context().getScopeForDescriptor(setterDescriptor.getContainingDeclaration()));
161    
162            assert setterDescriptor.getValueParameters().size() == 1 : "Setter must have 1 parameter";
163            ValueParameterDescriptor valueParameterDescriptor = setterDescriptor.getValueParameters().get(0);
164            JsParameter defaultParameter = new JsParameter(fun.getScope().declareTemporary());
165            JsNameRef defaultParameterRef = defaultParameter.getName().makeRef();
166    
167            fun.getParameters().add(defaultParameter);
168            TranslationContext contextWithAliased = context().innerContextWithAliased(valueParameterDescriptor, defaultParameterRef);
169    
170            JsExpression setExpression;
171            ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL,
172                                                                                  setterDescriptor);
173            if (delegatedCall != null) {
174                setExpression = CallTranslator.instance$.translate(contextWithAliased, delegatedCall, getDelegateNameRef(getPropertyName()));
175            } else {
176                setExpression = assignmentToBackingField(contextWithAliased, descriptor, defaultParameterRef);
177            }
178            fun.setBody(new JsBlock(setExpression.makeStmt()));
179            return fun;
180        }
181    
182        @NotNull
183        private JsPropertyInitializer generateDefaultAccessor(@NotNull PropertyAccessorDescriptor accessorDescriptor,
184                @NotNull JsFunction function) {
185            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(function, accessorDescriptor, context());
186        }
187    
188        @NotNull
189        private JsPropertyInitializer translateCustomAccessor(@NotNull JetPropertyAccessor expression) {
190            FunctionTranslator translator = Translation.functionTranslator(expression, context());
191            return translator.translateAsEcma5PropertyDescriptor();
192        }
193    }