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