blob: 378846a5b9889b44b55fdcd5e16acccffdaa6d33 [file] [log] [blame]
/*
* 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.resourceManagers;
import com.android.resources.ResourceType;
import com.android.tools.idea.AndroidPsiUtils;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
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.*;
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;
import com.intellij.util.indexing.FileBasedIndex;
import com.intellij.util.xml.DomElement;
import org.jetbrains.android.AndroidIdIndex;
import org.jetbrains.android.AndroidValueResourcesIndex;
import org.jetbrains.android.dom.attrs.AttributeDefinitions;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.Resources;
import org.jetbrains.android.dom.wrappers.FileResourceElementWrapper;
import org.jetbrains.android.dom.wrappers.LazyValueResourceElementWrapper;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.android.util.ResourceEntry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static java.util.Collections.addAll;
/**
* @author coyote
*/
public abstract class ResourceManager {
protected final Project myProject;
protected ResourceManager(@NotNull Project project) {
myProject = project;
}
/** Returns all the resource directories for this module <b>and all of its module dependencies</b> */
@NotNull
public abstract VirtualFile[] getAllResourceDirs();
/** Returns all the resource directories for this module only */
@NotNull
public abstract List<VirtualFile> getResourceDirs();
/** Returns true if the given directory is a resource directory in this module */
public abstract boolean isResourceDir(@NotNull VirtualFile dir);
public boolean processFileResources(@Nullable String resourceType, @NotNull FileResourceProcessor processor) {
return processFileResources(resourceType, processor, true);
}
public boolean processFileResources(@Nullable String resourceType, @NotNull FileResourceProcessor processor,
boolean withDependencies) {
return processFileResources(resourceType, processor, withDependencies, true);
}
public boolean processFileResources(@Nullable String resourceType, @NotNull FileResourceProcessor processor,
boolean withDependencies, boolean publicOnly) {
final VirtualFile[] resDirs;
if (withDependencies) {
resDirs = getAllResourceDirs();
} else {
List<VirtualFile> resourceDirs = getResourceDirs();
resDirs = resourceDirs.toArray(new VirtualFile[resourceDirs.size()]);
}
for (VirtualFile resSubdir : AndroidResourceUtil.getResourceSubdirs(resourceType, resDirs)) {
final String resType = AndroidCommonUtils.getResourceTypeByDirName(resSubdir.getName());
if (resType != null) {
assert resourceType == null || resourceType.equals(resType);
for (VirtualFile resFile : resSubdir.getChildren()) {
final String resName = AndroidCommonUtils.getResourceName(resType, resFile.getName());
if (!resFile.isDirectory() && (!publicOnly || isResourcePublic(resType, resName))) {
if (!processor.process(resFile, resName, resType)) {
return false;
}
}
}
}
}
return true;
}
@NotNull
public VirtualFile[] getResourceOverlayDirs() {
return VirtualFile.EMPTY_ARRAY;
}
protected boolean isResourcePublic(@NotNull String type, @NotNull String name) {
return true;
}
@NotNull
public List<VirtualFile> getResourceSubdirs(@Nullable String resourceType) {
return AndroidResourceUtil.getResourceSubdirs(resourceType, getAllResourceDirs());
}
@NotNull
public List<PsiFile> findResourceFiles(@NotNull final String resType,
@Nullable final String resName,
final boolean distinguishDelimetersInName,
@NotNull String... extensions) {
return findResourceFiles(resType, resName, distinguishDelimetersInName, true, extensions);
}
@NotNull
public List<PsiFile> findResourceFiles(@NotNull final String resType1,
@Nullable final String resName1,
final boolean distinguishDelimetersInName,
final boolean withDependencies,
@NotNull final String... extensions) {
final List<PsiFile> result = new ArrayList<PsiFile>();
final Set<String> extensionSet = new HashSet<String>();
addAll(extensionSet, extensions);
processFileResources(resType1, new FileResourceProcessor() {
@Override
public boolean process(@NotNull final VirtualFile resFile, @NotNull String resName, @NotNull String resFolderType) {
final String extension = resFile.getExtension();
if ((extensions.length == 0 || extensionSet.contains(extension)) &&
(resName1 == null || AndroidUtils.equal(resName1, resName, distinguishDelimetersInName))) {
final PsiFile file = AndroidPsiUtils.getPsiFileSafely(myProject, resFile);
if (file != null) {
result.add(file);
}
}
return true;
}
}, withDependencies);
return result;
}
public List<PsiFile> findResourceFiles(@NotNull String resType, @NotNull String resName, @NotNull String... extensions) {
return findResourceFiles(resType, resName, true, extensions);
}
@NotNull
public List<PsiFile> findResourceFiles(@NotNull String resType) {
return findResourceFiles(resType, null, true);
}
protected List<Pair<Resources, VirtualFile>> getResourceElements(@Nullable Set<VirtualFile> files) {
return getRootDomElements(Resources.class, files);
}
private <T extends DomElement> List<Pair<T, VirtualFile>> getRootDomElements(@NotNull Class<T> elementType,
@Nullable Set<VirtualFile> files) {
final List<Pair<T, VirtualFile>> result = new ArrayList<Pair<T, VirtualFile>>();
for (VirtualFile file : getAllValueResourceFiles()) {
if ((files == null || files.contains(file)) && file.isValid()) {
final T element = AndroidUtils.loadDomElement(myProject, file, elementType);
if (element != null) {
result.add(Pair.create(element, file));
}
}
}
return result;
}
@NotNull
protected Set<VirtualFile> getAllValueResourceFiles() {
final Set<VirtualFile> files = new HashSet<VirtualFile>();
for (VirtualFile valueResourceDir : getResourceSubdirs("values")) {
for (VirtualFile valueResourceFile : valueResourceDir.getChildren()) {
if (!valueResourceFile.isDirectory() && valueResourceFile.getFileType().equals(StdFileTypes.XML)) {
files.add(valueResourceFile);
}
}
}
return files;
}
protected List<ResourceElement> getValueResources(@NotNull final String resourceType, @Nullable Set<VirtualFile> files) {
final List<ResourceElement> result = new ArrayList<ResourceElement>();
List<Pair<Resources, VirtualFile>> resourceFiles = getResourceElements(files);
for (final Pair<Resources, VirtualFile> pair : resourceFiles) {
final Resources resources = pair.getFirst();
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (!resources.isValid() || myProject.isDisposed()) {
return;
}
final List<ResourceElement> valueResources = AndroidResourceUtil.getValueResourcesFromElement(resourceType, resources);
for (ResourceElement valueResource : valueResources) {
final String resName = valueResource.getName().getValue();
if (resName != null && isResourcePublic(resourceType, resName)) {
result.add(valueResource);
}
}
}
});
}
return result;
}
@Nullable
public String getValueResourceType(@NotNull XmlTag tag) {
String fileResType = getFileResourceType(tag.getContainingFile());
if ("values".equals(fileResType)) {
return tag.getName();
}
return null;
}
@Nullable
public String getFileResourceType(@NotNull final PsiFile file) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Nullable
@Override
public String compute() {
PsiDirectory dir = file.getContainingDirectory();
if (dir == null) return null;
PsiDirectory possibleResDir = dir.getParentDirectory();
if (possibleResDir == null || !isResourceDir(possibleResDir.getVirtualFile())) {
return null;
}
String type = AndroidCommonUtils.getResourceTypeByDirName(dir.getName());
if (type == null) return null;
return type;
}
});
}
@NotNull
public Set<String> getFileResourcesNames(@NotNull final String resourceType) {
final Set<String> result = new HashSet<String>();
processFileResources(resourceType, new FileResourceProcessor() {
@Override
public boolean process(@NotNull VirtualFile resFile, @NotNull String resName, @NotNull String resFolderType) {
result.add(resName);
return true;
}
});
return result;
}
@NotNull
public Collection<String> getValueResourceNames(@NotNull final String resourceType) {
final Set<String> result = new HashSet<String>();
final boolean attr = ResourceType.ATTR.getName().equals(resourceType);
for (ResourceEntry entry : getValueResourceEntries(resourceType)) {
final String name = entry.getName();
if (!attr || !name.startsWith("android:")) {
result.add(name);
}
}
return result;
}
@NotNull
public Collection<ResourceEntry> getValueResourceEntries(@NotNull final String resourceType) {
final ResourceType type = ResourceType.getEnum(resourceType);
if (type == null) {
return Collections.emptyList();
}
final FileBasedIndex index = FileBasedIndex.getInstance();
final ResourceEntry typeMarkerEntry = AndroidValueResourcesIndex.createTypeMarkerKey(resourceType);
final GlobalSearchScope scope = GlobalSearchScope.allScope(myProject);
final Map<VirtualFile, Set<ResourceEntry>> file2resourceSet = new HashMap<VirtualFile, Set<ResourceEntry>>();
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(info.getResourceEntry());
}
return true;
}
}, scope);
final List<ResourceEntry> result = new ArrayList<ResourceEntry>();
for (VirtualFile file : getAllValueResourceFiles()) {
final Set<ResourceEntry> entries = file2resourceSet.get(file);
if (entries != null) {
for (ResourceEntry entry : entries) {
if (isResourcePublic(entry.getType(), entry.getName())) {
result.add(entry);
}
}
}
}
return result;
}
@NotNull
public Collection<String> getResourceNames(@NotNull String type) {
final Set<String> result = new HashSet<String>();
result.addAll(getValueResourceNames(type));
result.addAll(getFileResourcesNames(type));
if (type.equals(ResourceType.ID.getName())) {
result.addAll(getIds(true));
}
return result;
}
@Nullable
public abstract AttributeDefinitions getAttributeDefinitions();
// searches only declarations such as "@+id/..."
@NotNull
public List<XmlAttributeValue> findIdDeclarations(@NotNull final String id) {
if (!isResourcePublic(ResourceType.ID.getName(), id)) {
return Collections.emptyList();
}
final List<XmlAttributeValue> declarations = new ArrayList<XmlAttributeValue>();
final Collection<VirtualFile> files =
FileBasedIndex.getInstance().getContainingFiles(AndroidIdIndex.INDEX_ID, "+" + id, GlobalSearchScope.allScope(myProject));
final Set<VirtualFile> fileSet = new HashSet<VirtualFile>(files);
final PsiManager psiManager = PsiManager.getInstance(myProject);
for (VirtualFile subdir : getResourceSubdirsToSearchIds()) {
for (VirtualFile file : subdir.getChildren()) {
if (fileSet.contains(file)) {
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 declarations;
}
@NotNull
public Collection<String> getIds(boolean declarationsOnly) {
if (myProject.isDisposed()) {
return Collections.emptyList();
}
final GlobalSearchScope scope = GlobalSearchScope.allScope(myProject);
final FileBasedIndex index = FileBasedIndex.getInstance();
final Map<VirtualFile, Set<String>> file2idEntries = new HashMap<VirtualFile, Set<String>>();
index.processValues(AndroidIdIndex.INDEX_ID, AndroidIdIndex.MARKER, null, new FileBasedIndex.ValueProcessor<Set<String>>() {
@Override
public boolean process(VirtualFile file, Set<String> value) {
file2idEntries.put(file, value);
return true;
}
}, scope);
final Set<String> result = new HashSet<String>();
for (VirtualFile resSubdir : getResourceSubdirsToSearchIds()) {
for (VirtualFile resFile : resSubdir.getChildren()) {
final Set<String> idEntries = file2idEntries.get(resFile);
if (idEntries != null) {
for (String idEntry : idEntries) {
if (idEntry.startsWith("+")) {
idEntry = idEntry.substring(1);
}
else if (declarationsOnly) {
continue;
}
if (isResourcePublic(ResourceType.ID.getName(), idEntry)) {
result.add(idEntry);
}
}
}
}
}
return result;
}
@NotNull
public List<VirtualFile> getResourceSubdirsToSearchIds() {
final List<VirtualFile> resSubdirs = new ArrayList<VirtualFile>();
for (ResourceType type : AndroidCommonUtils.ID_PROVIDING_RESOURCE_TYPES) {
resSubdirs.addAll(getResourceSubdirs(type.getName()));
}
return resSubdirs;
}
public List<ResourceElement> findValueResources(@NotNull String resType, @NotNull String resName) {
return findValueResources(resType, resName, true);
}
// not recommended to use, because it is too slow
@NotNull
public List<ResourceElement> findValueResources(@NotNull String resourceType,
@NotNull String resourceName,
boolean distinguishDelimitersInName) {
final List<ValueResourceInfoImpl> resources = findValueResourceInfos(resourceType, resourceName, distinguishDelimitersInName, false);
final List<ResourceElement> result = new ArrayList<ResourceElement>();
for (ValueResourceInfoImpl resource : resources) {
final ResourceElement domElement = resource.computeDomElement();
if (domElement != null) {
result.add(domElement);
}
}
return result;
}
public void collectLazyResourceElements(@NotNull String resType,
@NotNull String resName,
boolean withAttrs,
@NotNull PsiElement context,
@NotNull Collection<PsiElement> elements) {
List<ValueResourceInfoImpl> valueResources = findValueResourceInfos(resType, resName, false, withAttrs);
for (final ValueResourceInfo resource : valueResources) {
elements.add(new LazyValueResourceElementWrapper(resource, context));
}
if (resType.equals("id")) {
elements.addAll(findIdDeclarations(resName));
}
if (elements.size() == 0) {
for (PsiFile file : findResourceFiles(resType, resName, false)) {
elements.add(new FileResourceElementWrapper(file));
}
}
}
@NotNull
public List<ValueResourceInfoImpl> findValueResourceInfos(@NotNull String resourceType,
@NotNull final String resourceName,
final boolean distinguishDelimetersInName,
boolean searchAttrs) {
final ResourceType type = resourceType.startsWith("+") ? ResourceType.ID : ResourceType.getEnum(resourceType);
if (type == null ||
!AndroidResourceUtil.VALUE_RESOURCE_TYPES.contains(type) &&
(type != ResourceType.ATTR || !searchAttrs)) {
return Collections.emptyList();
}
final GlobalSearchScope scope = GlobalSearchScope.allScope(myProject);
final List<ValueResourceInfoImpl> result = new ArrayList<ValueResourceInfoImpl>();
final Set<VirtualFile> valueResourceFiles = getAllValueResourceFiles();
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)) {
if (valueResourceFiles.contains(file)) {
result.add(new ValueResourceInfoImpl(info.getResourceEntry().getName(), type, file, myProject, info.getOffset()));
}
}
}
return true;
}
}, scope);
return result;
}
}