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.declaration;
018
019 import com.google.dart.compiler.backend.js.ast.*;
020 import com.intellij.util.SmartList;
021 import gnu.trove.THashMap;
022 import gnu.trove.THashSet;
023 import gnu.trove.TObjectObjectProcedure;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
026 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
027 import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
028 import org.jetbrains.jet.lang.descriptors.Modality;
029 import org.jetbrains.jet.lang.psi.JetClass;
030 import org.jetbrains.jet.lang.psi.JetClassOrObject;
031 import org.jetbrains.jet.lang.types.JetType;
032 import org.jetbrains.jet.utils.DFS;
033 import org.jetbrains.k2js.translate.context.Namer;
034 import org.jetbrains.k2js.translate.context.TranslationContext;
035 import org.jetbrains.k2js.translate.general.AbstractTranslator;
036 import org.jetbrains.k2js.translate.initializer.InitializerUtils;
037
038 import java.util.Collection;
039 import java.util.Iterator;
040 import java.util.LinkedList;
041 import java.util.List;
042
043 import static com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
044 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getClassDescriptorForType;
045 import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
046
047 /**
048 * Generates a big block where are all the classes(objects representing them) are created.
049 */
050 public final class ClassDeclarationTranslator extends AbstractTranslator {
051 private final THashSet<String> nameClashGuard = new THashSet<String>();
052
053 @NotNull
054 private final THashMap<ClassDescriptor, OpenClassInfo> openClassDescriptorToItem = new THashMap<ClassDescriptor, OpenClassInfo>();
055
056 private final LinkedList<OpenClassInfo> openList = new LinkedList<OpenClassInfo>();
057 @NotNull
058 private final ClassDescriptorToLabel classDescriptorToLabel = new ClassDescriptorToLabel();
059
060 private final THashMap<ClassDescriptor, JsNameRef> openClassDescriptorToQualifiedLabel = new THashMap<ClassDescriptor, JsNameRef>();
061
062 private final ClassAliasingMap classDescriptorToQualifiedLabel = new ClassAliasingMap() {
063 @NotNull
064 @Override
065 public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
066 JsNameRef ref = openClassDescriptorToQualifiedLabel.get(descriptor);
067 if (ref != null) {
068 return ref;
069 }
070
071 // will be resolved later
072 ref = new JsNameRef("<unresolved class>");
073 openClassDescriptorToQualifiedLabel.put(descriptor, ref);
074 return ref;
075 }
076 };
077
078 @NotNull
079 private final JsFunction dummyFunction;
080
081 private final JsNameRef declarationsObjectRef;
082 private final JsVar classesVar;
083
084 public ClassDeclarationTranslator(@NotNull TranslationContext context) {
085 super(context);
086
087 dummyFunction = new JsFunction(context.scope());
088 JsName declarationsObject = context().scope().declareName(Namer.nameForClassesVariable());
089 classesVar = new JsVars.JsVar(declarationsObject);
090 declarationsObjectRef = declarationsObject.makeRef();
091 }
092
093 private final class ClassDescriptorToLabel implements ClassAliasingMap {
094 @Nullable
095 @Override
096 public JsNameRef get(ClassDescriptor descriptor, ClassDescriptor referencedDescriptor) {
097 OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
098 // class declared in library
099 if (item == null) {
100 return null;
101 }
102
103 return item.label;
104 }
105 }
106
107 private static class OpenClassInfo {
108 private final ClassDescriptor descriptor;
109 private final JetClass declaration;
110 private final JsNameRef label;
111 private boolean referencedFromOpenClass = false;
112
113 private OpenClassInfo(JetClass declaration, ClassDescriptor descriptor, JsNameRef label) {
114 this.descriptor = descriptor;
115 this.declaration = declaration;
116 this.label = label;
117 }
118 }
119
120 @NotNull
121 public JsVars.JsVar getDeclaration() {
122 return classesVar;
123 }
124
125 public void generateDeclarations() {
126 List<JsVar> vars = new SmartList<JsVar>();
127 List<JsPropertyInitializer> propertyInitializers = new SmartList<JsPropertyInitializer>();
128
129 generateOpenClassDeclarations(vars, propertyInitializers);
130 fixUnresolvedClassReferences();
131
132 if (vars.isEmpty()) {
133 if (!propertyInitializers.isEmpty()) {
134 classesVar.setInitExpression(new JsObjectLiteral(propertyInitializers, true));
135 }
136 return;
137 }
138
139 dummyFunction.setBody(new JsBlock(new JsVars(vars, true), new JsReturn(new JsObjectLiteral(propertyInitializers))));
140 classesVar.setInitExpression(new JsInvocation(dummyFunction));
141 }
142
143 private void generateOpenClassDeclarations(@NotNull List<JsVar> vars, @NotNull List<JsPropertyInitializer> propertyInitializers) {
144 // first pass: set up list order
145 LinkedList<OpenClassInfo> sortedOpenClasses =
146 (LinkedList<OpenClassInfo>) DFS.topologicalOrder(openList, new DFS.Neighbors<OpenClassInfo>() {
147 @NotNull
148 @Override
149 public Iterable<OpenClassInfo> getNeighbors(OpenClassInfo current) {
150 LinkedList<OpenClassInfo> parents = new LinkedList<OpenClassInfo>();
151 ClassDescriptor classDescriptor = getClassDescriptor(context().bindingContext(), current.declaration);
152 Collection<JetType> superTypes = classDescriptor.getTypeConstructor().getSupertypes();
153
154 for (JetType type : superTypes) {
155 ClassDescriptor descriptor = getClassDescriptorForType(type);
156 OpenClassInfo item = openClassDescriptorToItem.get(descriptor);
157 if (item == null) {
158 continue;
159 }
160
161 item.referencedFromOpenClass = true;
162 parents.add(item);
163 }
164
165 return parents;
166 }
167 });
168
169 assert sortedOpenClasses.size() == openList.size();
170
171 // second pass: generate
172 Iterator<OpenClassInfo> it = sortedOpenClasses.descendingIterator();
173 while (it.hasNext()) {
174 OpenClassInfo item = it.next();
175 JsExpression translatedDeclaration =
176 new ClassTranslator(item.declaration, item.descriptor, classDescriptorToLabel, context()).translate();
177
178 JsExpression value;
179 if (item.referencedFromOpenClass) {
180 vars.add(new JsVar(item.label.getName(), translatedDeclaration));
181 value = item.label;
182 }
183 else {
184 value = translatedDeclaration;
185 }
186
187 propertyInitializers.add(new JsPropertyInitializer(item.label, value));
188 }
189 }
190
191 private void fixUnresolvedClassReferences() {
192 openClassDescriptorToQualifiedLabel.forEachEntry(new TObjectObjectProcedure<ClassDescriptor, JsNameRef>() {
193 @Override
194 public boolean execute(ClassDescriptor descriptor, JsNameRef ref) {
195 if (ref.getName() == null) {
196 // from library
197 ref.resolve(context().getNameForDescriptor(descriptor));
198 ref.setQualifier(context().getQualifierForDescriptor(descriptor));
199 }
200 return true;
201 }
202 });
203 }
204
205 private String createNameForClass(ClassDescriptor descriptor) {
206 String suggestedName = descriptor.getName().asString();
207 String name = suggestedName;
208 DeclarationDescriptor containing = descriptor;
209 while (!nameClashGuard.add(name)) {
210 containing = containing.getContainingDeclaration();
211 assert containing != null;
212 name = suggestedName + '_' + containing.getName().asString();
213 }
214 return name;
215 }
216
217 @Nullable
218 public JsNameRef getQualifiedReference(ClassDescriptor descriptor) {
219 if (descriptor.getModality() != Modality.FINAL) {
220 //noinspection ConstantConditions
221 return classDescriptorToQualifiedLabel.get(descriptor, null);
222 }
223 return null;
224 }
225
226 @Nullable
227 public JsPropertyInitializer translate(@NotNull JetClassOrObject declaration, TranslationContext context) {
228 ClassDescriptor descriptor = getClassDescriptor(context().bindingContext(), declaration);
229 JsExpression value;
230 if (descriptor.getModality() == Modality.FINAL) {
231 value = new ClassTranslator(declaration, classDescriptorToQualifiedLabel, context).translate();
232 }
233 else {
234 String label = createNameForClass(descriptor);
235 JsName name = dummyFunction.getScope().declareName(label);
236 JsNameRef qualifiedLabel = openClassDescriptorToQualifiedLabel.get(descriptor);
237 if (qualifiedLabel == null) {
238 qualifiedLabel = new JsNameRef(name);
239 openClassDescriptorToQualifiedLabel.put(descriptor, qualifiedLabel);
240 }
241 else {
242 qualifiedLabel.resolve(name);
243 }
244 qualifiedLabel.setQualifier(declarationsObjectRef);
245
246 OpenClassInfo item = new OpenClassInfo((JetClass) declaration, descriptor, name.makeRef());
247
248 openList.add(item);
249 openClassDescriptorToItem.put(descriptor, item);
250
251 value = qualifiedLabel;
252
253 // not public api classes referenced to internal var _c
254 if (!descriptor.getVisibility().isPublicAPI()) {
255 return null;
256 }
257 }
258
259 return InitializerUtils.createPropertyInitializer(descriptor, value, context());
260 }
261 }