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.utils;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.intellij.openapi.util.Pair;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
024    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
025    import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor;
026    import org.jetbrains.jet.lang.descriptors.Visibilities;
027    import org.jetbrains.jet.lang.psi.*;
028    import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
029    import org.jetbrains.k2js.translate.context.TranslationContext;
030    import org.jetbrains.k2js.translate.general.Translation;
031    
032    import java.util.ArrayList;
033    import java.util.Collections;
034    import java.util.List;
035    
036    import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
037    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFQName;
038    import static org.jetbrains.k2js.translate.context.Namer.getKotlinBackingFieldName;
039    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression;
040    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
041    import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor;
042    
043    public final class TranslationUtils {
044        private TranslationUtils() {
045        }
046    
047        @NotNull
048        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
049                @NotNull FunctionDescriptor descriptor,
050                @NotNull TranslationContext context) {
051            if (JsDescriptorUtils.isExtension(descriptor)) {
052                return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
053            }
054            else {
055                JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
056                return new JsPropertyInitializer(getOrSet, function);
057            }
058        }
059    
060        @NotNull
061        public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
062            return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)));
063        }
064    
065        @NotNull
066        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
067                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
068            JsObjectLiteral meta = createDataDescriptor(function, descriptor.getModality().isOverridable(), false);
069            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
070        }
071    
072        @NotNull
073        public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
074            return new JsBinaryOperation(notOperator(baseBinaryExpression.getOperator()), baseBinaryExpression.getArg1(), baseBinaryExpression.getArg2());
075        }
076    
077        public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
078            return notOperator(operator) != null;
079        }
080    
081        @Nullable
082        private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
083            switch (operator) {
084                case REF_EQ:
085                    return REF_NEQ;
086                case REF_NEQ:
087                    return REF_EQ;
088                case EQ:
089                    return NEQ;
090                case NEQ:
091                    return EQ;
092                default:
093                    return null;
094            }
095        }
096    
097        @NotNull
098        public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
099            return nullCheck(expressionToCheck, false);
100        }
101    
102        @NotNull
103        public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
104            return nullCheck(expressionToCheck, true);
105        }
106    
107        @NotNull
108        public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
109            JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
110            return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
111        }
112    
113        @NotNull
114        public static JsConditional notNullConditional(
115                @NotNull JsExpression expression,
116                @NotNull JsExpression elseExpression,
117                @NotNull TranslationContext context
118        ) {
119            JsExpression testExpression;
120            JsExpression thenExpression;
121            if (isCacheNeeded(expression)) {
122                TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
123                testExpression = isNotNullCheck(tempVar.value());
124                thenExpression = tempVar.value();
125            }
126            else {
127                testExpression = isNotNullCheck(expression);
128                thenExpression = expression;
129            }
130    
131            return new JsConditional(testExpression, thenExpression, elseExpression);
132        }
133    
134        @NotNull
135        public static String getMangledName(@NotNull PropertyDescriptor descriptor, @NotNull String suggestedName) {
136            int absHashCode = Math.abs(getFQName(descriptor).asString().hashCode());
137            return suggestedName + "_" + Integer.toString(absHashCode, Character.MAX_RADIX) + "$";
138        }
139    
140        @NotNull
141        public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
142                @NotNull PropertyDescriptor descriptor) {
143            JsName backingFieldName = context.getNameForDescriptor(descriptor);
144            if(!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
145                String backingFieldMangledName;
146                if (descriptor.getVisibility() != Visibilities.PRIVATE) {
147                    backingFieldMangledName = getMangledName(descriptor, getKotlinBackingFieldName(backingFieldName.getIdent()));
148                } else {
149                    backingFieldMangledName = getKotlinBackingFieldName(backingFieldName.getIdent());
150                }
151                backingFieldName = context.declarePropertyOrPropertyAccessorName(descriptor, backingFieldMangledName, false);
152            }
153            return new JsNameRef(backingFieldName, JsLiteral.THIS);
154        }
155    
156        @NotNull
157        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
158                @NotNull PropertyDescriptor descriptor,
159                @NotNull JsExpression assignTo) {
160            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
161            return assignment(backingFieldReference, assignTo);
162        }
163    
164        @Nullable
165        public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
166                @NotNull TranslationContext context) {
167            JsExpression jsInitExpression = null;
168            JetExpression initializer = declaration.getInitializer();
169            if (initializer != null) {
170                jsInitExpression = Translation.translateAsExpression(initializer, context);
171            }
172            return jsInitExpression;
173        }
174    
175        @NotNull
176        public static List<JsExpression> translateExpressionList(@NotNull TranslationContext context,
177                @NotNull List<JetExpression> expressions) {
178            List<JsExpression> result = new ArrayList<JsExpression>();
179            for (JetExpression expression : expressions) {
180                result.add(Translation.translateAsExpression(expression, context));
181            }
182            return result;
183        }
184    
185        @NotNull
186        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
187                @NotNull JetUnaryExpression expression) {
188            JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
189            return Translation.translateAsExpression(baseExpression, context);
190        }
191    
192        @NotNull
193        public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
194                @NotNull JetBinaryExpression expression) {
195            JetExpression left = expression.getLeft();
196            assert left != null : "Binary expression should have a left expression: " + expression.getText();
197            return Translation.translateAsExpression(left, context);
198        }
199    
200        @NotNull
201        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
202                @NotNull JetBinaryExpression expression) {
203            JetExpression rightExpression = expression.getRight();
204            assert rightExpression != null : "Binary expression should have a right expression";
205            return Translation.translateAsExpression(rightExpression, context);
206        }
207    
208        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
209                @NotNull JetOperationExpression expression) {
210            FunctionDescriptor operationDescriptor = getFunctionDescriptorForOperationExpression(context.bindingContext(), expression);
211    
212            if (operationDescriptor == null) return true;
213            if (context.intrinsics().getFunctionIntrinsics().getIntrinsic(operationDescriptor).exists()) return true;
214    
215            return false;
216        }
217    
218        @NotNull
219        public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
220            if (arguments.isEmpty()) {
221                return Collections.singletonList(receiver);
222            }
223    
224            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
225            argumentList.add(receiver);
226            argumentList.addAll(arguments);
227            return argumentList;
228        }
229    
230        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
231            return !(expression instanceof JsLiteral) &&
232                   (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
233        }
234    
235        @NotNull
236        public static Pair<JsVars.JsVar, JsExpression> createTemporaryIfNeed(
237                @NotNull JsExpression expression,
238                @NotNull TranslationContext context
239        ) {
240            // don't create temp variable for simple expression
241            if (isCacheNeeded(expression)) {
242                return context.dynamicContext().createTemporary(expression);
243            }
244            else {
245                return Pair.create(null, expression);
246            }
247        }
248    
249        @NotNull
250        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
251            JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
252            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
253    
254            JsExpression thenExpression = ensureNotNull.getThenExpression();
255            if (thenExpression instanceof JsNameRef) {
256                // associate (cache) ensureNotNull expression to new TemporaryConstVariable with same name.
257                context.associateExpressionToLazyValue(ensureNotNull,
258                                                       new TemporaryConstVariable(((JsNameRef) thenExpression).getName(), ensureNotNull));
259            }
260    
261            return ensureNotNull;
262        }
263    }