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.JsName;
021import com.google.dart.compiler.backend.js.ast.JsScope;
022import org.jetbrains.annotations.NotNull;
023import org.jetbrains.annotations.Nullable;
024import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
026import org.jetbrains.jet.lang.resolve.name.Name;
027
028import java.util.Map;
029
030import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFQName;
031
032/**
033 * Provides a mechanism to bind some of the kotlin/java declations with library implementations.
034 * Makes sense only for those declaration that cannot be annotated. (Use library annotation in this case)
035 */
036public final class StandardClasses {
037
038    private final class Builder {
039
040        @Nullable
041        private /*var*/ FqNameUnsafe currentFQName = null;
042        @Nullable
043        private /*var*/ String currentObjectName = null;
044
045        @NotNull
046        public Builder forFQ(@NotNull String classFQName) {
047            currentFQName = new FqNameUnsafe(classFQName);
048            return this;
049        }
050
051        @NotNull
052        public Builder kotlinClass(@NotNull String kotlinName) {
053            kotlinTopLevelObject(kotlinName);
054            constructor();
055            return this;
056        }
057
058        private void kotlinTopLevelObject(@NotNull String kotlinName) {
059            assert currentFQName != null;
060            currentObjectName = kotlinName;
061            declareKotlinObject(currentFQName, kotlinName);
062        }
063
064        @NotNull
065        public Builder kotlinFunction(@NotNull String kotlinName) {
066            kotlinTopLevelObject(kotlinName);
067            return this;
068        }
069
070        @NotNull
071        private Builder constructor() {
072            assert currentFQName != null;
073            assert currentObjectName != null;
074            declareInner(currentFQName, "<init>", currentObjectName);
075            return this;
076        }
077
078        @NotNull
079        public Builder methods(@NotNull String... methodNames) {
080            assert currentFQName != null;
081            declareMethods(currentFQName, methodNames);
082            return this;
083        }
084
085        @NotNull
086        public Builder properties(@NotNull String... propertyNames) {
087            assert currentFQName != null;
088            declareReadonlyProperties(currentFQName, propertyNames);
089            return this;
090        }
091    }
092
093    @NotNull
094    public static StandardClasses bindImplementations(@NotNull JsScope kotlinObjectScope) {
095        StandardClasses standardClasses = new StandardClasses(kotlinObjectScope);
096        declareJetObjects(standardClasses);
097        return standardClasses;
098    }
099
100    private static void declareJetObjects(@NotNull StandardClasses standardClasses) {
101        standardClasses.declare().forFQ("jet.Iterator").kotlinClass("Iterator").methods("next").properties("hasNext");
102
103        standardClasses.declare().forFQ("jet.IntRange").kotlinClass("NumberRange")
104                .methods("iterator", "contains").properties("start", "end", "increment");
105
106        standardClasses.declare().forFQ("jet.IntProgression").kotlinClass("NumberProgression")
107                .methods("iterator", "contains").properties("start", "end", "increment");
108
109        standardClasses.declare().forFQ("jet.Any.toString").kotlinFunction("toString");
110    }
111
112
113    @NotNull
114    private final JsScope kotlinScope;
115
116
117    @NotNull
118    private final Map<FqNameUnsafe, JsName> standardObjects = Maps.newHashMap();
119
120    @NotNull
121    private final Map<FqNameUnsafe, JsScope> scopeMap = Maps.newHashMap();
122
123    private StandardClasses(@NotNull JsScope kotlinScope) {
124        this.kotlinScope = kotlinScope;
125    }
126
127    private void declareTopLevelObjectInScope(@NotNull JsScope scope, @NotNull Map<FqNameUnsafe, JsName> map,
128                                              @NotNull FqNameUnsafe fullQualifiedName, @NotNull String name) {
129        JsName declaredName = scope.declareName(name);
130        map.put(fullQualifiedName, declaredName);
131        scopeMap.put(fullQualifiedName, new JsScope(scope, "scope for " + name));
132    }
133
134    private void declareKotlinObject(@NotNull FqNameUnsafe fullQualifiedName, @NotNull String kotlinLibName) {
135        declareTopLevelObjectInScope(kotlinScope, standardObjects, fullQualifiedName, kotlinLibName);
136    }
137
138    private void declareInner(@NotNull FqNameUnsafe fullQualifiedClassName,
139                              @NotNull String shortMethodName,
140                              @NotNull String javascriptName) {
141        JsScope classScope = scopeMap.get(fullQualifiedClassName);
142        assert classScope != null;
143        FqNameUnsafe fullQualifiedMethodName = fullQualifiedClassName.child(Name.guess(shortMethodName));
144        standardObjects.put(fullQualifiedMethodName, classScope.declareName(javascriptName));
145    }
146
147    private void declareMethods(@NotNull FqNameUnsafe classFQName,
148                                @NotNull String... methodNames) {
149        for (String methodName : methodNames) {
150            declareInner(classFQName, methodName, methodName);
151        }
152    }
153
154    private void declareReadonlyProperties(@NotNull FqNameUnsafe classFQName,
155                                           @NotNull String... propertyNames) {
156        for (String propertyName : propertyNames) {
157            declareInner(classFQName, propertyName, propertyName);
158        }
159    }
160
161    public boolean isStandardObject(@NotNull DeclarationDescriptor descriptor) {
162        return standardObjects.containsKey(getFQName(descriptor));
163    }
164
165    @NotNull
166    public JsName getStandardObjectName(@NotNull DeclarationDescriptor descriptor) {
167        return standardObjects.get(getFQName(descriptor));
168    }
169
170    @NotNull
171    private Builder declare() {
172        return new Builder();
173    }
174}