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.context.TranslationContext;
029    import org.jetbrains.k2js.translate.expression.FunctionTranslator;
030    import org.jetbrains.k2js.translate.general.AbstractTranslator;
031    import org.jetbrains.k2js.translate.general.Translation;
032    import org.jetbrains.k2js.translate.reference.CallBuilder;
033    import org.jetbrains.k2js.translate.reference.CallType;
034    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
035    import org.jetbrains.k2js.translate.utils.TranslationUtils;
036    
037    import java.util.ArrayList;
038    import java.util.Collections;
039    import java.util.List;
040    
041    import static org.jetbrains.k2js.translate.context.Namer.getDelegateNameRef;
042    import static org.jetbrains.k2js.translate.utils.TranslationUtils.*;
043    
044    /**
045     * Translates single property /w accessors.
046     */
047    public final class PropertyTranslator extends AbstractTranslator {
048        @NotNull
049        private final PropertyDescriptor descriptor;
050        @Nullable
051        private final JetProperty declaration;
052    
053        public static void translateAccessors(@NotNull PropertyDescriptor descriptor, @NotNull List<JsPropertyInitializer> result, @NotNull TranslationContext context) {
054            translateAccessors(descriptor, null, result, context);
055        }
056    
057        public static void translateAccessors(@NotNull PropertyDescriptor descriptor,
058                @Nullable JetProperty declaration,
059                @NotNull List<JsPropertyInitializer> result,
060                @NotNull TranslationContext context) {
061            if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
062                new PropertyTranslator(descriptor, declaration, context).translate(result);
063            }
064        }
065    
066        private PropertyTranslator(@NotNull PropertyDescriptor descriptor, @Nullable JetProperty declaration, @NotNull TranslationContext context) {
067            super(context);
068    
069            this.descriptor = descriptor;
070            this.declaration = declaration;
071        }
072    
073        private void translate(@NotNull List<JsPropertyInitializer> result) {
074            List<JsPropertyInitializer> to;
075            if (!JsDescriptorUtils.isExtension(descriptor)) {
076                to = new SmartList<JsPropertyInitializer>();
077                result.add(new JsPropertyInitializer(context().getNameForDescriptor(descriptor).makeRef(), new JsObjectLiteral(to, true)));
078            }
079            else {
080                to = result;
081            }
082    
083            to.add(generateGetter());
084            if (descriptor.isVar()) {
085                to.add(generateSetter());
086            }
087        }
088    
089        private JsPropertyInitializer generateGetter() {
090            if (hasCustomGetter()) {
091                return translateCustomAccessor(getCustomGetterDeclaration());
092            }
093            else {
094                return generateDefaultGetter();
095            }
096        }
097    
098        private JsPropertyInitializer generateSetter() {
099            if (hasCustomSetter()) {
100                return translateCustomAccessor(getCustomSetterDeclaration());
101            }
102            else {
103                return generateDefaultSetter();
104            }
105        }
106    
107        private boolean hasCustomGetter() {
108            return declaration != null && declaration.getGetter() != null && getCustomGetterDeclaration().getBodyExpression() != null;
109        }
110    
111        private boolean hasCustomSetter() {
112            return declaration != null && declaration.getSetter() != null && getCustomSetterDeclaration().getBodyExpression() != null;
113        }
114    
115        @NotNull
116        private JetPropertyAccessor getCustomGetterDeclaration() {
117            assert declaration != null;
118            JetPropertyAccessor getterDeclaration = declaration.getGetter();
119            assert getterDeclaration != null;
120            return getterDeclaration;
121        }
122    
123        @NotNull
124        private JetPropertyAccessor getCustomSetterDeclaration() {
125            assert declaration != null;
126            JetPropertyAccessor setter = declaration.getSetter();
127            assert setter != null;
128            return setter;
129        }
130    
131        @NotNull
132        private JsPropertyInitializer generateDefaultGetter() {
133            PropertyGetterDescriptor getterDescriptor = descriptor.getGetter();
134            assert getterDescriptor != null : "Getter descriptor should not be null";
135            return generateDefaultAccessor(getterDescriptor, generateDefaultGetterFunction(getterDescriptor));
136        }
137    
138        private JsExpression createPropertyMetadata() {
139            JsNameRef propertyMetadataRef = context().namer().propertyMetadataRef();
140            JsExpression argument = context().program().getStringLiteral(getPropertyName());
141            return new JsNew(propertyMetadataRef, Collections.singletonList(argument));
142        }
143    
144        private JsExpression getDelegateCall(ResolvedCall<FunctionDescriptor> call, List<JsExpression> args) {
145            return CallBuilder.build(context())
146                    .receiver(getDelegateNameRef(getPropertyName()))
147                    .args(args)
148                    .resolvedCall(call)
149                    .type(CallType.NORMAL)
150                    .translate();
151        }
152    
153        private String getPropertyName() {
154            return descriptor.getName().asString();
155        }
156    
157        @NotNull
158        private JsFunction generateDefaultGetterFunction(@NotNull PropertyGetterDescriptor getterDescriptor) {
159            JsExpression value;
160            ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor);
161            if (delegatedCall != null) {
162                value = getDelegateCall(delegatedCall, getDelegateCallArgs(null));
163            } else {
164                value = backingFieldReference(context(), this.descriptor);
165            }
166            return simpleReturnFunction(context().getScopeForDescriptor(getterDescriptor.getContainingDeclaration()), value);
167        }
168    
169        @NotNull
170        private List<JsExpression> getDelegateCallArgs(@Nullable JsExpression valueExpression) {
171            List<JsExpression> args = new ArrayList<JsExpression>();
172            args.add(JsLiteral.THIS);
173            args.add(createPropertyMetadata());
174            if (valueExpression != null) {
175                args.add(valueExpression);
176            }
177            return args;
178        }
179    
180        @NotNull
181        private JsPropertyInitializer generateDefaultSetter() {
182            PropertySetterDescriptor setterDescriptor = descriptor.getSetter();
183            assert setterDescriptor != null : "Setter descriptor should not be null";
184            return generateDefaultAccessor(setterDescriptor, generateDefaultSetterFunction(setterDescriptor));
185        }
186    
187        @NotNull
188        private JsFunction generateDefaultSetterFunction(@NotNull PropertySetterDescriptor setterDescriptor) {
189            JsFunction fun = new JsFunction(context().getScopeForDescriptor(setterDescriptor.getContainingDeclaration()));
190            JsParameter defaultParameter = new JsParameter(propertyAccessContext(setterDescriptor).scope().declareTemporary());
191            fun.getParameters().add(defaultParameter);
192            JsExpression setExpression;
193    
194            ResolvedCall<FunctionDescriptor> delegatedCall = bindingContext().get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, setterDescriptor);
195            JsNameRef defaultParameterRef = defaultParameter.getName().makeRef();
196            if (delegatedCall != null) {
197                setExpression = getDelegateCall(delegatedCall, getDelegateCallArgs(defaultParameterRef));
198            } else {
199                setExpression = assignmentToBackingField(context(), descriptor, defaultParameterRef);
200            }
201            fun.setBody(new JsBlock(setExpression.makeStmt()));
202            return fun;
203        }
204    
205        @NotNull
206        private JsPropertyInitializer generateDefaultAccessor(@NotNull PropertyAccessorDescriptor accessorDescriptor,
207                @NotNull JsFunction function) {
208            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(function, accessorDescriptor, context());
209        }
210    
211        @NotNull
212        private TranslationContext propertyAccessContext(@NotNull PropertySetterDescriptor propertySetterDescriptor) {
213            return context().newDeclaration(propertySetterDescriptor);
214        }
215    
216        @NotNull
217        private JsPropertyInitializer translateCustomAccessor(@NotNull JetPropertyAccessor expression) {
218            FunctionTranslator translator = Translation.functionTranslator(expression, context());
219            return translator.translateAsEcma5PropertyDescriptor();
220        }
221    }