optimize working with android resources

This commit is contained in:
Eugene Kudelevsky
2012-07-27 18:48:39 +04:00
parent 37a4148519
commit 0357b26c4e
13 changed files with 3538 additions and 167 deletions
@@ -26,6 +26,7 @@ import com.intellij.psi.xml.*;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.ResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper;
import org.jetbrains.android.facet.AndroidFacet;
@@ -45,21 +46,26 @@ import java.util.List;
public class AndroidFindUsagesHandlerFactory extends FindUsagesHandlerFactory {
@Override
public boolean canFindUsages(@NotNull PsiElement element) {
if (element instanceof XmlAttributeValue) {
XmlAttributeValue value = (XmlAttributeValue)element;
PsiElement element1 = LazyValueResourceElementWrapper.computeLazyElement(element);
if (element1 == null) {
return false;
}
if (element1 instanceof XmlAttributeValue) {
XmlAttributeValue value = (XmlAttributeValue)element1;
if (AndroidResourceUtil.findIdFields(value).length > 0) {
return true;
}
}
element = correctResourceElement(element);
if (element instanceof PsiField) {
return AndroidResourceUtil.isResourceField((PsiField)element);
element1 = correctResourceElement(element1);
if (element1 instanceof PsiField) {
return AndroidResourceUtil.isResourceField((PsiField)element1);
}
else if (element instanceof PsiFile) {
return AndroidResourceUtil.findResourceFieldsForFileResource((PsiFile)element, true).length > 0;
else if (element1 instanceof PsiFile) {
return AndroidResourceUtil.findResourceFieldsForFileResource((PsiFile)element1, true).length > 0;
}
else if (element instanceof XmlTag) {
return AndroidResourceUtil.findResourceFieldsForValueResource((XmlTag)element, true).length > 0;
else if (element1 instanceof XmlTag) {
return AndroidResourceUtil.findResourceFieldsForValueResource((XmlTag)element1, true).length > 0;
}
return false;
}
@@ -101,7 +107,11 @@ public class AndroidFindUsagesHandlerFactory extends FindUsagesHandlerFactory {
@Override
public FindUsagesHandler createFindUsagesHandler(@NotNull PsiElement element, boolean forHighlightUsages) {
PsiElement e = element;
PsiElement e = LazyValueResourceElementWrapper.computeLazyElement(element);
if (e == null) {
return null;
}
AndroidFacet facet = AndroidFacet.getInstance(e);
assert facet != null;
if (e instanceof XmlAttributeValue) {
@@ -41,6 +41,7 @@ import com.intellij.util.IncorrectOperationException;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
@@ -67,23 +68,28 @@ public class AndroidRenameResourceProcessor extends RenamePsiElementProcessor {
public boolean canProcessElement(@NotNull final PsiElement element) {
return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
public Boolean compute() {
if (element instanceof PsiFile) {
return AndroidFacet.getInstance(element) != null && AndroidResourceUtil.isInResourceSubdirectory((PsiFile)element, null);
final PsiElement element1 = LazyValueResourceElementWrapper.computeLazyElement(element);
if (element1 == null) {
return false;
}
else if (element instanceof PsiField) {
PsiField field = (PsiField)element;
if (element1 instanceof PsiFile) {
return AndroidFacet.getInstance(element1) != null && AndroidResourceUtil.isInResourceSubdirectory((PsiFile)element1, null);
}
else if (element1 instanceof PsiField) {
PsiField field = (PsiField)element1;
if (AndroidResourceUtil.isResourceField(field)) {
return AndroidResourceUtil.findResourcesByField(field).size() > 0;
}
}
else if (element instanceof XmlAttributeValue) {
LocalResourceManager manager = LocalResourceManager.getInstance(element);
else if (element1 instanceof XmlAttributeValue) {
LocalResourceManager manager = LocalResourceManager.getInstance(element1);
if (manager != null) {
if (AndroidResourceUtil.isIdDeclaration((XmlAttributeValue)element)) {
if (AndroidResourceUtil.isIdDeclaration((XmlAttributeValue)element1)) {
return true;
}
// then it is value resource
XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
XmlTag tag = PsiTreeUtil.getParentOfType(element1, XmlTag.class);
return tag != null && manager.getValueResourceType(tag) != null;
}
}
@@ -94,22 +100,29 @@ public class AndroidRenameResourceProcessor extends RenamePsiElementProcessor {
@Override
public void prepareRenaming(PsiElement element, String newName, Map<PsiElement, String> allRenames) {
AndroidFacet facet = AndroidFacet.getInstance(element);
assert facet != null;
if (element instanceof PsiFile) {
prepareResourceFileRenaming((PsiFile)element, newName, allRenames, facet);
final PsiElement element1 = LazyValueResourceElementWrapper.computeLazyElement(element);
if (element1 == null) {
return;
}
else if (element instanceof XmlAttributeValue) {
XmlAttributeValue value = (XmlAttributeValue)element;
// todo: support renaming alternative value resources
AndroidFacet facet = AndroidFacet.getInstance(element1);
assert facet != null;
if (element1 instanceof PsiFile) {
prepareResourceFileRenaming((PsiFile)element1, newName, allRenames, facet);
}
else if (element1 instanceof XmlAttributeValue) {
XmlAttributeValue value = (XmlAttributeValue)element1;
if (AndroidResourceUtil.isIdDeclaration(value)) {
prepareIdRenaming(value, newName, allRenames, facet);
}
else {
prepareValueResourceRenaming(element, newName, allRenames, facet);
prepareValueResourceRenaming(element1, newName, allRenames, facet);
}
}
else if (element instanceof PsiField) {
prepareResourceFieldRenaming((PsiField)element, newName, allRenames);
else if (element1 instanceof PsiField) {
prepareResourceFieldRenaming((PsiField)element1, newName, allRenames);
}
}
@@ -1,9 +1,14 @@
package org.jetbrains.android;
import com.android.resources.ResourceType;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.indexing.*;
@@ -11,8 +16,9 @@ import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.KeyDescriptor;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.xml.NanoXmlUtil;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.ResourceEntry;
import org.jetbrains.android.util.ValueResourcesFileParser;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,8 +32,12 @@ import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidValueResourcesIndex extends FileBasedIndexExtension<ResourceEntry, Set<ResourceEntry>> {
public static final ID<ResourceEntry, Set<ResourceEntry>> INDEX_ID = ID.create("android.value.resources.index");
public class AndroidValueResourcesIndex extends FileBasedIndexExtension<ResourceEntry, Set<AndroidValueResourcesIndex.MyResourceInfo>> {
public static final ID<ResourceEntry, Set<MyResourceInfo>> INDEX_ID = ID.create("android.value.resources.index");
@NonNls private static final String RESOURCES_ROOT_TAG = "resources";
@NonNls private static final String NAME_ATTRIBUTE_VALUE = "name";
@NonNls private static final String TYPE_ATTRIBUTE_VALUE = "type";
private final FileBasedIndex.InputFilter myInputFilter = new FileBasedIndex.InputFilter() {
@Override
@@ -37,28 +47,54 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
}
};
private final DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent> myIndexer =
new DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent>() {
private final DataIndexer<ResourceEntry, Set<MyResourceInfo>, FileContent> myIndexer =
new DataIndexer<ResourceEntry, Set<MyResourceInfo>, FileContent>() {
@Override
@NotNull
public Map<ResourceEntry, Set<ResourceEntry>> map(FileContent inputData) {
if (CharArrayUtil.indexOf(inputData.getContentAsText(), "<resources", 0) < 0) {
public Map<ResourceEntry, Set<MyResourceInfo>> map(FileContent inputData) {
if (!isSimilarFile(inputData)) {
return Collections.emptyMap();
}
final Map<ResourceEntry, Set<ResourceEntry>> result = new HashMap<ResourceEntry, Set<ResourceEntry>>();
final PsiFile file = inputData.getPsiFile();
NanoXmlUtil.parse(CharArrayUtil.readerFromCharSequence(inputData.getContentAsText()), new ValueResourcesFileParser() {
@Override
protected void stop() {
throw new NanoXmlUtil.ParserStoppedException();
}
if (!(file instanceof XmlFile)) {
return Collections.emptyMap();
}
final Map<ResourceEntry, Set<MyResourceInfo>> result = new HashMap<ResourceEntry, Set<MyResourceInfo>>();
file.accept(new XmlRecursiveElementVisitor() {
@Override
protected void process(@NotNull ResourceEntry entry) {
result.put(entry, Collections.<ResourceEntry>emptySet());
addEntryToMap(entry, createTypeMarkerKey(entry.getType()), result);
addEntryToMap(entry, createTypeNameMarkerKey(entry.getType(), entry.getName()), result);
public void visitXmlTag(XmlTag tag) {
super.visitXmlTag(tag);
final String resName = tag.getAttributeValue(NAME_ATTRIBUTE_VALUE);
if (resName == null) {
return;
}
final String tagName = tag.getName();
final String resTypeStr;
if ("item".equals(tagName)) {
resTypeStr = tag.getAttributeValue(TYPE_ATTRIBUTE_VALUE);
}
else {
resTypeStr = AndroidCommonUtils.getResourceTypeByTagName(tagName);
}
final ResourceType resType = resTypeStr != null ? ResourceType.getEnum(resTypeStr) : null;
if (resType == null) {
return;
}
final int offset = tag.getTextRange().getStartOffset();
if (resType == ResourceType.ATTR) {
final XmlTag parentTag = tag.getParentTag();
final String contextName = parentTag != null ? parentTag.getAttributeValue(NAME_ATTRIBUTE_VALUE) : null;
processResourceEntry(new ResourceEntry(resTypeStr, resName, contextName != null ? contextName : ""), result, offset);
}
else {
processResourceEntry(new ResourceEntry(resTypeStr, resName, ""), result, offset);
}
}
});
@@ -66,14 +102,40 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
}
};
private static void addEntryToMap(ResourceEntry entry, ResourceEntry marker, Map<ResourceEntry, Set<ResourceEntry>> result) {
Set<ResourceEntry> set = result.get(marker);
private static boolean isSimilarFile(FileContent inputData) {
if (CharArrayUtil.indexOf(inputData.getContentAsText(), "<" + RESOURCES_ROOT_TAG, 0) < 0) {
return false;
}
final boolean[] ourRootTag = {false};
NanoXmlUtil.parse(CharArrayUtil.readerFromCharSequence(inputData.getContentAsText()), new NanoXmlUtil.IXMLBuilderAdapter() {
@Override
public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr)
throws Exception {
ourRootTag[0] = RESOURCES_ROOT_TAG.equals(name) && nsPrefix == null;
stop();
}
});
return ourRootTag[0];
}
private static void processResourceEntry(@NotNull ResourceEntry entry,
@NotNull Map<ResourceEntry, Set<MyResourceInfo>> result,
int offset) {
final MyResourceInfo info = new MyResourceInfo(entry, offset);
result.put(entry, Collections.singleton(info));
addEntryToMap(info, createTypeMarkerKey(entry.getType()), result);
addEntryToMap(info, createTypeNameMarkerKey(entry.getType(), entry.getName()), result);
}
private static void addEntryToMap(MyResourceInfo info, ResourceEntry marker, Map<ResourceEntry, Set<MyResourceInfo>> result) {
Set<MyResourceInfo> set = result.get(marker);
if (set == null) {
set = new HashSet<ResourceEntry>();
set = new HashSet<MyResourceInfo>();
result.put(marker, set);
}
set.add(entry);
set.add(info);
}
@NotNull
@@ -128,33 +190,35 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
}
};
private final DataExternalizer<Set<ResourceEntry>> myValueExternalizer = new DataExternalizer<Set<ResourceEntry>>() {
private final DataExternalizer<Set<MyResourceInfo>> myValueExternalizer = new DataExternalizer<Set<MyResourceInfo>>() {
@Override
public void save(DataOutput out, Set<ResourceEntry> value) throws IOException {
public void save(DataOutput out, Set<MyResourceInfo> value) throws IOException {
out.writeInt(value.size());
for (ResourceEntry entry : value) {
out.writeUTF(entry.getType());
out.writeUTF(entry.getName());
out.writeUTF(entry.getContext());
for (MyResourceInfo entry : value) {
out.writeUTF(entry.getResourceEntry().getType());
out.writeUTF(entry.getResourceEntry().getName());
out.writeUTF(entry.getResourceEntry().getContext());
out.writeInt(entry.getOffset());
}
}
@Nullable
@Override
public Set<ResourceEntry> read(DataInput in) throws IOException {
public Set<MyResourceInfo> read(DataInput in) throws IOException {
final int size = in.readInt();
if (size == 0) {
return Collections.emptySet();
}
final Set<ResourceEntry> result = new HashSet<ResourceEntry>(size);
final Set<MyResourceInfo> result = new HashSet<MyResourceInfo>(size);
for (int i = 0; i < size; i++) {
final String type = in.readUTF();
final String name = in.readUTF();
final String context = in.readUTF();
result.add(new ResourceEntry(type, name, context));
final int offset = in.readInt();
result.add(new MyResourceInfo(new ResourceEntry(type, name, context), offset));
}
return result;
}
@@ -162,13 +226,13 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
@NotNull
@Override
public ID<ResourceEntry, Set<ResourceEntry>> getName() {
public ID<ResourceEntry, Set<MyResourceInfo>> getName() {
return INDEX_ID;
}
@NotNull
@Override
public DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent> getIndexer() {
public DataIndexer<ResourceEntry, Set<MyResourceInfo>, FileContent> getIndexer() {
return myIndexer;
}
@@ -178,7 +242,7 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
}
@Override
public DataExternalizer<Set<ResourceEntry>> getValueExternalizer() {
public DataExternalizer<Set<MyResourceInfo>> getValueExternalizer() {
return myValueExternalizer;
}
@@ -194,6 +258,53 @@ public class AndroidValueResourcesIndex extends FileBasedIndexExtension<Resource
@Override
public int getVersion() {
return 4;
return 5;
}
public static class MyResourceInfo {
private final ResourceEntry myResourceEntry;
private final int myOffset;
private MyResourceInfo(@NotNull ResourceEntry resourceEntry, int offset) {
myResourceEntry = resourceEntry;
myOffset = offset;
}
@NotNull
public ResourceEntry getResourceEntry() {
return myResourceEntry;
}
public int getOffset() {
return myOffset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyResourceInfo info = (MyResourceInfo)o;
if (myOffset != info.myOffset) {
return false;
}
if (!myResourceEntry.equals(info.myResourceEntry)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = myResourceEntry.hashCode();
result = 31 * result + myOffset;
return result;
}
}
}
@@ -15,7 +15,7 @@
*/
package org.jetbrains.android.dom.converters;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.xml.XmlAttributeValue;
@@ -26,12 +26,13 @@ import com.intellij.util.xml.*;
import com.intellij.util.xml.impl.ConvertContextImpl;
import com.intellij.util.xml.impl.DomCompletionContributor;
import org.jetbrains.android.dom.AndroidDomUtil;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.ResourceValue;
import org.jetbrains.android.dom.wrappers.FileResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.ValueResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.resourceManagers.ResourceManager;
import org.jetbrains.android.resourceManagers.ValueResourceInfo;
import org.jetbrains.android.resourceManagers.ValueResourceInfoImpl;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.annotations.NotNull;
@@ -122,7 +123,7 @@ public class AndroidResourceReference extends PsiReferenceBase.Poly<XmlElement>
collectTargets(myFacet, myResourceValue, elements, files);
final List<ResolveResult> result = new ArrayList<ResolveResult>();
for (PsiFile target : files) {
if (target != null) {
final PsiFile e = new FileResourceElementWrapper(target);
@@ -131,15 +132,15 @@ public class AndroidResourceReference extends PsiReferenceBase.Poly<XmlElement>
}
for (PsiElement target : elements) {
final PsiElement e = target instanceof NavigationItem && target instanceof XmlAttributeValue
/*final PsiElement e = target instanceof NavigationItem && target instanceof XmlAttributeValue
? new ValueResourceElementWrapper((XmlAttributeValue)target)
: target;
result.add(new PsiElementResolveResult(e));
: target;*/
result.add(new PsiElementResolveResult(target));
}
return result.toArray(new ResolveResult[result.size()]);
}
private static void collectTargets(AndroidFacet facet, ResourceValue resValue, List<PsiElement> elements, List<PsiFile> files) {
private void collectTargets(AndroidFacet facet, ResourceValue resValue, List<PsiElement> elements, List<PsiFile> files) {
String resType = resValue.getResourceType();
if (resType == null) {
return;
@@ -148,15 +149,13 @@ public class AndroidResourceReference extends PsiReferenceBase.Poly<XmlElement>
if (manager != null) {
String resName = resValue.getResourceName();
if (resName != null) {
List<ResourceElement> valueResources = manager.findValueResources(resType, resName, false);
for (ResourceElement resource : valueResources) {
elements.add(resource.getName().getXmlAttributeValue());
List<ValueResourceInfoImpl> valueResources = manager.findValueResourceInfos(resType, resName, false);
for (final ValueResourceInfo resource : valueResources) {
elements.add(new LazyValueResourceElementWrapper(resource, myElement));
}
if (resType.equals("id")) {
List<PsiElement> idAttrs = manager.findIdDeclarations(resName);
if (idAttrs != null) {
elements.addAll(idAttrs);
}
elements.addAll(manager.findIdDeclarations(resName));
}
if (elements.size() == 0) {
files.addAll(manager.findResourceFiles(resType, resName, false));
@@ -164,4 +163,32 @@ public class AndroidResourceReference extends PsiReferenceBase.Poly<XmlElement>
}
}
}
@Override
public boolean isReferenceTo(PsiElement element) {
final ResolveResult[] results = multiResolve(false);
final PsiFile psiFile = element.getContainingFile();
final VirtualFile vFile = psiFile != null ? psiFile.getVirtualFile() : null;
for (ResolveResult result : results) {
final PsiElement target = result.getElement();
if (element.getManager().areElementsEquivalent(target, element)) {
return true;
}
if (target instanceof LazyValueResourceElementWrapper && vFile != null) {
final ValueResourceInfo info = ((LazyValueResourceElementWrapper)target).getResourceInfo();
if (info.getContainingFile().equals(vFile)) {
final XmlAttributeValue realTarget = info.computeXmlElement();
if (element.getManager().areElementsEquivalent(realTarget, element)) {
return true;
}
}
}
}
return false;
}
}
@@ -0,0 +1,103 @@
package org.jetbrains.android.dom.wrappers;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiTarget;
import com.intellij.psi.impl.RenameableFakePsiElement;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.android.resourceManagers.ValueResourceInfo;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* @author Eugene.Kudelevsky
*/
public class LazyValueResourceElementWrapper extends RenameableFakePsiElement implements PsiTarget {
private final ValueResourceInfo myResourceInfo;
private final PsiElement myParent;
public LazyValueResourceElementWrapper(@NotNull ValueResourceInfo resourceInfo, @NotNull PsiElement parent) {
super(parent);
myParent = parent;
myResourceInfo = resourceInfo;
}
@Override
public String getName() {
return myResourceInfo.getName();
}
@Nullable
@Override
public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
final XmlAttributeValue element = computeElement();
if (element == null) {
throw new IncorrectOperationException(
"Cannot find resource '" + myResourceInfo.getName() + "' in file " + myResourceInfo.getContainingFile().getPath());
}
return new ValueResourceElementWrapper(element).setName(name);
}
@Override
public ItemPresentation getPresentation() {
return new ItemPresentation() {
@Nullable
public String getPresentableText() {
final String name = myResourceInfo.getName();
final VirtualFile resDir = myResourceInfo.getContainingFile().getParent();
if (resDir == null) {
return name;
}
return name + " (" + resDir.getName() + ')';
}
public String getLocationString() {
return null;
}
public Icon getIcon(boolean open) {
return null;
}
};
}
@Nullable
public XmlAttributeValue computeElement() {
return myResourceInfo.computeXmlElement();
}
@NotNull
@Override
public PsiElement getNavigationElement() {
final XmlAttributeValue element = myResourceInfo.computeXmlElement();
return element != null ? element : myParent;
}
@NotNull
public ValueResourceInfo getResourceInfo() {
return myResourceInfo;
}
@Override
public String getTypeName() {
return "Android Value Resource";
}
@Override
public Icon getIcon() {
return null;
}
@Nullable
public static PsiElement computeLazyElement(PsiElement element) {
if (element instanceof LazyValueResourceElementWrapper) {
element = ((LazyValueResourceElementWrapper)element).computeElement();
}
return element;
}
}
@@ -141,21 +141,21 @@ public class LocalResourceManager extends ResourceManager {
for (ResourceType resourceType : AndroidResourceUtil.ALL_VALUE_RESOURCE_TYPES) {
final ResourceEntry typeMarkerEntry = AndroidValueResourcesIndex.createTypeMarkerKey(resourceType.getName());
for (Set<ResourceEntry> entrySet : index.getValues(AndroidValueResourcesIndex.INDEX_ID, typeMarkerEntry, scope)) {
for (ResourceEntry entry : entrySet) {
final Collection<VirtualFile> files = index.getContainingFiles(AndroidValueResourcesIndex.INDEX_ID, entry, scope);
for (VirtualFile file : files) {
index.processValues(AndroidValueResourcesIndex.INDEX_ID, typeMarkerEntry, null, new FileBasedIndex.ValueProcessor<Set<AndroidValueResourcesIndex.MyResourceInfo>>() {
@Override
public boolean process(VirtualFile file, Set<AndroidValueResourcesIndex.MyResourceInfo> infos) {
for (AndroidValueResourcesIndex.MyResourceInfo info : infos) {
Set<String> resourcesInFile = file2Types.get(file);
if (resourcesInFile == null) {
resourcesInFile = new HashSet<String>();
file2Types.put(file, resourcesInFile);
}
resourcesInFile.add(entry.getType());
resourcesInFile.add(info.getResourceEntry().getType());
}
return true;
}
}
}, scope);
}
final Set<String> result = new HashSet<String>();
@@ -23,11 +23,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
@@ -280,27 +279,28 @@ public abstract class ResourceManager {
if (type == null) {
return Collections.emptyList();
}
final FileBasedIndex index = FileBasedIndex.getInstance();
final ResourceEntry typeMarkerEntry = AndroidValueResourcesIndex.createTypeMarkerKey(resourceType);
final GlobalSearchScope scope = GlobalSearchScope.allScope(myModule.getProject());
final Map<VirtualFile, Set<ResourceEntry>> file2resourceSet = new HashMap<VirtualFile, Set<ResourceEntry>>();
for (Set<ResourceEntry> entrySet : index.getValues(AndroidValueResourcesIndex.INDEX_ID, typeMarkerEntry, scope)) {
for (ResourceEntry entry : entrySet) {
final Collection<VirtualFile> files = index.getContainingFiles(AndroidValueResourcesIndex.INDEX_ID, entry, scope);
for (VirtualFile file : files) {
index.processValues(AndroidValueResourcesIndex.INDEX_ID, typeMarkerEntry, null, new FileBasedIndex.ValueProcessor<Set<AndroidValueResourcesIndex.MyResourceInfo>>() {
@Override
public boolean process(VirtualFile file, Set<AndroidValueResourcesIndex.MyResourceInfo> infos) {
for (AndroidValueResourcesIndex.MyResourceInfo info : infos) {
Set<ResourceEntry> resourcesInFile = file2resourceSet.get(file);
if (resourcesInFile == null) {
resourcesInFile = new HashSet<ResourceEntry>();
file2resourceSet.put(file, resourcesInFile);
}
resourcesInFile.add(entry);
resourcesInFile.add(info.getResourceEntry());
}
return true;
}
}
}, scope);
final List<ResourceEntry> result = new ArrayList<ResourceEntry>();
for (VirtualFile file : getAllValueResourceFiles()) {
@@ -333,43 +333,40 @@ public abstract class ResourceManager {
// searches only declarations such as "@+id/..."
@NotNull
public List<IdResourceInfo> findIdDeclarationInfos(@NotNull final String id) {
public List<PsiElement> findIdDeclarations(@NotNull final String id) {
if (!isResourcePublic(ResourceType.ID.getName(), id)) {
return Collections.emptyList();
}
final List<IdResourceInfo> result = new ArrayList<IdResourceInfo>();
final List<PsiElement> declarations = new ArrayList<PsiElement>();
final Collection<VirtualFile> files =
FileBasedIndex.getInstance().getContainingFiles(AndroidIdIndex.INDEX_ID, id, GlobalSearchScope.allScope(myModule.getProject()));
final Set<VirtualFile> fileSet = new HashSet<VirtualFile>(files);
final PsiManager psiManager = PsiManager.getInstance(myModule.getProject());
for (VirtualFile subdir : getResourceSubdirsToSearchIds()) {
for (VirtualFile file : subdir.getChildren()) {
if (fileSet.contains(file)) {
result.add(new IdResourceInfo(id, file, myModule.getProject()));
final PsiFile psiFile = psiManager.findFile(file);
if (psiFile instanceof XmlFile) {
psiFile.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlAttributeValue(XmlAttributeValue attributeValue) {
if (AndroidResourceUtil.isIdDeclaration(attributeValue)) {
final String idInAttr = AndroidResourceUtil.getResourceNameByReferenceText(attributeValue.getValue());
if (id.equals(idInAttr)) {
declarations.add(attributeValue);
}
}
}
});
}
}
}
}
return result;
}
@NotNull
public List<PsiElement> findIdDeclarations(@NotNull String id) {
final List<IdResourceInfo> infos = findIdDeclarationInfos(id);
if (infos.size() == 0) {
return Collections.emptyList();
}
final List<PsiElement> result = new ArrayList<PsiElement>();
for (IdResourceInfo info : infos) {
final PsiElement element = info.computeXmlElement();
if (element != null) {
result.add(element);
}
}
return result;
return declarations;
}
@NotNull
@@ -451,37 +448,36 @@ public abstract class ResourceManager {
@NotNull
public List<ValueResourceInfoImpl> findValueResourceInfos(@NotNull String resourceType,
@NotNull String resourceName,
boolean distinguishDelimetersInName) {
@NotNull final String resourceName,
final boolean distinguishDelimetersInName) {
final ResourceType type = ResourceType.getEnum(resourceType);
if (type == null) {
if (type == null || !AndroidResourceUtil.VALUE_RESOURCE_TYPES.contains(type)) {
// do not search for styleables and atts here because we don't want to resolve references to it
return Collections.emptyList();
}
new HashMap<VirtualFile, Set<AndroidValueResourcesIndex.MyResourceInfo>>();
final GlobalSearchScope scope = GlobalSearchScope.allScope(myModule.getProject());
final List<Set<ResourceEntry>> values = FileBasedIndex.getInstance()
.getValues(AndroidValueResourcesIndex.INDEX_ID, AndroidValueResourcesIndex.createTypeNameMarkerKey(resourceType, resourceName),
scope);
final Set<VirtualFile> valueResourceFiles = getAllValueResourceFiles();
final List<ValueResourceInfoImpl> result = new ArrayList<ValueResourceInfoImpl>();
final Set<VirtualFile> valueResourceFiles = getAllValueResourceFiles();
for (Set<ResourceEntry> entrySet : values) {
for (ResourceEntry entry : entrySet) {
final String name = entry.getName();
FileBasedIndex.getInstance()
.processValues(AndroidValueResourcesIndex.INDEX_ID, AndroidValueResourcesIndex.createTypeNameMarkerKey(resourceType, resourceName),
null, new FileBasedIndex.ValueProcessor<Set<AndroidValueResourcesIndex.MyResourceInfo>>() {
@Override
public boolean process(VirtualFile file, Set<AndroidValueResourcesIndex.MyResourceInfo> infos) {
for (AndroidValueResourcesIndex.MyResourceInfo info : infos) {
final String name = info.getResourceEntry().getName();
if (AndroidUtils.equal(resourceName, name, distinguishDelimetersInName)) {
final Collection<VirtualFile> files =
FileBasedIndex.getInstance().getContainingFiles(AndroidValueResourcesIndex.INDEX_ID, entry, scope);
for (VirtualFile file : files) {
if (AndroidUtils.equal(resourceName, name, distinguishDelimetersInName)) {
if (valueResourceFiles.contains(file)) {
result.add(new ValueResourceInfoImpl(name, type, file, myModule));
result.add(new ValueResourceInfoImpl(info.getResourceEntry().getName(), type, file, myModule, info.getOffset()));
}
}
}
return true;
}
}
}, scope);
return result;
}
}
@@ -2,49 +2,67 @@ package org.jetbrains.android.resourceManagers;
import com.android.resources.ResourceType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.jetbrains.android.dom.resources.Item;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.Resources;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class ValueResourceInfoImpl extends ValueResourceInfoBase {
private final Module myModule;
private final int myOffset;
ValueResourceInfoImpl(@NotNull String name, @NotNull ResourceType type, @NotNull VirtualFile file, @NotNull Module module) {
ValueResourceInfoImpl(@NotNull String name, @NotNull ResourceType type, @NotNull VirtualFile file, @NotNull Module module, int offset) {
super(name, type, file);
myModule = module;
myOffset = offset;
}
@Override
public XmlAttributeValue computeXmlElement() {
final ResourceElement element = computeDomElement();
return element != null ? element.getName().getXmlAttributeValue() : null;
final ResourceElement resDomElement = computeDomElement();
return resDomElement != null ? resDomElement.getName().getXmlAttributeValue() : null;
}
@Nullable
public ResourceElement computeDomElement() {
final Resources resources = AndroidUtils.loadDomElement(myModule, myFile, Resources.class);
if (resources == null) {
final Project project = myModule.getProject();
final PsiFile file = PsiManager.getInstance(project).findFile(myFile);
if (!(file instanceof XmlFile)) {
return null;
}
final List<ResourceElement> elements = AndroidResourceUtil.getValueResourcesFromElement(myType.getName(), resources);
final XmlTag tag = PsiTreeUtil.findElementOfClassAtOffset(file, myOffset, XmlTag.class, true);
for (ResourceElement element : elements) {
final String name = element.getName().getValue();
if (myName.equals(name)) {
return element;
}
if (tag == null) {
return null;
}
return null;
final DomElement domElement = DomManager.getDomManager(project).getDomElement(tag);
if (!(domElement instanceof ResourceElement)) {
return null;
}
final String resType = domElement instanceof Item
? ((Item)domElement).getType().getStringValue()
: AndroidCommonUtils.getResourceTypeByTagName(tag.getName());
if (!myType.getName().equals(resType)) {
return null;
}
final ResourceElement resDomElement = (ResourceElement)domElement;
final String resName = ((ResourceElement)domElement).getName().getStringValue();
return myName.equals(resName) ? resDomElement : null;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/str1"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/str2"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/str100"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/str1000"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/str2500"
/>
</LinearLayout>
@@ -0,0 +1,11 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@strin<caret>g/str1000"
/>
</LinearLayout>
@@ -12,6 +12,9 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.util.ThrowableRunnable;
import org.jetbrains.android.inspections.CreateFileResourceQuickFix;
import org.jetbrains.android.inspections.CreateValueResourceQuickFix;
@@ -405,26 +408,67 @@ public class AndroidLayoutDomTest extends AndroidDomTest {
toTestCompletion(getTestName(true) + ".xml", getTestName(true) + "_after.xml");
}
/*public void testCustomAttrsPerformance() throws Throwable {
public void testCustomAttrsPerformance() throws Throwable {
myFixture.copyFileToProject("dom/resources/bigfile.xml", "res/values/bigfile.xml");
myFixture.copyFileToProject("dom/resources/bigattrs.xml", "res/values/bigattrs.xml");
myFixture.copyFileToProject("dom/resources/bigattrs.xml", "res/values/bigattrs1.xml");
myFixture.copyFileToProject("dom/resources/bigattrs.xml", "res/values/bigattrs2.xml");
myFixture.copyFileToProject("dom/resources/bigattrs.xml", "res/values/bigattrs3.xml");
String path = copyFileToProject("bigfile.xml");
VirtualFile f = myFixture.findFileInTempDir(path);
VirtualFile f = copyFileToProject("bigfile.xml");
myFixture.configureFromExistingVirtualFile(f);
IdeaTestUtil.assertTiming("", 800, new Runnable() {
PlatformTestUtil.startPerformanceTest("android custom attrs highlighting is slow", 800, new ThrowableRunnable() {
@Override
public void run() {
try {
myFixture.doHighlighting();
}
catch (Exception e) {
public void run() throws Throwable {
myFixture.doHighlighting();
}
}).attempts(2).cpuBound().usesAllCPUCores().assertTiming();
}
public void testResourceHighlightingPerformance() throws Throwable {
doCopyManyStrings();
final VirtualFile f = copyFileToProject(getTestName(true) + ".xml");
myFixture.configureFromExistingVirtualFile(f);
PlatformTestUtil.startPerformanceTest("android highlighting is slow", 400, new ThrowableRunnable() {
@Override
public void run() throws Throwable {
myFixture.doHighlighting();
}
}).attempts(2).cpuBound().usesAllCPUCores().assertTiming();
}
public void testResourceNavigationPerformance() throws Throwable {
doCopyManyStrings();
final VirtualFile f = copyFileToProject(getTestName(true) + ".xml");
myFixture.configureFromExistingVirtualFile(f);
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
final List<PsiElement> navElements = new ArrayList<PsiElement>();
// warm
myFixture.doHighlighting();
PlatformTestUtil.startPerformanceTest("android highlighting is slow", 7000, new ThrowableRunnable() {
@SuppressWarnings("ConstantConditions")
@Override
public void run() throws Throwable {
final PsiReference reference = TargetElementUtilBase.findReference(myFixture.getEditor(), myFixture.getCaretOffset());
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
for (ResolveResult result : results) {
final PsiElement navElement = result.getElement().getNavigationElement();
assertInstanceOf(navElement, XmlAttributeValue.class);
navElements.add(navElement);
}
}
});
}*/
}).attempts(1).cpuBound().usesAllCPUCores().assertTiming();
assertEquals(31, navElements.size());
}
private void doCopyManyStrings() {
myFixture.copyFileToProject(testFolder + "/many_strings.xml", "res/values/strings.xml");
for (int i = 0; i < 30; i++) {
myFixture.copyFileToProject(testFolder + "/many_strings.xml", "res/values-" + Integer.toString(i) + "/strings.xml");
}
}
public void testViewClassReference() throws Throwable {
VirtualFile file = myFixture.copyFileToProject(testFolder + "/vcr.xml", getPathToCopy("vcr.xml"));
@@ -30,9 +30,9 @@ import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.refactoring.actions.InlineAction;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
import org.jetbrains.android.inspections.CreateValueResourceQuickFix;
import java.util.ArrayList;
@@ -252,7 +252,8 @@ public class AndroidValueResourcesTest extends AndroidDomTest {
PsiReference rootReference = psiFile.findReferenceAt(rootOffset);
assertNotNull(rootReference);
PsiElement element = rootReference.resolve();
assertTrue("Must be PsiClass reference", element instanceof XmlAttributeValue);
assertInstanceOf(element, LazyValueResourceElementWrapper.class);
assertNotNull(((LazyValueResourceElementWrapper)element).computeElement());
}
// see getPathToCopy()