2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.psi.impl.source.resolve.reference.impl.providers;
18 import com.intellij.codeInsight.TailType;
19 import com.intellij.codeInsight.completion.JavaCompletionUtil;
20 import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
21 import com.intellij.codeInsight.daemon.QuickFixProvider;
22 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
23 import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix;
24 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
25 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
26 import com.intellij.codeInsight.daemon.quickFix.CreateClassOrPackageFix;
27 import com.intellij.codeInsight.lookup.LookupElementFactoryImpl;
28 import com.intellij.codeInsight.lookup.LookupItem;
29 import com.intellij.codeInspection.LocalQuickFix;
30 import com.intellij.codeInspection.LocalQuickFixProvider;
31 import com.intellij.lang.StdLanguages;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.module.Module;
34 import com.intellij.openapi.module.ModuleUtil;
35 import com.intellij.openapi.util.Key;
36 import com.intellij.openapi.util.TextRange;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.psi.*;
39 import com.intellij.psi.impl.PsiManagerImpl;
40 import com.intellij.psi.impl.source.resolve.ClassResolverProcessor;
41 import com.intellij.psi.impl.source.resolve.ResolveCache;
42 import com.intellij.psi.impl.source.resolve.reference.impl.GenericReference;
43 import com.intellij.psi.infos.CandidateInfo;
44 import com.intellij.psi.infos.ClassCandidateInfo;
45 import com.intellij.psi.jsp.JspFile;
46 import com.intellij.psi.scope.JavaScopeProcessorEvent;
47 import com.intellij.psi.scope.PsiScopeProcessor;
48 import com.intellij.psi.search.GlobalSearchScope;
49 import com.intellij.psi.search.PackageScope;
50 import com.intellij.psi.search.ProjectScope;
51 import com.intellij.psi.search.searches.ClassInheritorsSearch;
52 import com.intellij.psi.util.ClassKind;
53 import com.intellij.psi.util.ClassUtil;
54 import com.intellij.psi.util.PsiUtil;
55 import com.intellij.util.ArrayUtil;
56 import com.intellij.util.IncorrectOperationException;
57 import com.intellij.util.containers.ContainerUtil;
58 import org.jetbrains.annotations.NotNull;
59 import org.jetbrains.annotations.Nullable;
66 public class JavaClassReference extends GenericReference implements PsiJavaReference, QuickFixProvider, LocalQuickFixProvider {
67 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference");
68 protected final int myIndex;
69 private TextRange myRange;
70 private final String myText;
71 private final boolean myInStaticImport;
72 private final JavaClassReferenceSet myJavaClassReferenceSet;
74 public JavaClassReference(final JavaClassReferenceSet referenceSet, TextRange range, int index, String text, final boolean staticImport) {
75 super(referenceSet.getProvider());
76 myInStaticImport = staticImport;
77 LOG.assertTrue(range.getEndOffset() <= referenceSet.getElement().getTextLength());
81 myJavaClassReferenceSet = referenceSet;
85 public PsiElement getContext() {
86 final PsiReference contextRef = getContextReference();
87 return contextRef != null ? contextRef.resolve() : null;
90 public void processVariants(final PsiScopeProcessor processor) {
91 if (processor instanceof JavaCompletionProcessor) {
92 final Map<CustomizableReferenceProvider.CustomizationKey, Object> options = getOptions();
93 if (options != null &&
94 (JavaClassReferenceProvider.EXTEND_CLASS_NAMES.getValue(options) != null ||
95 JavaClassReferenceProvider.NOT_INTERFACE.getBooleanValue(options) ||
96 JavaClassReferenceProvider.CONCRETE.getBooleanValue(options))) {
97 ((JavaCompletionProcessor)processor).setCompletionElements(getVariants());
102 PsiScopeProcessor processorToUse = processor;
103 if (myInStaticImport) {
104 // allows to complete members
105 processor.handleEvent(JavaScopeProcessorEvent.CHANGE_LEVEL, null);
108 if (isDefinitelyStatic()) {
109 processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null);
111 processorToUse = new PsiScopeProcessor() {
112 public boolean execute(PsiElement element, ResolveState state) {
113 return !(element instanceof PsiClass || element instanceof PsiPackage) || processor.execute(element, state);
116 public <V> V getHint(Key<V> hintKey) {
117 return processor.getHint(hintKey);
120 public void handleEvent(Event event, Object associated) {
121 processor.handleEvent(event, associated);
125 super.processVariants(processorToUse);
128 private boolean isDefinitelyStatic() {
129 final String s = getElement().getText();
130 return isStaticClassReference(s, true);
133 private boolean isStaticClassReference(final String s, boolean strict) {
137 char c = s.charAt(getRangeInElement().getStartOffset() - 1);
138 return myJavaClassReferenceSet.isStaticSeparator(c, strict);
142 public PsiReference getContextReference() {
143 return myIndex > 0 ? myJavaClassReferenceSet.getReference(myIndex - 1) : null;
146 private boolean canReferencePackage() {
147 return myJavaClassReferenceSet.canReferencePackage(myIndex);
150 public PsiElement getElement() {
151 return myJavaClassReferenceSet.getElement();
154 public boolean isReferenceTo(PsiElement element) {
155 return (element instanceof PsiClass || element instanceof PsiPackage) && super.isReferenceTo(element);
158 public TextRange getRangeInElement() {
162 public String getCanonicalText() {
166 public boolean isSoft() {
167 return myJavaClassReferenceSet.isSoft();
170 public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
171 final ElementManipulator<PsiElement> manipulator = getManipulator(getElement());
172 if (manipulator != null) {
173 final PsiElement element = manipulator.handleContentChange(getElement(), getRangeInElement(), newElementName);
174 myRange = new TextRange(getRangeInElement().getStartOffset(), getRangeInElement().getStartOffset() + newElementName.length());
177 throw new IncorrectOperationException("Manipulator for this element is not defined: " + getElement());
180 public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
181 if (isReferenceTo(element)) return getElement();
183 final String newName;
184 if (element instanceof PsiClass) {
185 PsiClass psiClass = (PsiClass)element;
186 final boolean jvmFormat = Boolean.TRUE.equals(JavaClassReferenceProvider.JVM_FORMAT.getValue(getOptions()));
187 newName = jvmFormat ? ClassUtil.getJVMClassName(psiClass) : psiClass.getQualifiedName();
189 else if (element instanceof PsiPackage) {
190 PsiPackage psiPackage = (PsiPackage)element;
191 newName = psiPackage.getQualifiedName();
194 throw new IncorrectOperationException("Cannot bind to " + element);
196 assert newName != null;
199 new TextRange(myJavaClassReferenceSet.getReference(0).getRangeInElement().getStartOffset(), getRangeInElement().getEndOffset());
200 final ElementManipulator<PsiElement> manipulator = getManipulator(getElement());
201 if (manipulator != null) {
202 final PsiElement finalElement = manipulator.handleContentChange(getElement(), range, newName);
203 range = new TextRange(range.getStartOffset(), range.getStartOffset() + newName.length());
204 myJavaClassReferenceSet.reparse(finalElement, range);
210 public PsiElement resolveInner() {
211 return advancedResolve(true).getElement();
214 public Object[] getVariants() {
215 PsiElement context = getContext();
216 if (context == null) {
217 context = JavaPsiFacade.getInstance(getElement().getProject()).findPackage("");
219 if (context instanceof PsiPackage) {
220 final String[] extendClasses = getExtendClassNames();
221 if (extendClasses != null) {
222 return getSubclassVariants((PsiPackage)context, extendClasses);
224 return processPackage((PsiPackage)context);
226 if (context instanceof PsiClass) {
227 final PsiClass aClass = (PsiClass)context;
229 if (myInStaticImport) {
230 return ArrayUtil.mergeArrays(aClass.getInnerClasses(), aClass.getFields(), Object.class);
232 else if (isDefinitelyStatic()) {
233 final PsiClass[] psiClasses = aClass.getInnerClasses();
234 final List<PsiClass> staticClasses = new ArrayList<PsiClass>(psiClasses.length);
236 for (PsiClass c : psiClasses) {
237 if (c.hasModifierProperty(PsiModifier.STATIC)) {
238 staticClasses.add(c);
241 return staticClasses.isEmpty() ? PsiClass.EMPTY_ARRAY : staticClasses.toArray(new PsiClass[staticClasses.size()]);
244 return ArrayUtil.EMPTY_OBJECT_ARRAY;
247 public String[] getExtendClassNames() {
248 return JavaClassReferenceProvider.EXTEND_CLASS_NAMES.getValue(getOptions());
251 private Object[] processPackage(final PsiPackage aPackage) {
252 final ArrayList<Object> list = new ArrayList<Object>();
253 final int startOffset = StringUtil.isEmpty(aPackage.getName()) ? 0 : aPackage.getQualifiedName().length() + 1;
254 final GlobalSearchScope scope = getScope();
255 for (final PsiPackage subPackage : aPackage.getSubPackages(scope)) {
256 final String shortName = subPackage.getQualifiedName().substring(startOffset);
257 if (JavaPsiFacade.getInstance(subPackage.getProject()).getNameHelper().isIdentifier(shortName)) {
258 list.add(subPackage);
262 final PsiClass[] classes = aPackage.getClasses(scope);
263 final Map<CustomizableReferenceProvider.CustomizationKey, Object> options = getOptions();
264 if (options != null) {
265 final boolean instantiatable = JavaClassReferenceProvider.INSTANTIATABLE.getBooleanValue(options);
266 final boolean concrete = JavaClassReferenceProvider.CONCRETE.getBooleanValue(options);
267 final boolean notInterface = JavaClassReferenceProvider.NOT_INTERFACE.getBooleanValue(options);
268 final boolean notEnum = JavaClassReferenceProvider.NOT_ENUM.getBooleanValue(options);
269 for (PsiClass clazz : classes) {
270 if (isClassAccepted(clazz, instantiatable, concrete, notInterface, notEnum)) {
276 list.addAll(Arrays.asList(classes));
278 return list.toArray();
281 private static boolean isClassAccepted(final PsiClass clazz,
282 final boolean instantiatable,
283 final boolean concrete,
284 final boolean notInterface,
285 final boolean notEnum) {
286 if (instantiatable) {
287 if (PsiUtil.isInstantiatable(clazz)) {
292 if (!clazz.hasModifierProperty(PsiModifier.ABSTRACT) && !clazz.isInterface()) {
296 else if (notInterface) {
297 if (!clazz.isInterface()) {
302 if (!clazz.isEnum()) {
313 public JavaResolveResult advancedResolve(boolean incompleteCode) {
314 final PsiManager manager = getElement().getManager();
315 if(manager instanceof PsiManagerImpl){
316 return (JavaResolveResult)((PsiManagerImpl)manager).getResolveCache().resolveWithCaching(this, MyResolver.INSTANCE, false, false)[0];
318 return doAdvancedResolve();
321 private JavaResolveResult doAdvancedResolve() {
322 final PsiElement psiElement = getElement();
324 if (!psiElement.isValid()) return JavaResolveResult.EMPTY;
326 final String elementText = psiElement.getText();
328 final PsiElement context = getContext();
329 if (context instanceof PsiClass) {
330 if (isStaticClassReference(elementText, false)) {
331 final PsiClass psiClass = ((PsiClass)context).findInnerClassByName(getCanonicalText(), false);
332 if (psiClass != null) return new ClassCandidateInfo(psiClass, PsiSubstitutor.EMPTY, false, psiElement);
333 PsiElement member = doResolveMember((PsiClass)context, myText);
334 return member == null ? JavaResolveResult.EMPTY : new CandidateInfo(member, PsiSubstitutor.EMPTY, false, false, psiElement);
336 else if (!myInStaticImport && myJavaClassReferenceSet.isAllowDollarInNames()) {
337 return JavaResolveResult.EMPTY;
341 final int endOffset = getRangeInElement().getEndOffset();
342 LOG.assertTrue(endOffset <= elementText.length(), elementText);
343 final int startOffset = myJavaClassReferenceSet.getReference(0).getRangeInElement().getStartOffset();
344 final String qName = elementText.substring(startOffset, endOffset);
345 if (!qName.contains(".")) {
346 final String defaultPackage = JavaClassReferenceProvider.DEFAULT_PACKAGE.getValue(getOptions());
347 if (StringUtil.isNotEmpty(defaultPackage)) {
348 final JavaResolveResult resolveResult = advancedResolveInner(psiElement, defaultPackage + "." + qName);
349 if (resolveResult != JavaResolveResult.EMPTY) {
350 return resolveResult;
354 return advancedResolveInner(psiElement, qName);
357 private JavaResolveResult advancedResolveInner(final PsiElement psiElement, final String qName) {
358 final PsiManager manager = psiElement.getManager();
359 final GlobalSearchScope scope = getScope();
360 if (myIndex == myJavaClassReferenceSet.getReferences().length - 1) {
361 final PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(qName, scope);
362 if (aClass != null) {
363 return new ClassCandidateInfo(aClass, PsiSubstitutor.EMPTY, false, psiElement);
366 if (!JavaClassReferenceProvider.ADVANCED_RESOLVE.getBooleanValue(getOptions())) {
367 return JavaResolveResult.EMPTY;
371 PsiElement resolveResult = JavaPsiFacade.getInstance(manager.getProject()).findPackage(qName);
372 if (resolveResult == null) {
373 resolveResult = JavaPsiFacade.getInstance(manager.getProject()).findClass(qName, scope);
375 if (myInStaticImport && resolveResult == null) {
376 resolveResult = resolveMember(qName, manager, getElement().getResolveScope());
378 if (resolveResult == null) {
379 PsiFile containingFile = psiElement.getContainingFile();
381 if (containingFile instanceof PsiJavaFile) {
382 if (containingFile instanceof JspFile) {
383 containingFile = containingFile.getViewProvider().getPsi(StdLanguages.JAVA);
384 if (containingFile == null) return JavaResolveResult.EMPTY;
387 final ClassResolverProcessor processor = new ClassResolverProcessor(getCanonicalText(), psiElement);
388 containingFile.processDeclarations(processor, ResolveState.initial(), null, psiElement);
390 if (processor.getResult().length == 1) {
391 final JavaResolveResult javaResolveResult = processor.getResult()[0];
393 if (javaResolveResult != JavaResolveResult.EMPTY && getOptions() != null) {
394 final Boolean value = JavaClassReferenceProvider.RESOLVE_QUALIFIED_CLASS_NAME.getValue(getOptions());
395 final PsiClass psiClass = (PsiClass)javaResolveResult.getElement();
396 if (value != null && value.booleanValue() && psiClass != null) {
397 final String qualifiedName = psiClass.getQualifiedName();
399 if (!qName.equals(qualifiedName)) {
400 return JavaResolveResult.EMPTY;
405 return javaResolveResult;
409 return resolveResult != null
410 ? new CandidateInfo(resolveResult, PsiSubstitutor.EMPTY, false, false, psiElement)
411 : JavaResolveResult.EMPTY;
414 private GlobalSearchScope getScope() {
415 final GlobalSearchScope scope = myJavaClassReferenceSet.getProvider().getScope();
417 final Module module = ModuleUtil.findModuleForPsiElement(getElement());
418 if (module != null) {
419 return module.getModuleWithDependenciesAndLibrariesScope(true);
421 return GlobalSearchScope.allScope(getElement().getProject());
427 private Map<CustomizableReferenceProvider.CustomizationKey, Object> getOptions() {
428 return myJavaClassReferenceSet.getOptions();
432 public JavaResolveResult[] multiResolve(boolean incompleteCode) {
433 final JavaResolveResult javaResolveResult = advancedResolve(incompleteCode);
434 if (javaResolveResult.getElement() == null) return JavaResolveResult.EMPTY_ARRAY;
435 return new JavaResolveResult[]{javaResolveResult};
438 public void registerQuickfix(HighlightInfo info, PsiReference reference) {
443 private List<? extends LocalQuickFix> registerFixes(final HighlightInfo info) {
445 final List<LocalQuickFix> list = OrderEntryFix.registerFixes(new QuickFixActionRegistrarImpl(info), this);
447 final String[] extendClasses = getExtendClassNames();
448 final String extendClass = extendClasses != null && extendClasses.length > 0 ? extendClasses[0] : null;
450 final JavaClassReference[] references = getJavaClassReferenceSet().getAllReferences();
451 PsiPackage contextPackage = null;
452 for (int i = myIndex; i >= 0; i--) {
453 final PsiElement context = references[i].getContext();
454 if (context != null) {
455 if (context instanceof PsiPackage) {
456 contextPackage = (PsiPackage)context;
462 boolean createJavaClass = !canReferencePackage();
463 ClassKind kind = createJavaClass ? JavaClassReferenceProvider.CLASS_KIND.getValue(getOptions()) : null;
464 if (createJavaClass && kind == null) kind = ClassKind.CLASS;
465 final String templateName = JavaClassReferenceProvider.CLASS_TEMPLATE.getValue(getOptions());
466 final TextRange range = new TextRange(references[0].getRangeInElement().getStartOffset(),
467 getRangeInElement().getEndOffset());
468 final String qualifiedName = range.substring(getElement().getText());
469 final CreateClassOrPackageFix action = CreateClassOrPackageFix.createFix(qualifiedName, getScope(), getElement(), contextPackage,
470 kind, extendClass, templateName);
471 if (action != null) {
472 QuickFixAction.registerQuickFixAction(info, action);
474 return Arrays.asList(action);
477 final ArrayList<LocalQuickFix> fixes = new ArrayList<LocalQuickFix>(list.size() + 1);
487 private Object[] getSubclassVariants(@NotNull PsiPackage context, @NotNull String[] extendClasses) {
488 HashSet<Object> lookups = new HashSet<Object>();
489 GlobalSearchScope packageScope = PackageScope.packageScope(context, true);
490 GlobalSearchScope scope = myJavaClassReferenceSet.getProvider().getScope();
492 packageScope = packageScope.intersectWith(scope);
494 final GlobalSearchScope allScope = ProjectScope.getAllScope(context.getProject());
495 final boolean instantiatable = JavaClassReferenceProvider.INSTANTIATABLE.getBooleanValue(getOptions());
496 final boolean notInterface = JavaClassReferenceProvider.NOT_INTERFACE.getBooleanValue(getOptions());
497 final boolean notEnum = JavaClassReferenceProvider.NOT_ENUM.getBooleanValue(getOptions());
498 final boolean concrete = JavaClassReferenceProvider.CONCRETE.getBooleanValue(getOptions());
500 for (String extendClassName : extendClasses) {
501 final PsiClass extendClass = JavaPsiFacade.getInstance(context.getProject()).findClass(extendClassName, allScope);
502 if (extendClass != null) {
504 if (packageScope.contains(extendClass.getContainingFile().getVirtualFile())) {
505 if (isClassAccepted(extendClass, instantiatable, concrete, notInterface, notEnum)) {
506 ContainerUtil.addIfNotNull(createSubclassLookupValue(context, extendClass), lookups);
509 for (final PsiClass clazz : ClassInheritorsSearch.search(extendClass, packageScope, true)) {
510 if (isClassAccepted(clazz, instantiatable, concrete, notInterface, notEnum)) {
511 ContainerUtil.addIfNotNull(createSubclassLookupValue(context, clazz), lookups);
516 return lookups.toArray();
520 private static Object createSubclassLookupValue(@NotNull final PsiPackage context, @NotNull final PsiClass clazz) {
521 String name = clazz.getQualifiedName();
522 if (name == null) return null;
523 final String pack = context.getQualifiedName();
524 if (pack.length() > 0) {
525 if (name.startsWith(pack)) {
526 name = name.substring(pack.length() + 1);
532 final LookupItem<PsiClass> lookup = LookupElementFactoryImpl.getInstance().createLookupElement(clazz, name);
533 lookup.addLookupStrings(clazz.getName());
534 return JavaCompletionUtil.setShowFQN(lookup).setTailType(TailType.NONE);
537 public LocalQuickFix[] getQuickFixes() {
538 final List<? extends LocalQuickFix> list = registerFixes(null);
539 return list == null ? LocalQuickFix.EMPTY_ARRAY : list.toArray(new LocalQuickFix[list.size()]);
543 public static PsiElement resolveMember(String fqn, PsiManager manager, GlobalSearchScope resolveScope) {
544 PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(fqn, resolveScope);
545 if (aClass != null) return aClass;
546 int i = fqn.lastIndexOf('.');
547 if (i == -1) return null;
548 String memberName = fqn.substring(i + 1);
549 fqn = fqn.substring(0, i);
550 aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(fqn, resolveScope);
551 if (aClass == null) return null;
552 return doResolveMember(aClass, memberName);
555 private static PsiElement doResolveMember(PsiClass aClass, String memberName) {
556 PsiMember member = aClass.findFieldByName(memberName, true);
557 if (member != null) return member;
559 PsiMethod[] methods = aClass.findMethodsByName(memberName, true);
560 return methods.length == 0 ? null : methods[0];
563 public JavaClassReferenceSet getJavaClassReferenceSet() {
564 return myJavaClassReferenceSet;
567 public String getUnresolvedMessagePattern() {
568 return myJavaClassReferenceSet.getUnresolvedMessagePattern(myIndex);
571 private static class MyResolver implements ResolveCache.PolyVariantResolver<JavaClassReference> {
572 private static final MyResolver INSTANCE = new MyResolver();
574 public JavaResolveResult[] resolve(JavaClassReference javaClassReference, boolean incompleteCode) {
575 return new JavaResolveResult[]{javaClassReference.doAdvancedResolve()};