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.PropertyAccessorDescriptor;
024    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025    import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor;
026    import org.jetbrains.jet.lang.descriptors.PropertySetterDescriptor;
027    import org.jetbrains.jet.lang.psi.JetProperty;
028    import org.jetbrains.jet.lang.psi.JetPropertyAccessor;
029    import org.jetbrains.k2js.translate.context.TranslationContext;
030    import org.jetbrains.k2js.translate.expression.FunctionTranslator;
031    import org.jetbrains.k2js.translate.general.AbstractTranslator;
032    import org.jetbrains.k2js.translate.general.Translation;
033    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
034    import org.jetbrains.k2js.translate.utils.TranslationUtils;
035    
036    import java.util.List;
037    
038    import static org.jetbrains.k2js.translate.utils.TranslationUtils.assignmentToBackingField;
039    import static org.jetbrains.k2js.translate.utils.TranslationUtils.backingFieldReference;
040    
041    /**
042     * Translates single property /w accessors.
043     */
044    public 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    }