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 }