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
017package org.jetbrains.k2js.translate.context;
018
019import com.google.common.collect.Maps;
020import com.google.dart.compiler.backend.js.ast.JsExpression;
021import com.google.dart.compiler.backend.js.ast.JsLiteral;
022import com.google.dart.compiler.backend.js.ast.JsName;
023import com.google.dart.compiler.backend.js.ast.JsNameRef;
024import org.jetbrains.annotations.NotNull;
025import org.jetbrains.annotations.Nullable;
026import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
027import org.jetbrains.jet.lang.descriptors.ClassOrNamespaceDescriptor;
028import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
029import org.jetbrains.jet.lang.psi.JetExpression;
030import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
031import org.jetbrains.jet.lang.resolve.scopes.receivers.*;
032import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
033import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver;
034
035import java.util.Map;
036
037import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getDeclarationDescriptorForReceiver;
038import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
039
040public 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}