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