| /* |
| * 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; |
| } |
| } |