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