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