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.common.collect.Maps;
020    import com.google.dart.compiler.backend.js.ast.JsExpression;
021    import com.google.dart.compiler.backend.js.ast.JsLiteral;
022    import com.google.dart.compiler.backend.js.ast.JsName;
023    import com.google.dart.compiler.backend.js.ast.JsNameRef;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
027    import org.jetbrains.jet.lang.descriptors.ClassOrNamespaceDescriptor;
028    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
029    import org.jetbrains.jet.lang.psi.JetExpression;
030    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
031    import org.jetbrains.jet.lang.resolve.scopes.receivers.*;
032    import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
033    import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver;
034    
035    import java.util.Map;
036    
037    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getDeclarationDescriptorForReceiver;
038    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
039    
040    public class AliasingContext {
041        private static final ThisAliasProvider EMPTY_THIS_ALIAS_PROVIDER = new ThisAliasProvider() {
042            @Nullable
043            @Override
044            public JsNameRef get(@NotNull DeclarationDescriptor descriptor) {
045                return null;
046            }
047    
048            @Nullable
049            @Override
050            public JsExpression get(@NotNull ResolvedCall<?> call) {
051                ReceiverValue callThisObject = call.getThisObject();
052                return callThisObject.exists() && (callThisObject instanceof ClassReceiver || callThisObject instanceof ExtensionReceiver)
053                       ? JsLiteral.THIS
054                       : null;
055            }
056        };
057    
058        private static final AliasingContext ROOT = new AliasingContext(null, EMPTY_THIS_ALIAS_PROVIDER) {
059            @Override
060            public JsName getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
061                return null;
062            }
063    
064            @Override
065            public JsName getAliasForExpression(@NotNull JetExpression element) {
066                return null;
067            }
068        };
069    
070        public static AliasingContext getCleanContext() {
071            return new AliasingContext(ROOT, ROOT.thisAliasProvider);
072        }
073    
074        @NotNull
075        private final Map<DeclarationDescriptor, JsName> aliasesForDescriptors = Maps.newHashMap();
076    
077        @NotNull
078        final ThisAliasProvider thisAliasProvider;
079        @NotNull
080        private final Map<JetExpression, JsName> aliasesForExpressions = Maps.newHashMap();
081    
082        @Nullable
083        private final AliasingContext parent;
084    
085        private AliasingContext(@Nullable AliasingContext parent, @NotNull ThisAliasProvider thisAliasProvider) {
086            this.parent = parent;
087            this.thisAliasProvider = thisAliasProvider;
088        }
089    
090        public interface ThisAliasProvider {
091            @Nullable
092            JsNameRef get(@NotNull DeclarationDescriptor descriptor);
093            @Nullable
094            JsExpression get(@NotNull ResolvedCall<?> call);
095        }
096    
097        public abstract static class AbstractThisAliasProvider implements ThisAliasProvider {
098            @NotNull
099            protected static DeclarationDescriptor normalize(@NotNull DeclarationDescriptor descriptor) {
100                if (descriptor instanceof ClassOrNamespaceDescriptor) {
101                    return descriptor;
102                }
103                else if (descriptor instanceof CallableDescriptor) {
104                    DeclarationDescriptor receiverDescriptor = getExpectedReceiverDescriptor((CallableDescriptor) descriptor);
105                    assert receiverDescriptor != null;
106                    return receiverDescriptor;
107                }
108    
109                return descriptor;
110            }
111    
112            @Nullable
113            @Override
114            public JsExpression get(@NotNull ResolvedCall<?> call) {
115                ReceiverValue thisObject = call.getThisObject();
116                if (!thisObject.exists()) {
117                    return null;
118                }
119    
120                if (thisObject instanceof ExtensionReceiver || thisObject instanceof ClassReceiver) {
121                    JsNameRef ref = get(((ThisReceiver) thisObject).getDeclarationDescriptor());
122                    if (ref != null) {
123                        return ref;
124                    }
125                }
126    
127                JsNameRef ref = get(getDeclarationDescriptorForReceiver(thisObject));
128                return ref == null ? JsLiteral.THIS : ref;
129            }
130        }
131    
132        @NotNull
133        public AliasingContext inner(@NotNull ThisAliasProvider thisAliasProvider) {
134            return new AliasingContext(this, thisAliasProvider);
135        }
136    
137        @NotNull
138        public AliasingContext inner(@NotNull final DeclarationDescriptor correspondingDescriptor, @NotNull final JsName alias) {
139            return inner(new AbstractThisAliasProvider() {
140                @Nullable
141                @Override
142                public JsNameRef get(@NotNull DeclarationDescriptor descriptor) {
143                    return correspondingDescriptor == normalize(descriptor) ? alias.makeRef() : null;
144                }
145            });
146        }
147    
148        @NotNull
149        public AliasingContext withAliasesForExpressions(@NotNull Map<JetExpression, JsName> aliasesForExpressions) {
150            AliasingContext newContext = new AliasingContext(this, thisAliasProvider);
151            newContext.aliasesForExpressions.putAll(aliasesForExpressions);
152            return newContext;
153        }
154    
155        @NotNull
156        public AliasingContext withDescriptorsAliased(@NotNull Map<DeclarationDescriptor, JsName> aliases) {
157            AliasingContext newContext = new AliasingContext(this, thisAliasProvider);
158            newContext.aliasesForDescriptors.putAll(aliases);
159            return newContext;
160        }
161    
162        @Nullable
163        public JsName getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
164            JsName alias = aliasesForDescriptors.get(descriptor.getOriginal());
165            if (alias != null) {
166                return alias;
167            }
168            assert parent != null;
169            return parent.getAliasForDescriptor(descriptor);
170        }
171    
172        @Nullable
173        public JsName getAliasForExpression(@NotNull JetExpression element) {
174            JsName alias = aliasesForExpressions.get(element);
175            if (alias != null) {
176                return alias;
177            }
178            assert parent != null;
179            return parent.getAliasForExpression(element);
180        }
181    }