001 /*
002 * Copyright 2010-2014 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.LightClass;
033 import com.intellij.psi.impl.light.LightMethod;
034 import com.intellij.psi.stubs.PsiClassHolderFileStub;
035 import com.intellij.psi.util.CachedValue;
036 import com.intellij.psi.util.CachedValuesManager;
037 import com.intellij.psi.util.PsiTreeUtil;
038 import com.intellij.util.ArrayUtil;
039 import com.intellij.util.IncorrectOperationException;
040 import org.jetbrains.annotations.NonNls;
041 import org.jetbrains.annotations.NotNull;
042 import org.jetbrains.annotations.Nullable;
043 import org.jetbrains.jet.codegen.binding.PsiCodegenPredictor;
044 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
045 import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
046 import org.jetbrains.jet.lang.psi.*;
047 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
048 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
049 import org.jetbrains.jet.lang.resolve.java.jetAsJava.JetJavaMirrorMarker;
050 import org.jetbrains.jet.lang.resolve.name.FqName;
051 import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
052 import org.jetbrains.jet.lang.types.JetType;
053 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
054 import org.jetbrains.jet.lexer.JetKeywordToken;
055 import org.jetbrains.jet.plugin.JetLanguage;
056
057 import javax.swing.*;
058 import java.util.Arrays;
059 import java.util.Collection;
060 import java.util.List;
061
062 import static org.jetbrains.jet.lexer.JetTokens.*;
063
064 public class KotlinLightClassForExplicitDeclaration extends KotlinWrappingLightClass implements JetJavaMirrorMarker {
065 private final static Key<CachedValue<OutermostKotlinClassLightClassData>> JAVA_API_STUB = Key.create("JAVA_API_STUB");
066
067 @Nullable
068 public static KotlinLightClassForExplicitDeclaration create(@NotNull PsiManager manager, @NotNull JetClassOrObject classOrObject) {
069 if (LightClassUtil.belongsToKotlinBuiltIns((JetFile) classOrObject.getContainingFile())) {
070 return null;
071 }
072
073 String jvmInternalName = getJvmInternalName(classOrObject);
074 if (jvmInternalName == null) return null;
075
076 FqName fqName = JvmClassName.byInternalName(jvmInternalName).getFqNameForClassNameWithoutDollars();
077
078 if (classOrObject instanceof JetObjectDeclaration && ((JetObjectDeclaration) classOrObject).isObjectLiteral()) {
079 return new KotlinLightClassForAnonymousDeclaration(manager, fqName, classOrObject);
080 }
081 return new KotlinLightClassForExplicitDeclaration(manager, fqName, classOrObject);
082 }
083
084 private static String getJvmInternalName(JetClassOrObject classOrObject) {
085 if (JetPsiUtil.isLocal(classOrObject)) {
086 LightClassDataForKotlinClass data = getLightClassDataExactly(classOrObject);
087 return data != null ? data.getJvmInternalName() : "";
088 }
089 return PsiCodegenPredictor.getPredefinedJvmInternalName(classOrObject);
090 }
091
092 private final FqName classFqName; // FqName of (possibly inner) class
093 protected final JetClassOrObject classOrObject;
094 private PsiClass delegate;
095
096 private final NullableLazyValue<PsiElement> parent = new NullableLazyValue<PsiElement>() {
097 @Nullable
098 @Override
099 protected PsiElement compute() {
100 if (JetPsiUtil.isLocal(classOrObject)) {
101 //noinspection unchecked
102 PsiElement declaration = JetPsiUtil.getTopmostParentOfTypes(
103 classOrObject,
104 JetNamedFunction.class, JetProperty.class, JetClassInitializer.class, JetParameter.class
105 );
106
107 if (declaration instanceof JetParameter) {
108 declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
109 }
110
111 if (declaration instanceof JetNamedFunction) {
112 JetNamedFunction function = (JetNamedFunction) declaration;
113 return getParentByPsiMethod(LightClassUtil.getLightClassMethod(function), function.getName(), false);
114 }
115
116 // Represent the property as a fake method with the same name
117 if (declaration instanceof JetProperty) {
118 JetProperty property = (JetProperty) declaration;
119 return getParentByPsiMethod(LightClassUtil.getLightClassPropertyMethods(property).getGetter(), property.getName(), true);
120 }
121
122 if (declaration instanceof JetClassInitializer) {
123 PsiElement parent = declaration.getParent();
124 PsiElement grandparent = parent.getParent();
125
126 if (parent instanceof JetClassBody && grandparent instanceof JetClassOrObject) {
127 return LightClassUtil.getPsiClass((JetClassOrObject) grandparent);
128 }
129 }
130
131 if (declaration instanceof JetClass) {
132 return LightClassUtil.getPsiClass((JetClass) declaration);
133 }
134 }
135
136 return classOrObject.getParent() == classOrObject.getContainingFile() ? getContainingFile() : getContainingClass();
137 }
138
139 private PsiElement getParentByPsiMethod(PsiMethod method, final String name, boolean forceMethodWrapping) {
140 if (method == null || name == null) return null;
141
142 PsiClass containingClass = method.getContainingClass();
143 if (containingClass == null) return null;
144
145 final String currentFileName = classOrObject.getContainingFile().getName();
146
147 boolean createWrapper = forceMethodWrapping;
148 // Use PsiClass wrapper instead of package light class to avoid names like "FooPackage" in Type Hierarchy and related views
149 if (containingClass instanceof KotlinLightClassForPackage) {
150 containingClass = new LightClass(containingClass, JetLanguage.INSTANCE) {
151 @Nullable
152 @Override
153 public String getName() {
154 return currentFileName;
155 }
156 };
157 createWrapper = true;
158 }
159
160 if (createWrapper) {
161 return new LightMethod(myManager, method, containingClass, JetLanguage.INSTANCE) {
162 @Override
163 public PsiElement getParent() {
164 return getContainingClass();
165 }
166
167 @NotNull
168 @Override
169 public String getName() {
170 return name;
171 }
172 };
173 }
174
175 return method;
176 }
177 };
178
179 @Nullable
180 private PsiModifierList modifierList;
181
182 private final NullableLazyValue<PsiTypeParameterList> typeParameterList = new NullableLazyValue<PsiTypeParameterList>() {
183 @Nullable
184 @Override
185 protected PsiTypeParameterList compute() {
186 return LightClassUtil.buildLightTypeParameterList(KotlinLightClassForExplicitDeclaration.this, classOrObject);
187 }
188 };
189
190 KotlinLightClassForExplicitDeclaration(
191 @NotNull PsiManager manager,
192 @NotNull FqName name,
193 @NotNull JetClassOrObject classOrObject
194 ) {
195 super(manager, JetLanguage.INSTANCE);
196 this.classFqName = name;
197 this.classOrObject = classOrObject;
198 }
199
200 @Override
201 @NotNull
202 public JetClassOrObject getOrigin() {
203 return classOrObject;
204 }
205
206 @NotNull
207 @Override
208 public FqName getFqName() {
209 return classFqName;
210 }
211
212 @NotNull
213 @Override
214 public PsiElement copy() {
215 return new KotlinLightClassForExplicitDeclaration(getManager(), classFqName, (JetClassOrObject) classOrObject.copy());
216 }
217
218 @NotNull
219 @Override
220 public PsiClass getDelegate() {
221 if (delegate == null) {
222 PsiJavaFileStub javaFileStub = getJavaFileStub();
223
224 PsiClass psiClass = LightClassUtil.findClass(classFqName, javaFileStub);
225 if (psiClass == null) {
226 JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
227 throw new IllegalStateException("Class was not found " + classFqName + "\n" +
228 "in " + outermostClassOrObject.getContainingFile().getText() + "\n" +
229 "stub: \n" + javaFileStub.getPsi().getText());
230 }
231 delegate = psiClass;
232 }
233
234 return delegate;
235 }
236
237 @NotNull
238 private PsiJavaFileStub getJavaFileStub() {
239 return getLightClassData().getJavaFileStub();
240 }
241
242 @Nullable
243 protected final ClassDescriptor getDescriptor() {
244 LightClassDataForKotlinClass data = getLightClassDataExactly(classOrObject);
245 return data != null ? data.getDescriptor() : null;
246 }
247
248 @NotNull
249 private OutermostKotlinClassLightClassData getLightClassData() {
250 return getLightClassData(classOrObject);
251 }
252
253 @NotNull
254 private static OutermostKotlinClassLightClassData getLightClassData(JetClassOrObject classOrObject) {
255 JetClassOrObject outermostClassOrObject = getOutermostClassOrObject(classOrObject);
256 return CachedValuesManager.getManager(classOrObject.getProject()).getCachedValue(
257 outermostClassOrObject,
258 JAVA_API_STUB,
259 KotlinJavaFileStubProvider.createForDeclaredClass(outermostClassOrObject),
260 /*trackValue = */false
261 );
262 }
263
264 @Nullable
265 private static LightClassDataForKotlinClass getLightClassDataExactly(JetClassOrObject classOrObject) {
266 OutermostKotlinClassLightClassData data = getLightClassData(classOrObject);
267 return data.getClassOrObject().equals(classOrObject) ? data : data.getAllInnerClasses().get(classOrObject);
268 }
269
270 @NotNull
271 private static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
272 JetClassOrObject outermostClass = JetPsiUtil.getOutermostClassOrObject(classOrObject);
273 if (outermostClass == null) {
274 throw new IllegalStateException("Attempt to build a light class for a local class: " + classOrObject.getText());
275 }
276 else {
277 return outermostClass;
278 }
279 }
280
281 private final NullableLazyValue<PsiFile> _containingFile = new NullableLazyValue<PsiFile>() {
282 @Nullable
283 @Override
284 protected PsiFile compute() {
285 VirtualFile virtualFile = classOrObject.getContainingFile().getVirtualFile();
286 assert virtualFile != null : "No virtual file for " + classOrObject.getText();
287 return new ClsFileImpl((PsiManagerImpl) getManager(), new ClassFileViewProvider(getManager(), virtualFile)) {
288 @NotNull
289 @Override
290 public String getPackageName() {
291 return JetPsiUtil.getFQName((JetFile) classOrObject.getContainingFile()).asString();
292 }
293
294 @NotNull
295 @Override
296 public PsiClassHolderFileStub getStub() {
297 return getJavaFileStub();
298 }
299 };
300 }
301 };
302
303 @Override
304 public PsiFile getContainingFile() {
305 return _containingFile.getValue();
306 }
307
308 @NotNull
309 @Override
310 public PsiElement getNavigationElement() {
311 return classOrObject;
312 }
313
314 @Override
315 public boolean isEquivalentTo(PsiElement another) {
316 return another instanceof PsiClass && Comparing.equal(((PsiClass) another).getQualifiedName(), getQualifiedName());
317 }
318
319 @Override
320 public ItemPresentation getPresentation() {
321 return ItemPresentationProviders.getItemPresentation(this);
322 }
323
324 @Override
325 public Icon getElementIcon(int flags) {
326 throw new UnsupportedOperationException("This should be done byt JetIconProvider");
327 }
328
329 @Override
330 public boolean equals(Object o) {
331 if (this == o) return true;
332 if (o == null || getClass() != o.getClass()) return false;
333
334 KotlinLightClassForExplicitDeclaration aClass = (KotlinLightClassForExplicitDeclaration) o;
335
336 if (!classFqName.equals(aClass.classFqName)) return false;
337
338 return true;
339 }
340
341 @Override
342 public int hashCode() {
343 return classFqName.hashCode();
344 }
345
346 @Nullable
347 @Override
348 public PsiClass getContainingClass() {
349 if (classOrObject.getParent() == classOrObject.getContainingFile()) return null;
350 return super.getContainingClass();
351 }
352
353 @Nullable
354 @Override
355 public PsiElement getParent() {
356 return parent.getValue();
357 }
358
359 @Nullable
360 @Override
361 public PsiTypeParameterList getTypeParameterList() {
362 return typeParameterList.getValue();
363 }
364
365 @NotNull
366 @Override
367 public PsiTypeParameter[] getTypeParameters() {
368 PsiTypeParameterList typeParameterList = getTypeParameterList();
369 return typeParameterList == null ? PsiTypeParameter.EMPTY_ARRAY : typeParameterList.getTypeParameters();
370 }
371
372 @Nullable
373 @Override
374 public String getName() {
375 return classFqName.shortName().asString();
376 }
377
378 @Nullable
379 @Override
380 public String getQualifiedName() {
381 return classFqName.asString();
382 }
383
384 @NotNull
385 @Override
386 public PsiModifierList getModifierList() {
387 if (modifierList == null) {
388 modifierList = new KotlinLightModifierList(this.getManager(), computeModifiers()) {
389 @Override
390 public PsiAnnotationOwner getDelegate() {
391 return KotlinLightClassForExplicitDeclaration.this.getDelegate().getModifierList();
392 }
393 };
394 }
395 return modifierList;
396 }
397
398 @NotNull
399 private String[] computeModifiers() {
400 boolean nestedClass = classOrObject.getParent() != classOrObject.getContainingFile();
401 Collection<String> psiModifiers = Sets.newHashSet();
402
403 // PUBLIC, PROTECTED, PRIVATE, ABSTRACT, FINAL
404 //noinspection unchecked
405 List<Pair<JetKeywordToken, String>> jetTokenToPsiModifier = Lists.newArrayList(
406 Pair.create(PUBLIC_KEYWORD, PsiModifier.PUBLIC),
407 Pair.create(INTERNAL_KEYWORD, PsiModifier.PUBLIC),
408 Pair.create(PROTECTED_KEYWORD, PsiModifier.PROTECTED),
409 Pair.create(FINAL_KEYWORD, PsiModifier.FINAL));
410
411 for (Pair<JetKeywordToken, String> tokenAndModifier : jetTokenToPsiModifier) {
412 if (classOrObject.hasModifier(tokenAndModifier.first)) {
413 psiModifiers.add(tokenAndModifier.second);
414 }
415 }
416
417 if (classOrObject.hasModifier(PRIVATE_KEYWORD)) {
418 // Top-level private class has PUBLIC visibility in Java
419 // Nested private class has PRIVATE visibility
420 psiModifiers.add(nestedClass ? PsiModifier.PRIVATE : PsiModifier.PUBLIC);
421 }
422
423 if (!psiModifiers.contains(PsiModifier.PRIVATE) && !psiModifiers.contains(PsiModifier.PROTECTED)) {
424 psiModifiers.add(PsiModifier.PUBLIC); // For internal (default) visibility
425 }
426
427
428 // FINAL
429 if (isAbstract(classOrObject)) {
430 psiModifiers.add(PsiModifier.ABSTRACT);
431 }
432 else if (!classOrObject.hasModifier(OPEN_KEYWORD)) {
433 psiModifiers.add(PsiModifier.FINAL);
434 }
435
436 if (nestedClass && !classOrObject.hasModifier(INNER_KEYWORD)) {
437 psiModifiers.add(PsiModifier.STATIC);
438 }
439
440 return ArrayUtil.toStringArray(psiModifiers);
441 }
442
443 private boolean isAbstract(@NotNull JetClassOrObject object) {
444 return object.hasModifier(ABSTRACT_KEYWORD) || isInterface();
445 }
446
447 @Override
448 public boolean hasModifierProperty(@NonNls @NotNull String name) {
449 return getModifierList().hasModifierProperty(name);
450 }
451
452 @Override
453 public boolean isDeprecated() {
454 JetModifierList jetModifierList = classOrObject.getModifierList();
455 if (jetModifierList == null) {
456 return false;
457 }
458
459 ClassDescriptor deprecatedAnnotation = KotlinBuiltIns.getInstance().getDeprecatedAnnotation();
460 String deprecatedName = deprecatedAnnotation.getName().asString();
461 FqNameUnsafe deprecatedFqName = DescriptorUtils.getFqName(deprecatedAnnotation);
462
463 for (JetAnnotationEntry annotationEntry : jetModifierList.getAnnotationEntries()) {
464 JetTypeReference typeReference = annotationEntry.getTypeReference();
465 if (typeReference == null) continue;
466
467 JetTypeElement typeElement = typeReference.getTypeElement();
468 if (!(typeElement instanceof JetUserType)) continue; // If it's not a user type, it's definitely not a ref to deprecated
469
470 FqName fqName = JetPsiUtil.toQualifiedName((JetUserType) typeElement);
471 if (fqName == null) continue;
472
473 if (deprecatedFqName.equals(fqName.toUnsafe())) return true;
474 if (deprecatedName.equals(fqName.asString())) return true;
475 }
476 return false;
477 }
478
479 @Override
480 public boolean isInterface() {
481 if (!(classOrObject instanceof JetClass)) return false;
482 JetClass jetClass = (JetClass) classOrObject;
483 return jetClass.isTrait() || jetClass.isAnnotation();
484 }
485
486 @Override
487 public boolean isAnnotationType() {
488 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isAnnotation();
489 }
490
491 @Override
492 public boolean isEnum() {
493 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isEnum();
494 }
495
496 @Override
497 public boolean hasTypeParameters() {
498 return classOrObject instanceof JetClass && !((JetClass) classOrObject).getTypeParameters().isEmpty();
499 }
500
501 @Override
502 public boolean isValid() {
503 return classOrObject.isValid();
504 }
505
506 @Override
507 public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
508 // Java inheritor check doesn't work when trait (interface in Java) subclasses Java class and for Kotlin local classes
509 if (baseClass instanceof KotlinLightClassForExplicitDeclaration || (isInterface() && !baseClass.isInterface())) {
510 String qualifiedName;
511 if (baseClass instanceof KotlinLightClassForExplicitDeclaration) {
512 ClassDescriptor baseDescriptor = ((KotlinLightClassForExplicitDeclaration) baseClass).getDescriptor();
513 qualifiedName = baseDescriptor != null ? DescriptorUtils.getFqName(baseDescriptor).asString() : null;
514 }
515 else {
516 qualifiedName = baseClass.getQualifiedName();
517 }
518
519 ClassDescriptor thisDescriptor = getDescriptor();
520 return qualifiedName != null
521 && thisDescriptor != null
522 && checkSuperTypeByFQName(thisDescriptor, qualifiedName, checkDeep);
523 }
524
525 return super.isInheritor(baseClass, checkDeep);
526 }
527
528 @Override
529 public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
530 throw new IncorrectOperationException("Cannot modify compiled kotlin element");
531 }
532
533 @Override
534 public String toString() {
535 try {
536 return KotlinLightClass.class.getSimpleName() + ":" + getQualifiedName();
537 }
538 catch (Throwable e) {
539 return KotlinLightClass.class.getSimpleName() + ":" + e.toString();
540 }
541 }
542
543 @NotNull
544 @Override
545 public List<PsiClass> getOwnInnerClasses() {
546 // TODO: Should return inner class wrapper
547 return Arrays.asList(getDelegate().getInnerClasses());
548 }
549
550 private static boolean checkSuperTypeByFQName(@NotNull ClassDescriptor classDescriptor, @NotNull String qualifiedName, Boolean deep) {
551 if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) return true;
552
553 if (qualifiedName.equals(DescriptorUtils.getFqName(classDescriptor).asString())) return true;
554
555 for (JetType superType : classDescriptor.getTypeConstructor().getSupertypes()) {
556 ClassifierDescriptor superDescriptor = superType.getConstructor().getDeclarationDescriptor();
557
558 if (superDescriptor instanceof ClassDescriptor) {
559 if (qualifiedName.equals(DescriptorUtils.getFqName(superDescriptor).asString())) return true;
560
561 if (deep) {
562 if (checkSuperTypeByFQName((ClassDescriptor)superDescriptor, qualifiedName, true)) {
563 return true;
564 }
565 }
566 }
567 }
568
569 return false;
570 }
571 }