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 }