| /* |
| * Copyright 2000-2010 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jetbrains.android.dom.converters; |
| |
| import com.intellij.codeInsight.completion.JavaLookupElementBuilder; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.resolve.ResolveCache; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ClassInheritorsSearch; |
| import com.intellij.psi.xml.XmlAttributeValue; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Query; |
| import com.intellij.util.xml.*; |
| import org.jetbrains.android.dom.manifest.Manifest; |
| import org.jetbrains.android.util.AndroidUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * @author yole |
| */ |
| public class PackageClassConverter extends ResolvingConverter<PsiClass> implements CustomReferenceConverter<PsiClass> { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.dom.converters.PackageClassConverter"); |
| |
| private final String[] myExtendClassesNames; |
| |
| public PackageClassConverter(String... extendClassesNames) { |
| myExtendClassesNames = extendClassesNames; |
| } |
| |
| public PackageClassConverter() { |
| myExtendClassesNames = ArrayUtil.EMPTY_STRING_ARRAY; |
| } |
| |
| @Override |
| @NotNull |
| public Collection<? extends PsiClass> getVariants(ConvertContext context) { |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public PsiClass fromString(@Nullable @NonNls String s, ConvertContext context) { |
| if (s == null) return null; |
| DomElement domElement = context.getInvocationElement(); |
| Manifest manifest = domElement.getParentOfType(Manifest.class, true); |
| s = s.replace('$', '.'); |
| String packageName = manifest != null ? manifest.getPackage().getValue() : null; |
| String className = null; |
| |
| if (packageName != null) { |
| if (s.startsWith(".")) { |
| className = packageName + s; |
| } |
| else { |
| className = packageName + "." + s; |
| } |
| } |
| JavaPsiFacade facade = JavaPsiFacade.getInstance(context.getPsiManager().getProject()); |
| final Module module = context.getModule(); |
| GlobalSearchScope scope = module != null |
| ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) |
| : context.getInvocationElement().getResolveScope(); |
| PsiClass psiClass = className != null ? facade.findClass(className, scope) : null; |
| if (psiClass == null) { |
| psiClass = facade.findClass(s, scope); |
| } |
| return psiClass; |
| } |
| |
| @NotNull |
| @Override |
| public PsiReference[] createReferences(GenericDomValue<PsiClass> value, PsiElement element, ConvertContext context) { |
| assert element instanceof XmlAttributeValue; |
| final XmlAttributeValue attrValue = (XmlAttributeValue)element; |
| final String strValue = attrValue.getValue(); |
| |
| final boolean startsWithPoint = strValue.startsWith("."); |
| final int start = attrValue.getValueTextRange().getStartOffset() - attrValue.getTextRange().getStartOffset(); |
| |
| final DomElement domElement = context.getInvocationElement(); |
| final Manifest manifest = domElement.getParentOfType(Manifest.class, true); |
| final String basePackage = manifest == null ? null : manifest.getPackage().getValue(); |
| final ExtendClass extendClassAnnotation = domElement.getAnnotation(ExtendClass.class); |
| |
| final String[] extendClassesNames = extendClassAnnotation != null |
| ? new String[]{extendClassAnnotation.value()} |
| : myExtendClassesNames; |
| final boolean inModuleOnly = domElement.getAnnotation(CompleteNonModuleClass.class) == null; |
| |
| List<PsiReference> result = new ArrayList<PsiReference>(); |
| final String[] nameParts = strValue.split("\\."); |
| if (nameParts.length == 0) { |
| return PsiReference.EMPTY_ARRAY; |
| } |
| |
| final Module module = context.getModule(); |
| int offset = start; |
| |
| for (int i = 0, n = nameParts.length; i < n - 1; i++) { |
| final String packageName = nameParts[i]; |
| |
| if (packageName.length() > 0) { |
| offset += packageName.length(); |
| final TextRange range = new TextRange(offset - packageName.length(), offset); |
| result.add(new MyReference(element, range, basePackage, startsWithPoint, start, true, module, extendClassesNames, inModuleOnly)); |
| } |
| offset++; |
| } |
| |
| final String className = nameParts[nameParts.length - 1]; |
| final String[] classNameParts = className.split("\\$"); |
| |
| for (String s : classNameParts) { |
| if (s.length() > 0) { |
| offset += s.length(); |
| |
| final TextRange range = new TextRange(offset - s.length(), offset); |
| result.add(new MyReference(element, range, basePackage, startsWithPoint, start, false, module, extendClassesNames, inModuleOnly)); |
| } |
| offset++; |
| } |
| |
| return result.toArray(new PsiReference[result.size()]); |
| } |
| |
| @Nullable |
| public static String getQualifiedName(@NotNull PsiClass aClass) { |
| PsiElement parent = aClass.getParent(); |
| if (parent instanceof PsiClass) { |
| String parentQName = getQualifiedName((PsiClass)parent); |
| if (parentQName == null) return null; |
| return parentQName + "$" + aClass.getName(); |
| } |
| return aClass.getQualifiedName(); |
| } |
| |
| @Nullable |
| private static String getName(@NotNull PsiClass aClass) { |
| PsiElement parent = aClass.getParent(); |
| if (parent instanceof PsiClass) { |
| String parentName = getName((PsiClass)parent); |
| if (parentName == null) return null; |
| return parentName + "$" + aClass.getName(); |
| } |
| return aClass.getName(); |
| } |
| |
| @Override |
| @Nullable |
| public String toString(@Nullable PsiClass psiClass, ConvertContext context) { |
| DomElement domElement = context.getInvocationElement(); |
| Manifest manifest = domElement.getParentOfType(Manifest.class, true); |
| final String packageName = manifest == null ? null : manifest.getPackage().getValue(); |
| return classToString(psiClass, packageName, ""); |
| } |
| |
| @Nullable |
| public static String getPackageName(@NotNull PsiClass psiClass) { |
| final PsiFile file = psiClass.getContainingFile(); |
| return file instanceof PsiClassOwner ? ((PsiClassOwner)file).getPackageName() : null; |
| } |
| |
| @Nullable |
| private static String classToString(PsiClass psiClass, String basePackageName, String prefix) { |
| if (psiClass == null) return null; |
| String qName = getQualifiedName(psiClass); |
| if (qName == null) return null; |
| PsiFile file = psiClass.getContainingFile(); |
| if (file instanceof PsiClassOwner) { |
| PsiClassOwner psiFile = (PsiClassOwner)file; |
| if (Comparing.equal(psiFile.getPackageName(), basePackageName)) { |
| String name = getName(psiClass); |
| if (name != null) { |
| final String dottedName = '.' + name; |
| if (dottedName.startsWith(prefix)) { |
| return dottedName; |
| } |
| else if (name.startsWith(prefix)) { |
| return name; |
| } |
| } |
| } |
| else if (basePackageName != null && qName.startsWith(basePackageName + ".")) { |
| final String name = qName.substring(basePackageName.length()); |
| if (name.startsWith(prefix)) { |
| return name; |
| } |
| } |
| } |
| return qName; |
| } |
| |
| @NotNull |
| public static Collection<PsiClass> findInheritors(@NotNull Project project, @Nullable final Module module, @NotNull final String className, boolean inModuleOnly) { |
| PsiClass base = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)); |
| if (base != null) { |
| GlobalSearchScope scope = inModuleOnly && module != null |
| ? GlobalSearchScope.moduleWithDependenciesScope(module) |
| : GlobalSearchScope.allScope(project); |
| Query<PsiClass> query = ClassInheritorsSearch.search(base, scope, true); |
| return query.findAll(); |
| } |
| return new ArrayList<PsiClass>(); |
| } |
| |
| private static class MyReference extends PsiReferenceBase<PsiElement> { |
| private final int myStart; |
| private final String myBasePackage; |
| private final boolean myStartsWithPoint; |
| private final boolean myIsPackage; |
| private final Module myModule; |
| private final String[] myExtendsClasses; |
| private final boolean myCompleteOnlyModuleClasses; |
| |
| public MyReference(PsiElement element, |
| TextRange range, |
| String basePackage, |
| boolean startsWithPoint, |
| int start, |
| boolean isPackage, |
| Module module, |
| String[] extendsClasses, |
| boolean completeOnlyModuleClasses) { |
| super(element, range, true); |
| myBasePackage = basePackage; |
| myStartsWithPoint = startsWithPoint; |
| myStart = start; |
| myIsPackage = isPackage; |
| myModule = module; |
| myExtendsClasses = extendsClasses; |
| myCompleteOnlyModuleClasses = completeOnlyModuleClasses; |
| } |
| |
| @Override |
| public PsiElement resolve() { |
| return ResolveCache.getInstance(myElement.getProject()).resolveWithCaching(this, new ResolveCache.Resolver() { |
| @Nullable |
| @Override |
| public PsiElement resolve(@NotNull PsiReference reference, boolean incompleteCode) { |
| return resolveInner(); |
| } |
| }, false, false); |
| } |
| |
| @Nullable |
| private PsiElement resolveInner() { |
| final String value = getCurrentValue(); |
| final JavaPsiFacade facade = JavaPsiFacade.getInstance(myElement.getProject()); |
| |
| if (!myStartsWithPoint) { |
| final PsiElement element = myIsPackage ? |
| facade.findPackage(value) : |
| facade.findClass(value, myModule != null |
| ? myModule.getModuleWithDependenciesAndLibrariesScope(false) |
| : myElement.getResolveScope()); |
| |
| if (element != null) { |
| return element; |
| } |
| } |
| |
| final String absName = getAbsoluteName(value); |
| if (absName != null) { |
| return myIsPackage ? |
| facade.findPackage(absName) : |
| facade.findClass(absName, myModule != null |
| ? myModule.getModuleWithDependenciesAndLibrariesScope(false) |
| : myElement.getResolveScope()); |
| } |
| return null; |
| } |
| |
| @NotNull |
| private String getCurrentValue() { |
| final int end = getRangeInElement().getEndOffset(); |
| return myElement.getText().substring(myStart, end).replace('$', '.'); |
| } |
| |
| @Nullable |
| private String getAbsoluteName(String value) { |
| if (myBasePackage == null) { |
| return null; |
| } |
| return myBasePackage + (myStartsWithPoint ? "" : ".") + value; |
| } |
| |
| @NotNull |
| @Override |
| public Object[] getVariants() { |
| if (myExtendsClasses != null) { |
| final List<PsiClass> classes = new ArrayList<PsiClass>(); |
| for (String extendsClass : myExtendsClasses) { |
| classes.addAll(findInheritors(myElement.getProject(), myModule, extendsClass, myCompleteOnlyModuleClasses)); |
| } |
| final List<Object> result = new ArrayList<Object>(classes.size()); |
| |
| for (int i = 0, n = classes.size(); i < n; i++) { |
| final PsiClass psiClass = classes.get(i); |
| final String prefix = myElement.getText().substring(myStart, getRangeInElement().getStartOffset()); |
| String name = classToString(psiClass, myBasePackage, prefix); |
| |
| if (name != null && name.startsWith(prefix)) { |
| name = name.substring(prefix.length()); |
| result.add(JavaLookupElementBuilder.forClass(psiClass, name, true)); |
| } |
| } |
| return ArrayUtil.toObjectArray(result); |
| } |
| return EMPTY_ARRAY; |
| } |
| |
| @Override |
| public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { |
| if (element instanceof PsiClass || element instanceof PsiPackage) { |
| if (myIsPackage && myBasePackage != null && |
| AndroidUtils.isPackagePrefix(getCurrentValue(), myBasePackage)) { |
| // in such case reference updating is performed by AndroidPackageConverter.MyPsiPackageReference#bindToElement() |
| return super.bindToElement(element); |
| } |
| String newName = element instanceof PsiClass ? classToString((PsiClass)element, myBasePackage, "") : |
| packageToString((PsiPackage)element, myBasePackage); |
| assert newName != null; |
| |
| final ElementManipulator<PsiElement> manipulator = ElementManipulators.getManipulator(myElement); |
| final TextRange range = new TextRange(myStart, getRangeInElement().getEndOffset()); |
| |
| if (manipulator != null) { |
| final PsiElement newElement = manipulator.handleContentChange(myElement, range, newName); |
| |
| if (newElement instanceof XmlAttributeValue) { |
| final XmlAttributeValue attrValue = (XmlAttributeValue)newElement; |
| final String newRef = attrValue.getValue(); |
| |
| if (myBasePackage != null && |
| myBasePackage.length() > 0 && |
| newRef.startsWith(myBasePackage + ".")) { |
| return manipulator.handleContentChange(myElement, newRef.substring(myBasePackage.length())); |
| } |
| } |
| return newElement; |
| } |
| return element; |
| } |
| LOG.error("PackageClassConverter resolved to " + element.getClass()); |
| return super.bindToElement(element); |
| } |
| |
| private static String packageToString(PsiPackage psiPackage, String basePackageName) { |
| final String qName = psiPackage.getQualifiedName(); |
| return basePackageName != null && AndroidUtils.isPackagePrefix(basePackageName, qName) ? |
| qName.substring(basePackageName.length()) : |
| qName; |
| } |
| } |
| } |