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.context;
018    
019    import com.google.dart.compiler.backend.js.ast.JsExpression;
020    import com.google.dart.compiler.backend.js.ast.JsName;
021    import gnu.trove.THashMap;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025    import org.jetbrains.jet.lang.psi.JetExpression;
026    
027    import java.util.Collections;
028    import java.util.Map;
029    
030    public class AliasingContext {
031        private static final AliasingContext ROOT = new AliasingContext(null) {
032            @Override
033            protected JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor, boolean fromChild) {
034                return null;
035            }
036    
037            @Override
038            public JsName getAliasForExpression(@NotNull JetExpression element) {
039                return null;
040            }
041    
042            @Override
043            public void registerAlias(@NotNull DeclarationDescriptor descriptor, @NotNull JsExpression alias) {
044                throw new IllegalStateException();
045            }
046        };
047    
048        public static AliasingContext getCleanContext() {
049            return new AliasingContext(ROOT);
050        }
051    
052        @Nullable
053        private Map<DeclarationDescriptor, JsExpression> aliasesForDescriptors;
054    
055        @Nullable
056        private final Map<JetExpression, JsName> aliasesForExpressions;
057    
058        @Nullable
059        private final AliasingContext parent;
060    
061        /*package*/ AliasingContext(@Nullable AliasingContext parent) {
062            this(parent, null, null);
063        }
064    
065        private AliasingContext(
066                @Nullable AliasingContext parent,
067                @Nullable Map<DeclarationDescriptor, JsExpression> aliasesForDescriptors,
068                @Nullable Map<JetExpression, JsName> aliasesForExpressions
069        ) {
070            this.parent = parent;
071            this.aliasesForDescriptors = aliasesForDescriptors;
072            this.aliasesForExpressions = aliasesForExpressions;
073        }
074    
075        @NotNull
076        public AliasingContext inner(@NotNull DeclarationDescriptor descriptor, @NotNull JsExpression alias) {
077            return new AliasingContext(this, Collections.singletonMap(descriptor, alias), null);
078        }
079    
080        @NotNull
081        public AliasingContext notShareableThisAliased(@NotNull final DeclarationDescriptor thisDescriptor, @NotNull final JsExpression alias) {
082            return new AliasingContext(this) {
083                @Nullable
084                @Override
085                protected JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor, boolean fromChild) {
086                    if (!fromChild && thisDescriptor == descriptor) {
087                        return alias;
088                    }
089                    return super.getAliasForDescriptor(descriptor, fromChild);
090                }
091            };
092        }
093    
094        @NotNull
095        public AliasingContext withExpressionsAliased(@NotNull Map<JetExpression, JsName> aliasesForExpressions) {
096            return new AliasingContext(this, null, aliasesForExpressions);
097        }
098    
099        @NotNull
100        public AliasingContext withDescriptorsAliased(@NotNull Map<DeclarationDescriptor, JsExpression> aliases) {
101            return new AliasingContext(this, aliases, null);
102        }
103    
104    
105        @Nullable
106        public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
107            // these aliases cannot be shared and applicable only in current context
108            return getAliasForDescriptor(descriptor, false);
109        }
110    
111        @Nullable
112        protected JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor, boolean fromChild) {
113            JsExpression alias = aliasesForDescriptors == null ? null : aliasesForDescriptors.get(descriptor.getOriginal());
114            return alias != null || parent == null ? alias : parent.getAliasForDescriptor(descriptor, true);
115        }
116    
117        @Nullable
118        public JsName getAliasForExpression(@NotNull JetExpression element) {
119            JsName alias = aliasesForExpressions == null ? null : aliasesForExpressions.get(element);
120            return alias != null || parent == null ? alias : parent.getAliasForExpression(element);
121        }
122    
123        /**
124         * Usages:
125         * 1) Local variable captured in closure. If captured in closure, any modification in closure should affect captured variable.
126         * So, "var count = n" wrapped as "var count = {v: n}". descriptor wil be property descriptor, alias will be JsObjectLiteral
127         *
128         * 2) Local named function.
129         */
130        public void registerAlias(@NotNull DeclarationDescriptor descriptor, @NotNull JsExpression alias) {
131            if (aliasesForDescriptors == null) {
132                aliasesForDescriptors = Collections.singletonMap(descriptor, alias);
133            }
134            else {
135                if (aliasesForDescriptors.size() == 1) {
136                    Map<DeclarationDescriptor, JsExpression> singletonMap = aliasesForDescriptors;
137                    aliasesForDescriptors = new THashMap<DeclarationDescriptor, JsExpression>();
138                    aliasesForDescriptors.put(singletonMap.keySet().iterator().next(), singletonMap.values().iterator().next());
139                }
140                JsExpression prev = aliasesForDescriptors.put(descriptor, alias);
141                assert prev == null;
142            }
143        }
144    }