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
017package org.jetbrains.k2js.translate.declaration;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import com.intellij.util.SmartList;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
024import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor;
026import org.jetbrains.jet.lang.descriptors.PropertySetterDescriptor;
027import org.jetbrains.jet.lang.psi.JetProperty;
028import org.jetbrains.jet.lang.psi.JetPropertyAccessor;
029import org.jetbrains.k2js.translate.context.TranslationContext;
030import org.jetbrains.k2js.translate.expression.FunctionTranslator;
031import org.jetbrains.k2js.translate.general.AbstractTranslator;
032import org.jetbrains.k2js.translate.general.Translation;
033import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
034import org.jetbrains.k2js.translate.utils.TranslationUtils;
035
036import java.util.List;
037
038import static org.jetbrains.k2js.translate.utils.TranslationUtils.assignmentToBackingField;
039import static org.jetbrains.k2js.translate.utils.TranslationUtils.backingFieldReference;
040
041/**
042 * Translates single property /w accessors.
043 */
044public 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 (context.isEcma5() && !JsDescriptorUtils.isAsPrivate(descriptor)) {
059            return;
060        }
061
062        new PropertyTranslator(descriptor, declaration, context).translate(result);
063    }
064
065    private PropertyTranslator(@NotNull PropertyDescriptor descriptor, @Nullable JetProperty declaration, @NotNull TranslationContext context) {
066        super(context);
067
068        this.descriptor = descriptor;
069        this.declaration = declaration;
070    }
071
072    private void translate(@NotNull List<JsPropertyInitializer> result) {
073        List<JsPropertyInitializer> to;
074        if (context().isEcma5() && !JsDescriptorUtils.isExtension(descriptor)) {
075            to = new SmartList<JsPropertyInitializer>();
076            result.add(new JsPropertyInitializer(context().nameToLiteral(descriptor), new JsObjectLiteral(to, true)));
077        }
078        else {
079            to = result;
080        }
081
082        to.add(generateGetter());
083        if (descriptor.isVar()) {
084            to.add(generateSetter());
085        }
086    }
087
088    private JsPropertyInitializer generateGetter() {
089        if (hasCustomGetter()) {
090            return translateCustomAccessor(getCustomGetterDeclaration());
091        }
092        else {
093            return generateDefaultGetter();
094        }
095    }
096
097    private JsPropertyInitializer generateSetter() {
098        if (hasCustomSetter()) {
099            return translateCustomAccessor(getCustomSetterDeclaration());
100        }
101        else {
102            return generateDefaultSetter();
103        }
104    }
105
106    private boolean hasCustomGetter() {
107        return declaration != null && declaration.getGetter() != null && getCustomGetterDeclaration().getBodyExpression() != null;
108    }
109
110    private boolean hasCustomSetter() {
111        return declaration != null && declaration.getSetter() != null && getCustomSetterDeclaration().getBodyExpression() != null;
112    }
113
114    @NotNull
115    private JetPropertyAccessor getCustomGetterDeclaration() {
116        assert declaration != null;
117        JetPropertyAccessor getterDeclaration = declaration.getGetter();
118        assert getterDeclaration != null;
119        return getterDeclaration;
120    }
121
122    @NotNull
123    private JetPropertyAccessor getCustomSetterDeclaration() {
124        assert declaration != null;
125        JetPropertyAccessor setter = declaration.getSetter();
126        assert setter != null;
127        return setter;
128    }
129
130    @NotNull
131    private JsPropertyInitializer generateDefaultGetter() {
132        PropertyGetterDescriptor getterDescriptor = descriptor.getGetter();
133        assert getterDescriptor != null : "Getter descriptor should not be null";
134        return generateDefaultAccessor(getterDescriptor, generateDefaultGetterFunction(getterDescriptor));
135    }
136
137    @NotNull
138    private JsFunction generateDefaultGetterFunction(@NotNull PropertyGetterDescriptor descriptor) {
139        JsFunction fun = new JsFunction(context().getScopeForDescriptor(descriptor.getContainingDeclaration()));
140        fun.setBody(new JsBlock(new JsReturn(backingFieldReference(context(), this.descriptor))));
141        return fun;
142    }
143
144    @NotNull
145    private JsPropertyInitializer generateDefaultSetter() {
146        PropertySetterDescriptor setterDescriptor = descriptor.getSetter();
147        assert setterDescriptor != null : "Setter descriptor should not be null";
148        return generateDefaultAccessor(setterDescriptor, generateDefaultSetterFunction(setterDescriptor));
149    }
150
151    @NotNull
152    private JsFunction generateDefaultSetterFunction(@NotNull PropertySetterDescriptor setterDescriptor) {
153        JsFunction fun = new JsFunction(context().getScopeForDescriptor(setterDescriptor.getContainingDeclaration()));
154        JsParameter defaultParameter = new JsParameter(propertyAccessContext(setterDescriptor).scope().declareTemporary());
155        fun.getParameters().add(defaultParameter);
156        fun.setBody(new JsBlock(assignmentToBackingField(context(), descriptor, defaultParameter.getName().makeRef()).makeStmt()));
157        return fun;
158    }
159
160    @NotNull
161    private JsPropertyInitializer generateDefaultAccessor(@NotNull PropertyAccessorDescriptor accessorDescriptor,
162            @NotNull JsFunction function) {
163        if (context().isEcma5()) {
164            return TranslationUtils.translateFunctionAsEcma5PropertyDescriptor(function, accessorDescriptor, context());
165        }
166        else {
167            return new JsPropertyInitializer(context().getNameForDescriptor(accessorDescriptor).makeRef(), function);
168        }
169    }
170
171    @NotNull
172    private TranslationContext propertyAccessContext(@NotNull PropertySetterDescriptor propertySetterDescriptor) {
173        return context().newDeclaration(propertySetterDescriptor);
174    }
175
176    @NotNull
177    private JsPropertyInitializer translateCustomAccessor(@NotNull JetPropertyAccessor expression) {
178        FunctionTranslator translator = Translation.functionTranslator(expression, context());
179        return context().isEcma5() ? translator.translateAsEcma5PropertyDescriptor() : translator.translateAsMethod();
180    }
181}