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