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.jet.asJava;
018
019 import com.google.common.collect.Lists;
020 import com.google.common.collect.Sets;
021 import com.intellij.navigation.ItemPresentation;
022 import com.intellij.navigation.ItemPresentationProviders;
023 import com.intellij.openapi.util.Comparing;
024 import com.intellij.openapi.util.Key;
025 import com.intellij.openapi.util.NullableLazyValue;
026 import com.intellij.openapi.util.Pair;
027 import com.intellij.openapi.vfs.VirtualFile;
028 import com.intellij.psi.*;
029 import com.intellij.psi.impl.PsiManagerImpl;
030 import com.intellij.psi.impl.compiled.ClsFileImpl;
031 import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
032 import com.intellij.psi.impl.light.AbstractLightClass;
033 import com.intellij.psi.impl.light.LightModifierList;
034 import com.intellij.psi.impl.light.LightTypeParameterListBuilder;
035 import com.intellij.psi.stubs.PsiClassHolderFileStub;
036 import com.intellij.psi.util.CachedValue;
037 import com.intellij.psi.util.CachedValuesManager;
038 import com.intellij.util.IncorrectOperationException;
039 import org.jetbrains.annotations.NonNls;
040 import org.jetbrains.annotations.NotNull;
041 import org.jetbrains.annotations.Nullable;
042 import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
043 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
044 import org.jetbrains.jet.lang.psi.*;
045 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
046 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
047 import org.jetbrains.jet.lang.resolve.java.jetAsJava.JetJavaMirrorMarker;
048 import org.jetbrains.jet.lang.resolve.name.FqName;
049 import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
050 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
051 import org.jetbrains.jet.lexer.JetKeywordToken;
052 import org.jetbrains.jet.plugin.JetLanguage;
053
054 import javax.swing.*;
055 import java.util.Collection;
056 import java.util.List;
057
058 import static org.jetbrains.jet.lexer.JetTokens.*;
059
060 public class KotlinLightClassForExplicitDeclaration extends AbstractLightClass implements KotlinLightClass, JetJavaMirrorMarker {
061 private final static Key<CachedValue<PsiJavaFileStub>> JAVA_API_STUB = Key.create("JAVA_API_STUB");
062
063 @Nullable
064 public static KotlinLightClassForExplicitDeclaration create(@NotNull PsiManager manager, @NotNull JetClassOrObject classOrObject) {
065 if (LightClassUtil.belongsToKotlinBuiltIns((JetFile) classOrObject.getContainingFile())) {
066 return null;
067 }
068
069 // TODO temporary not building light classes for local classes: e.g., they won't be visible in hierarchy
070 if (JetPsiUtil.getOutermostClassOrObject(classOrObject) == null) {
071 return null;
072 }
073
074 String jvmInternalName = PsiCodegenPredictor.getPredefinedJvmInternalName(classOrObject);
075 if (jvmInternalName == null) return null;
076
077 FqName fqName = JvmClassName.byInternalName(jvmInternalName).getFqNameForClassNameWithoutDollars();
078 return new KotlinLightClassForExplicitDeclaration(manager, fqName, classOrObject);
079 }
080
081 private final FqName classFqName; // FqName of (possibly inner) class
082 private final JetClassOrObject classOrObject;
083 private PsiClass delegate;
084
085 @Nullable
086 private PsiModifierList modifierList;
087
088 private final NullableLazyValue<PsiTypeParameterList> typeParameterList = new NullableLazyValue<PsiTypeParameterList>() {
089 @Nullable
090 @Override
091 protected PsiTypeParameterList compute() {
092 LightTypeParameterListBuilder builder = new LightTypeParameterListBuilder(getManager(), getLanguage());
093 if (classOrObject instanceof JetTypeParameterListOwner) {
094 JetTypeParameterListOwner typeParameterListOwner = (JetTypeParameterListOwner) classOrObject;
095 List<JetTypeParameter> parameters = typeParameterListOwner.getTypeParameters();
096 for (int i = 0; i < parameters.size(); i++) {
097 JetTypeParameter jetTypeParameter = parameters.get(i);
098 String name = jetTypeParameter.getName();
099 String safeName = name == null ? "__no_name__" : name;
100 builder.addParameter(new KotlinLightTypeParameter(KotlinLightClassForExplicitDeclaration.this, i, safeName));
101 }
102 }
103 return builder;
104 }
105 };
106
107 private KotlinLightClassForExplicitDeclaration(
108 @NotNull PsiManager manager,
109 @NotNull FqName name,
110 @NotNull JetClassOrObject classOrObject
111 ) {
112 super(manager, JetLanguage.INSTANCE);
113 this.classFqName = name;
114 this.classOrObject = classOrObject;
115 }
116
117 @NotNull
118 public JetClassOrObject getJetClassOrObject() {
119 return classOrObject;
120 }
121
122 @NotNull
123 @Override
124 public FqName getFqName() {
125 return classFqName;
126 }
127
128 @NotNull
129 @Override
130 public PsiElement copy() {
131 return new KotlinLightClassForExplicitDeclaration(getManager(), classFqName, classOrObject);
132 }
133
134 @NotNull
135 @Override
136 public PsiClass getDelegate() {
137 if (delegate == null) {
138 PsiJavaFileStub javaFileStub = getJavaFileStub();
139
140 PsiClass psiClass = LightClassUtil.findClass(classFqName, javaFileStub);
141 if (psiClass == null) {
142 JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
143 throw new IllegalStateException("Class was not found " + classFqName + "\n" +
144 "in " + outermostClassOrObject.getContainingFile().getText() + "\n" +
145 "stub: \n" + javaFileStub.getPsi().getText());
146 }
147 delegate = psiClass;
148 }
149
150 return delegate;
151 }
152
153 @NotNull
154 private PsiJavaFileStub getJavaFileStub() {
155 JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
156 return CachedValuesManager.getManager(getProject()).getCachedValue(
157 outermostClassOrObject,
158 JAVA_API_STUB,
159 KotlinJavaFileStubProvider.createForDeclaredTopLevelClass(outermostClassOrObject),
160 /*trackValue = */false);
161 }
162
163 @NotNull
164 private static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
165 JetClassOrObject outermostClass = JetPsiUtil.getOutermostClassOrObject(classOrObject);
166 if (outermostClass == null) {
167 throw new IllegalStateException("Attempt to build a light class for a local class: " + classOrObject.getText());
168 }
169 else {
170 return outermostClass;
171 }
172 }
173
174 private final NullableLazyValue<PsiFile> _containingFile = new NullableLazyValue<PsiFile>() {
175 @Nullable
176 @Override
177 protected PsiFile compute() {
178 VirtualFile virtualFile = classOrObject.getContainingFile().getVirtualFile();
179 assert virtualFile != null : "No virtual file for " + classOrObject.getText();
180 return new ClsFileImpl((PsiManagerImpl) getManager(), new ClassFileViewProvider(getManager(), virtualFile)) {
181 @NotNull
182 @Override
183 public String getPackageName() {
184 return JetPsiUtil.getFQName((JetFile) classOrObject.getContainingFile()).asString();
185 }
186
187 @NotNull
188 @Override
189 public PsiClassHolderFileStub getStub() {
190 return getJavaFileStub();
191 }
192 };
193 }
194 };
195
196 @Override
197 public PsiFile getContainingFile() {
198 return _containingFile.getValue();
199 }
200
201 @NotNull
202 @Override
203 public PsiElement getNavigationElement() {
204 return classOrObject;
205 }
206
207 @Override
208 public boolean isEquivalentTo(PsiElement another) {
209 return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
210 }
211
212 @Override
213 public ItemPresentation getPresentation() {
214 return ItemPresentationProviders.getItemPresentation(this);
215 }
216
217 @Override
218 public Icon getElementIcon(int flags) {
219 throw new UnsupportedOperationException("This should be done byt JetIconProvider");
220 }
221
222 @Override
223 public boolean equals(Object o) {
224 if (this == o) return true;
225 if (o == null || getClass() != o.getClass()) return false;
226
227 KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
228
229 if (!classFqName.equals(aClass.classFqName)) return false;
230
231 return true;
232 }
233
234 @Override
235 public int hashCode() {
236 return classFqName.hashCode();
237 }
238
239 @Nullable
240 @Override
241 public PsiClass getContainingClass() {
242 if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
243 return super.getContainingClass();
244 }
245
246 @Nullable
247 @Override
248 public PsiElement getParent() {
249 if (classOrObject.getParent() == classOrObject.getContainingFile()) return getContainingFile();
250 return getContainingClass();
251 }
252
253 @Nullable
254 @Override
255 public PsiTypeParameterList getTypeParameterList() {
256 return typeParameterList.getValue();
257 }
258
259 @NotNull
260 @Override
261 public PsiTypeParameter[] getTypeParameters() {
262 PsiTypeParameterList typeParameterList = getTypeParameterList();
263 return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
264 }
265
266 @Nullable
267 @Override
268 public String getName() {
269 return classFqName.shortName().asString();
270 }
271
272 @Nullable
273 @Override
274 public String getQualifiedName() {
275 return classFqName.asString();
276 }
277
278 @NotNull
279 @Override
280 public PsiModifierList getModifierList() {
281 if (modifierList == null) {
282 modifierList = new LightModifierList(getManager(), JetLanguage.INSTANCE, computeModifiers());
283 }
284 return modifierList;
285 }
286
287 @NotNull
288 private String[] computeModifiers() {
289 boolean nestedClass = classOrObject.getParent() != classOrObject.getContainingFile();
290 Collection<String> psiModifiers = Sets.newHashSet();
291
292 // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
293 List<Pair<JetKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
294 Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
295 Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
296 Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
297 Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
298
299 for (Pair<JetKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
300 if (classOrObject.hasModifier(tokenAndModifier.first)) {
301 psiModifiers.add(tokenAndModifier.second);
302 }
303 }
304
305 if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
306 // Top-level private class has PUBLIC visibility in Java
307 // Nested private class has PRIVATE visibility
308 psiModifiers.add(nestedClass ? PsiModifier.PRIVATE : PsiModifier.PUBLIC);
309 }
310
311 if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
312 psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
313 }
314
315
316 // FINAL
317 if (isAbstract(classOrObject)) {
318 psiModifiers.add(PsiModifier.ABSTRACT);
319 }
320 else if (!classOrObject.hasModifier(OPEN_KEYWORD)) {
321 psiModifiers.add(PsiModifier.FINAL);
322 }
323
324 if (nestedClass && !classOrObject.hasModifier(INNER_KEYWORD)) {
325 psiModifiers.add(PsiModifier.STATIC);
326 }
327
328 return psiModifiers.toArray(new String[psiModifiers.size()]);
329 }
330
331 private boolean isAbstract(@NotNull JetClassOrObject object) {
332 return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
333 }
334
335 @Override
336 public boolean hasModifierProperty(@NonNls @NotNull String name) {
337 return getModifierList().hasModifierProperty(name);
338 }
339
340 @Override
341 public boolean isDeprecated() {
342 JetModifierList jetModifierList = classOrObject.getModifierList();
343 if (jetModifierList == null) {
344 return false;
345 }
346
347 ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
348 String deprecatedName = deprecatedAnnotation.getName().asString();
349 FqNameUnsafe deprecatedFqName = DescriptorUtils.getFQName(deprecatedAnnotation);
350
351 for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
352 JetTypeReference typeReference = annotationEntry.getTypeReference();
353 if (typeReference == null) continue;
354
355 JetTypeElement typeElement = typeReference.getTypeElement();
356 if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
357
358 FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
359 if (fqName == null) continue;
360
361 if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
362 if (deprecatedName.equals(fqName.asString())) return true;
363 }
364 return false;
365 }
366
367 @Override
368 public boolean isInterface() {
369 if (!(classOrObject instanceof JetClass)) return false;
370 JetClass jetClass = (JetClass) classOrObject;
371 return jetClass.isTrait() || jetClass.isAnnotation();
372 }
373
374 @Override
375 public boolean isAnnotationType() {
376 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
377 }
378
379 @Override
380 public boolean isEnum() {
381 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
382 }
383
384 @Override
385 public boolean hasTypeParameters() {
386 return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
387 }
388
389 @Override
390 public boolean isValid() {
391 return classOrObject.isValid();
392 }
393
394 @Override
395 public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
396 return super.setName(name); // TODO
397 }
398
399 @Override
400 public String toString() {
401 try {
402 return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
403 }
404 catch (Throwable e) {
405 return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
406 }
407 }
408 }