/* * 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.util; import com.android.SdkConstants; import com.android.ide.common.res2.ValueXmlHelper; import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.tools.idea.rendering.ResourceHelper; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.ide.actions.CreateElementActionBase; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.FileTemplateUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModulePackageIndex; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import org.jetbrains.android.AndroidFileTemplateProvider; import org.jetbrains.android.augment.AndroidPsiElementFinder; import org.jetbrains.android.dom.manifest.Manifest; 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.dom.wrappers.LazyValueResourceElementWrapper; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; import static com.android.SdkConstants.*; import static com.android.resources.ResourceType.ATTR; import static com.android.resources.ResourceType.STYLEABLE; /** * @author Eugene.Kudelevsky */ public class AndroidResourceUtil { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.util.AndroidResourceUtil"); public static final Set<ResourceType> VALUE_RESOURCE_TYPES = EnumSet.of(ResourceType.DRAWABLE, ResourceType.COLOR, ResourceType.DIMEN, ResourceType.STRING, ResourceType.STYLE, ResourceType.ARRAY, ResourceType.PLURALS, ResourceType.ID, ResourceType.BOOL, ResourceType.INTEGER, ResourceType.FRACTION, // For aliases only ResourceType.LAYOUT); public static final Set<ResourceType> ALL_VALUE_RESOURCE_TYPES = EnumSet.noneOf(ResourceType.class); public static final Set<ResourceType> REFERRABLE_RESOURCE_TYPES = EnumSet.noneOf(ResourceType.class); public static final Set<ResourceType> XML_FILE_RESOURCE_TYPES = EnumSet.of(ResourceType.ANIM, ResourceType.ANIMATOR, ResourceType.INTERPOLATOR, ResourceType.LAYOUT, ResourceType.MENU, ResourceType.XML, ResourceType.COLOR, ResourceType.DRAWABLE); static final String ROOT_TAG_PROPERTY = "ROOT_TAG"; static final String LAYOUT_WIDTH_PROPERTY = "LAYOUT_WIDTH"; static final String LAYOUT_HEIGHT_PROPERTY = "LAYOUT_HEIGHT"; /** * Comparator which orders {@link com.intellij.psi.PsiElement} items into a priority order most suitable for presentation * to the user; for example, it prefers base resource folders such as {@code values/} over resource * folders such as {@code values-en-rUS} */ public static final Comparator<PsiElement> RESOURCE_ELEMENT_COMPARATOR = new Comparator<PsiElement>() { @Override public int compare(PsiElement e1, PsiElement e2) { if (e1 instanceof LazyValueResourceElementWrapper && e2 instanceof LazyValueResourceElementWrapper) { return ((LazyValueResourceElementWrapper)e1).compareTo((LazyValueResourceElementWrapper)e2); } PsiFile file1 = e1.getContainingFile(); PsiFile file2 = e2.getContainingFile(); int delta = compareResourceFiles(file1, file2); if (delta != 0) { return delta; } return e1.getTextOffset() - e2.getTextOffset(); } }; private AndroidResourceUtil() { } @NotNull public static String normalizeXmlResourceValue(@NotNull String value) { return ValueXmlHelper.escapeResourceString(value, false); } static { REFERRABLE_RESOURCE_TYPES.addAll(Arrays.asList(ResourceType.values())); REFERRABLE_RESOURCE_TYPES.remove(ATTR); REFERRABLE_RESOURCE_TYPES.remove(STYLEABLE); ALL_VALUE_RESOURCE_TYPES.addAll(VALUE_RESOURCE_TYPES); ALL_VALUE_RESOURCE_TYPES.add(ATTR); ALL_VALUE_RESOURCE_TYPES.add(STYLEABLE); } @NotNull public static PsiField[] findResourceFields(@NotNull AndroidFacet facet, @NotNull String resClassName, @NotNull String resourceName, boolean onlyInOwnPackages) { resourceName = getRJavaFieldName(resourceName); final List<PsiJavaFile> rClassFiles = findRJavaFiles(facet, onlyInOwnPackages); final List<PsiField> result = new ArrayList<PsiField>(); for (PsiJavaFile rClassFile : rClassFiles) { if (rClassFile == null) { continue; } final PsiClass rClass = findClass(rClassFile.getClasses(), AndroidUtils.R_CLASS_NAME); if (rClass != null) { final PsiClass resourceTypeClass = findClass(rClass.getInnerClasses(), resClassName); if (resourceTypeClass != null) { final PsiField field = resourceTypeClass.findFieldByName(resourceName, false); if (field != null) { result.add(field); } } } } return result.toArray(new PsiField[result.size()]); } /** * Like {@link #findResourceFields(org.jetbrains.android.facet.AndroidFacet, String, String, boolean)} but * can match than more than a single field name */ @NotNull public static PsiField[] findResourceFields(@NotNull AndroidFacet facet, @NotNull String resClassName, @NotNull Collection<String> resourceNames, boolean onlyInOwnPackages) { final List<PsiJavaFile> rClassFiles = findRJavaFiles(facet, onlyInOwnPackages); final List<PsiField> result = new ArrayList<PsiField>(); for (PsiJavaFile rClassFile : rClassFiles) { if (rClassFile == null) { continue; } final PsiClass rClass = findClass(rClassFile.getClasses(), AndroidUtils.R_CLASS_NAME); if (rClass != null) { final PsiClass resourceTypeClass = findClass(rClass.getInnerClasses(), resClassName); if (resourceTypeClass != null) { for (String resourceName : resourceNames) { String fieldName = getRJavaFieldName(resourceName); final PsiField field = resourceTypeClass.findFieldByName(fieldName, false); if (field != null) { result.add(field); } } } } } return result.toArray(new PsiField[result.size()]); } @NotNull private static List<PsiJavaFile> findRJavaFiles(@NotNull AndroidFacet facet, boolean onlyInOwnPackages) { final Module module = facet.getModule(); final Project project = module.getProject(); final Manifest manifest = facet.getManifest(); if (manifest == null) { return Collections.emptyList(); } final Set<PsiDirectory> dirs = new HashSet<PsiDirectory>(); collectDirsForPackage(module, project, null, dirs, new HashSet<Module>(), onlyInOwnPackages); final List<PsiJavaFile> rJavaFiles = new ArrayList<PsiJavaFile>(); for (PsiDirectory dir : dirs) { final VirtualFile file = dir.getVirtualFile().findChild(AndroidCommonUtils.R_JAVA_FILENAME); if (file != null) { final PsiFile psiFile = PsiManager.getInstance(project).findFile(file); if (psiFile instanceof PsiJavaFile) { rJavaFiles.add((PsiJavaFile)psiFile); } } } return rJavaFiles; } private static void collectDirsForPackage(Module module, final Project project, @Nullable String packageName, final Set<PsiDirectory> dirs, Set<Module> visitedModules, boolean onlyInOwnPackages) { if (!visitedModules.add(module)) { return; } if (packageName != null) { ModulePackageIndex.getInstance(module).getDirsByPackageName(packageName, false).forEach(new Processor<VirtualFile>() { @Override public boolean process(final VirtualFile directory) { final PsiDirectory psiDir = PsiManager.getInstance(project).findDirectory(directory); if (psiDir != null) { dirs.add(psiDir); } return true; } }); } final AndroidFacet ownFacet = AndroidFacet.getInstance(module); String ownPackageName = null; if (ownFacet != null) { final Manifest ownManifest = ownFacet.getManifest(); ownPackageName = ownManifest != null ? ownManifest.getPackage().getValue() : null; if (ownPackageName != null && !ownPackageName.equals(packageName)) { ModulePackageIndex.getInstance(module).getDirsByPackageName(ownPackageName, false).forEach(new Processor<VirtualFile>() { @Override public boolean process(final VirtualFile directory) { final PsiDirectory psiDir = PsiManager.getInstance(project).findDirectory(directory); if (psiDir != null) { dirs.add(psiDir); } return true; } }); } } for (Module otherModule : ModuleManager.getInstance(project).getModules()) { if (ModuleRootManager.getInstance(otherModule).isDependsOn(module)) { collectDirsForPackage(otherModule, project, packageName != null || onlyInOwnPackages ? packageName : ownPackageName, dirs, visitedModules, onlyInOwnPackages); } } } @Nullable private static PsiClass findClass(@NotNull PsiClass[] classes, @NotNull String name) { for (PsiClass c : classes) { if (name.equals(c.getName())) { return c; } } return null; } @NotNull public static PsiField[] findResourceFieldsForFileResource(@NotNull PsiFile file, boolean onlyInOwnPackages) { final AndroidFacet facet = AndroidFacet.getInstance(file); if (facet == null) { return PsiField.EMPTY_ARRAY; } final String resourceType = facet.getLocalResourceManager().getFileResourceType(file); if (resourceType == null) { return PsiField.EMPTY_ARRAY; } final String resourceName = AndroidCommonUtils.getResourceName(resourceType, file.getName()); return findResourceFields(facet, resourceType, resourceName, onlyInOwnPackages); } @NotNull public static PsiField[] findResourceFieldsForValueResource(XmlTag tag, boolean onlyInOwnPackages) { final AndroidFacet facet = AndroidFacet.getInstance(tag); if (facet == null) { return PsiField.EMPTY_ARRAY; } ResourceFolderType fileResType = ResourceHelper.getFolderType(tag.getContainingFile()); final String resourceType = fileResType == ResourceFolderType.VALUES ? getResourceTypeByValueResourceTag(tag) : null; if (resourceType == null) { return PsiField.EMPTY_ARRAY; } String name = tag.getAttributeValue(ATTR_NAME); if (name == null) { return PsiField.EMPTY_ARRAY; } return findResourceFields(facet, resourceType, name, onlyInOwnPackages); } @NotNull public static PsiField[] findStyleableAttributeFields(XmlTag tag, boolean onlyInOwnPackages) { String tagName = tag.getName(); if (TAG_DECLARE_STYLEABLE.equals(tagName)) { String styleableName = tag.getAttributeValue(ATTR_NAME); if (styleableName == null) { return PsiField.EMPTY_ARRAY; } AndroidFacet facet = AndroidFacet.getInstance(tag); if (facet == null) { return PsiField.EMPTY_ARRAY; } Set<String> names = Sets.newHashSet(); for (XmlTag attr : tag.getSubTags()) { if (TAG_ATTR.equals(attr.getName())) { String attrName = attr.getAttributeValue(ATTR_NAME); if (attrName != null) { names.add(styleableName + '_' + attrName); } } } if (!names.isEmpty()) { return findResourceFields(facet, STYLEABLE.getName(), names, onlyInOwnPackages); } } else if (TAG_ATTR.equals(tagName)) { XmlTag parentTag = tag.getParentTag(); if (parentTag != null && TAG_DECLARE_STYLEABLE.equals(parentTag.getName())) { String styleName = parentTag.getAttributeValue(ATTR_NAME); String attributeName = tag.getAttributeValue(ATTR_NAME); AndroidFacet facet = AndroidFacet.getInstance(tag); if (facet != null && styleName != null && attributeName != null) { return findResourceFields(facet, STYLEABLE.getName(), styleName + '_' + attributeName, onlyInOwnPackages); } } } return PsiField.EMPTY_ARRAY; } @NotNull public static String getRJavaFieldName(@NotNull String resourceName) { if (resourceName.indexOf('.') == -1) { return resourceName; } final String[] identifiers = resourceName.split("\\."); final StringBuilder result = new StringBuilder(resourceName.length()); for (int i = 0, n = identifiers.length; i < n; i++) { result.append(identifiers[i]); if (i < n - 1) { result.append('_'); } } return result.toString(); } public static boolean isCorrectAndroidResourceName(@NotNull String resourceName) { // TODO: No, we need to check per resource folder type here. There is a validator for this! if (resourceName.length() == 0) { return false; } if (resourceName.startsWith(".") || resourceName.endsWith(".")) { return false; } final String[] identifiers = resourceName.split("\\."); for (String identifier : identifiers) { if (!StringUtil.isJavaIdentifier(identifier)) { return false; } } return true; } @Nullable public static String getResourceTypeByValueResourceTag(@NotNull XmlTag tag) { String resClassName = tag.getName(); resClassName = resClassName.equals("item") ? tag.getAttributeValue("type", null) : AndroidCommonUtils.getResourceTypeByTagName(resClassName); if (resClassName != null) { final String resourceName = tag.getAttributeValue("name"); return resourceName != null ? resClassName : null; } return null; } @Nullable public static ResourceType getResourceForResourceTag(@NotNull XmlTag tag) { String typeName = getResourceTypeByValueResourceTag(tag); if (typeName != null) { return ResourceType.getEnum(typeName); } return null; } @Nullable public static String getResourceClassName(@NotNull PsiField field) { final PsiClass resourceClass = field.getContainingClass(); if (resourceClass != null) { final PsiClass parentClass = resourceClass.getContainingClass(); if (parentClass != null && AndroidUtils.R_CLASS_NAME.equals(parentClass.getName()) && parentClass.getContainingClass() == null) { return resourceClass.getName(); } } return null; } // result contains XmlAttributeValue or PsiFile @NotNull public static List<PsiElement> findResourcesByField(@NotNull PsiField field) { final AndroidFacet facet = AndroidFacet.getInstance(field); return facet != null ? facet.getLocalResourceManager().findResourcesByField(field) : Collections.<PsiElement>emptyList(); } public static boolean isResourceField(@NotNull PsiField field) { PsiClass c = field.getContainingClass(); if (c == null) return false; c = c.getContainingClass(); if (c != null && AndroidUtils.R_CLASS_NAME.equals(c.getName())) { AndroidFacet facet = AndroidFacet.getInstance(field); if (facet != null) { PsiFile file = c.getContainingFile(); if (file != null && isRJavaFile(facet, file)) { return true; } } } return false; } @NotNull public static PsiField[] findIdFields(@NotNull XmlAttributeValue value) { if (value.getParent() instanceof XmlAttribute) { return findIdFields((XmlAttribute)value.getParent()); } return PsiField.EMPTY_ARRAY; } public static boolean isIdDeclaration(@Nullable String attrValue) { return attrValue != null && attrValue.startsWith(SdkConstants.NEW_ID_PREFIX); } public static boolean isIdReference(@Nullable String attrValue) { return attrValue != null && attrValue.startsWith(SdkConstants.ID_PREFIX); } public static boolean isIdDeclaration(@NotNull XmlAttributeValue value) { return isIdDeclaration(value.getValue()); } @NotNull public static PsiField[] findIdFields(@NotNull XmlAttribute attribute) { final XmlAttributeValue value = attribute.getValueElement(); if (value != null && isIdDeclaration(value)) { final String id = getResourceNameByReferenceText(attribute.getValue()); if (id != null) { final AndroidFacet facet = AndroidFacet.getInstance(attribute); if (facet != null) { return findResourceFields(facet, ResourceType.ID.getName(), id, false); } } } return PsiField.EMPTY_ARRAY; } @Nullable public static String getResourceNameByReferenceText(@NotNull String text) { int i = text.indexOf('/'); if (i < text.length() - 1) { return text.substring(i + 1, text.length()); } return null; } @NotNull public static ResourceElement addValueResource(@NotNull final ResourceType resType, @NotNull final Resources resources) { switch (resType) { case STRING: return resources.addString(); case PLURALS: return resources.addPlurals(); case DIMEN: return resources.addDimen(); case COLOR: return resources.addColor(); case DRAWABLE: return resources.addDrawable(); case STYLE: return resources.addStyle(); case ARRAY: // todo: choose among string-array, integer-array and array return resources.addStringArray(); case INTEGER: return resources.addInteger(); case FRACTION: return resources.addFraction(); case BOOL: return resources.addBool(); case ID: final Item item = resources.addItem(); item.getType().setValue(ResourceType.ID.getName()); return item; case ATTR: return resources.addAttr(); case STYLEABLE: return resources.addDeclareStyleable(); default: throw new IllegalArgumentException("Incorrect resource type"); } } @NotNull public static List<VirtualFile> getResourceSubdirs(@Nullable String resourceType, @NotNull VirtualFile[] resourceDirs) { if (resourceType != null && ResourceFolderType.getTypeByName(resourceType) == null) { return Collections.emptyList(); } final List<VirtualFile> dirs = new ArrayList<VirtualFile>(); for (VirtualFile resourcesDir : resourceDirs) { if (resourcesDir == null) { return dirs; } if (resourceType == null) { ContainerUtil.addAll(dirs, resourcesDir.getChildren()); } else { for (VirtualFile child : resourcesDir.getChildren()) { String type = AndroidCommonUtils.getResourceTypeByDirName(child.getName()); if (resourceType.equals(type)) dirs.add(child); } } } return dirs; } @Nullable public static String getDefaultResourceFileName(@NotNull ResourceType type) { if (ResourceType.PLURALS == type || ResourceType.STRING == type) { return "strings.xml"; } if (VALUE_RESOURCE_TYPES.contains(type)) { if (type == ResourceType.LAYOUT // Lots of unit tests assume drawable aliases are written in "drawables.xml" but going // forward lets combine both layouts and drawables in refs.xml as is done in the templates: || type == ResourceType.DRAWABLE && !ApplicationManager.getApplication().isUnitTestMode()) { return "refs.xml"; } return type.getName() + "s.xml"; } if (ATTR == type || STYLEABLE == type) { return "attrs.xml"; } return null; } @NotNull public static List<ResourceElement> getValueResourcesFromElement(@NotNull String resourceType, @NotNull Resources resources) { final List<ResourceElement> result = new ArrayList<ResourceElement>(); if (resourceType.equals(ResourceType.STRING.getName())) { result.addAll(resources.getStrings()); } else if (resourceType.equals(ResourceType.PLURALS.getName())) { result.addAll(resources.getPluralss()); } else if (resourceType.equals(ResourceType.DRAWABLE.getName())) { result.addAll(resources.getDrawables()); } else if (resourceType.equals(ResourceType.COLOR.getName())) { result.addAll(resources.getColors()); } else if (resourceType.equals(ResourceType.DIMEN.getName())) { result.addAll(resources.getDimens()); } else if (resourceType.equals(ResourceType.STYLE.getName())) { result.addAll(resources.getStyles()); } else if (resourceType.equals(ResourceType.ARRAY.getName())) { result.addAll(resources.getStringArrays()); result.addAll(resources.getIntegerArrays()); result.addAll(resources.getArrays()); } else if (resourceType.equals(ResourceType.INTEGER.getName())) { result.addAll(resources.getIntegers()); } else if (resourceType.equals(ResourceType.FRACTION.getName())) { result.addAll(resources.getFractions()); } else if (resourceType.equals(ResourceType.BOOL.getName())) { result.addAll(resources.getBools()); } for (Item item : resources.getItems()) { String type = item.getType().getValue(); if (resourceType.equals(type)) { result.add(item); } } return result; } public static boolean isInResourceSubdirectory(@NotNull PsiFile file, @Nullable String resourceType) { file = file.getOriginalFile(); PsiDirectory dir = file.getContainingDirectory(); if (dir == null) return false; return isResourceSubdirectory(dir, resourceType); } public static boolean isResourceSubdirectory(@NotNull PsiDirectory directory, @Nullable String resourceType) { PsiDirectory dir = directory; final String dirName = dir.getName(); if (resourceType != null && !dirName.equals(resourceType) && !dirName.startsWith(resourceType + '-')) { return false; } dir = dir.getParent(); if (dir == null) { return false; } if ("default".equals(dir.getName())) { dir = dir.getParentDirectory(); } return dir != null && isResourceDirectory(dir); } public static boolean isLocalResourceDirectory(@NotNull VirtualFile dir, @NotNull Project project) { final Module module = ModuleUtil.findModuleForFile(dir, project); if (module != null) { final AndroidFacet facet = AndroidFacet.getInstance(module); return facet != null && facet.getLocalResourceManager().isResourceDir(dir); } return false; } public static boolean isResourceDirectory(@NotNull PsiDirectory directory) { PsiDirectory dir = directory; // check facet settings VirtualFile vf = dir.getVirtualFile(); if (isLocalResourceDirectory(vf, dir.getProject())) { return true; } // method can be invoked for system resource dir, so we should check it if (!SdkConstants.FD_RES.equals(dir.getName())) return false; dir = dir.getParent(); if (dir != null) { if (dir.findFile(SdkConstants.FN_ANDROID_MANIFEST_XML) != null) { return true; } dir = dir.getParent(); if (dir != null) { if (containsAndroidJar(dir)) return true; dir = dir.getParent(); if (dir != null) { return containsAndroidJar(dir); } } } return false; } private static boolean containsAndroidJar(@NotNull PsiDirectory psiDirectory) { return psiDirectory.findFile(SdkConstants.FN_FRAMEWORK_LIBRARY) != null; } public static boolean isRJavaFile(@NotNull AndroidFacet facet, @NotNull PsiFile file) { if (file.getName().equals(AndroidCommonUtils.R_JAVA_FILENAME) && file instanceof PsiJavaFile) { final PsiJavaFile javaFile = (PsiJavaFile)file; final Manifest manifest = facet.getManifest(); if (manifest == null) { return false; } final String manifestPackage = manifest.getPackage().getValue(); if (manifestPackage != null && javaFile.getPackageName().equals(manifestPackage)) { return true; } for (String aPackage : AndroidUtils.getDepLibsPackages(facet.getModule())) { if (javaFile.getPackageName().equals(aPackage)) { return true; } } } return false; } public static boolean isManifestJavaFile(@NotNull AndroidFacet facet, @NotNull PsiFile file) { if (file.getName().equals(AndroidCommonUtils.MANIFEST_JAVA_FILE_NAME) && file instanceof PsiJavaFile) { final Manifest manifest = facet.getManifest(); final PsiJavaFile javaFile = (PsiJavaFile)file; return manifest != null && javaFile.getPackageName().equals(manifest.getPackage().getValue()); } return false; } public static List<String> getNames(@NotNull Collection<ResourceType> resourceTypes) { if (resourceTypes.size() == 0) { return Collections.emptyList(); } final List<String> result = new ArrayList<String>(); for (ResourceType type : resourceTypes) { result.add(type.getName()); } return result; } @NotNull public static String[] getNamesArray(@NotNull Collection<ResourceType> resourceTypes) { final List<String> names = getNames(resourceTypes); return ArrayUtil.toStringArray(names); } public static boolean createValueResource(@NotNull Module module, @NotNull String resourceName, @NotNull final ResourceType resourceType, @NotNull String fileName, @NotNull List<String> dirNames, @NotNull Processor<ResourceElement> afterAddedProcessor) { final Project project = module.getProject(); final AndroidFacet facet = AndroidFacet.getInstance(module); assert facet != null; try { return addValueResource(facet, resourceName, resourceType, fileName, dirNames, afterAddedProcessor); } catch (Exception e) { final String message = CreateElementActionBase.filterMessage(e.getMessage()); if (message == null || message.length() == 0) { LOG.error(e); } else { LOG.info(e); AndroidUtils.reportError(project, message); } return false; } } public static boolean createValueResource(@NotNull Module module, @NotNull String resourceName, @NotNull final ResourceType resourceType, @NotNull String fileName, @NotNull List<String> dirNames, @NotNull final String value) { return createValueResource(module, resourceName, resourceType, fileName, dirNames, value, null); } public static boolean createValueResource(@NotNull Module module, @NotNull String resourceName, @NotNull final ResourceType resourceType, @NotNull String fileName, @NotNull List<String> dirNames, @NotNull final String value, @Nullable final List<ResourceElement> outTags) { return createValueResource(module, resourceName, resourceType, fileName, dirNames, new Processor<ResourceElement>() { @Override public boolean process(ResourceElement element) { if (value.length() > 0) { final String s = resourceType == ResourceType.STRING ? normalizeXmlResourceValue(value) : value; element.setStringValue(s); } else if (resourceType == STYLEABLE || resourceType == ResourceType.STYLE) { element.setStringValue("value"); element.getXmlTag().getValue().setText(""); } if (outTags != null) { outTags.add(element); } return true; } }); } private static boolean addValueResource(@NotNull AndroidFacet facet, @NotNull final String resourceName, @NotNull final ResourceType resourceType, @NotNull String fileName, @NotNull List<String> dirNames, @NotNull final Processor<ResourceElement> afterAddedProcessor) throws Exception { if (dirNames.size() == 0) { return false; } final VirtualFile[] resFiles = new VirtualFile[dirNames.size()]; for (int i = 0, n = dirNames.size(); i < n; i++) { final VirtualFile resFile = findOrCreateResourceFile(facet, fileName, dirNames.get(i)); if (resFile == null) { return false; } resFiles[i] = resFile; } if (!ReadonlyStatusHandler.ensureFilesWritable(facet.getModule().getProject(), resFiles)) { return false; } final Resources[] resourcesElements = new Resources[resFiles.length]; for (int i = 0; i < resFiles.length; i++) { final Resources resources = AndroidUtils.loadDomElement(facet.getModule(), resFiles[i], Resources.class); if (resources == null) { AndroidUtils.reportError(facet.getModule().getProject(), AndroidBundle.message("not.resource.file.error", fileName)); return false; } resourcesElements[i] = resources; } List<PsiFile> psiFiles = Lists.newArrayListWithExpectedSize(resFiles.length); Project project = facet.getModule().getProject(); PsiManager manager = PsiManager.getInstance(project); for (VirtualFile file : resFiles) { PsiFile psiFile = manager.findFile(file); if (psiFile != null) { psiFiles.add(psiFile); } } PsiFile[] files = psiFiles.toArray(new PsiFile[psiFiles.size()]); WriteCommandAction<Void> action = new WriteCommandAction<Void>(project, "Add Resource", files) { @Override protected void run(@NotNull Result<Void> result) { for (Resources resources : resourcesElements) { final ResourceElement element = addValueResource(resourceType, resources); element.getName().setValue(resourceName); afterAddedProcessor.process(element); } } }; action.execute(); return true; } @Nullable private static VirtualFile findOrCreateResourceFile(@NotNull AndroidFacet facet, @NotNull final String fileName, @NotNull String dirName) throws Exception { final Module module = facet.getModule(); final Project project = module.getProject(); final VirtualFile resDir = facet.getPrimaryResourceDir(); if (resDir == null) { AndroidUtils.reportError(project, AndroidBundle.message("check.resource.dir.error", module.getName())); return null; } final VirtualFile dir = AndroidUtils.createChildDirectoryIfNotExist(project, resDir, dirName); final String dirPath = FileUtil.toSystemDependentName(resDir.getPath() + '/' + dirName); if (dir == null) { AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.dir.error", dirPath)); return null; } final VirtualFile file = dir.findChild(fileName); if (file != null) { return file; } AndroidFileTemplateProvider .createFromTemplate(project, dir, AndroidFileTemplateProvider.VALUE_RESOURCE_FILE_TEMPLATE, fileName); final VirtualFile result = dir.findChild(fileName); if (result == null) { AndroidUtils.reportError(project, AndroidBundle.message("android.cannot.create.file.error", dirPath + File.separatorChar + fileName)); } return result; } @Nullable public static MyReferredResourceFieldInfo getReferredResourceOrManifestField(@NotNull AndroidFacet facet, @NotNull PsiReferenceExpression exp, boolean localOnly) { return getReferredResourceOrManifestField(facet, exp, null, localOnly); } @Nullable public static MyReferredResourceFieldInfo getReferredResourceOrManifestField(@NotNull AndroidFacet facet, @NotNull PsiReferenceExpression exp, @Nullable String className, boolean localOnly) { final String resFieldName = exp.getReferenceName(); if (resFieldName == null || resFieldName.length() == 0) { return null; } PsiExpression qExp = exp.getQualifierExpression(); if (!(qExp instanceof PsiReferenceExpression)) { return null; } final PsiReferenceExpression resClassReference = (PsiReferenceExpression)qExp; final String resClassName = resClassReference.getReferenceName(); if (resClassName == null || resClassName.length() == 0 || className != null && !className.equals(resClassName)) { return null; } qExp = resClassReference.getQualifierExpression(); if (!(qExp instanceof PsiReferenceExpression)) { return null; } final PsiElement resolvedElement = ((PsiReferenceExpression)qExp).resolve(); if (!(resolvedElement instanceof PsiClass)) { return null; } final PsiClass aClass = (PsiClass)resolvedElement; final String classShortName = aClass.getName(); final boolean fromManifest = AndroidUtils.MANIFEST_CLASS_NAME.equals(classShortName); if (!fromManifest && !AndroidUtils.R_CLASS_NAME.equals(classShortName)) { return null; } if (!localOnly) { final String qName = aClass.getQualifiedName(); if (SdkConstants.CLASS_R.equals(qName) || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME.equals(qName)) { return new MyReferredResourceFieldInfo(resClassName, resFieldName, true, false); } } final PsiFile containingFile = resolvedElement.getContainingFile(); if (containingFile == null) { return null; } if (fromManifest ? !isManifestJavaFile(facet, containingFile) : !isRJavaFile(facet, containingFile)) { return null; } return new MyReferredResourceFieldInfo(resClassName, resFieldName, false, fromManifest); } /** * Utility method suitable for Comparator implementations which order resource files, * which will sort files by base folder followed by alphabetical configurations. Prioritizes * XML files higher than non-XML files. */ public static int compareResourceFiles(@Nullable VirtualFile file1, @Nullable VirtualFile file2) { if (file1 != null && file2 != null && file1 != file2) { boolean xml1 = file1.getFileType() == StdFileTypes.XML; boolean xml2 = file2.getFileType() == StdFileTypes.XML; if (xml1 != xml2) { return xml1 ? -1 : 1; } VirtualFile parent1 = file1.getParent(); VirtualFile parent2 = file2.getParent(); if (parent1 != null && parent2 != null && parent1 != parent2) { boolean qualifier1 = parent1.getName().indexOf('-') != -1; boolean qualifier2 = parent2.getName().indexOf('-') != -1; if (qualifier1 != qualifier2) { return qualifier1 ? 1 : -1; } } return file1.getPath().compareTo(file2.getPath()); } else if (file1 != null) { return -1; } else if (file2 != null) { return 1; } return 0; } /** * Utility method suitable for Comparator implementations which order resource files, * which will sort files by base folder followed by alphabetical configurations. Prioritizes * XML files higher than non-XML files. */ public static int compareResourceFiles(@Nullable PsiFile file1, @Nullable PsiFile file2) { if (file1 != null && file2 != null && file1 != file2) { boolean xml1 = file1.getFileType() == StdFileTypes.XML; boolean xml2 = file2.getFileType() == StdFileTypes.XML; if (xml1 != xml2) { return xml1 ? -1 : 1; } PsiDirectory parent1 = file1.getParent(); PsiDirectory parent2 = file2.getParent(); if (parent1 != null && parent2 != null && parent1 != parent2) { boolean qualifier1 = parent1.getName().indexOf('-') != -1; boolean qualifier2 = parent2.getName().indexOf('-') != -1; // TODO: Sort in FolderConfiguration order! if (qualifier1 != qualifier2) { return qualifier1 ? 1 : -1; } } int delta = file1.getName().compareTo(file2.getName()); if (delta != 0) { return delta; } } else if (file1 != null) { return -1; } else if (file2 != null) { return 1; } return 0; } public static class MyReferredResourceFieldInfo { private final String myClassName; private final String myFieldName; private final boolean mySystem; private final boolean myFromManifest; public MyReferredResourceFieldInfo(@NotNull String className, @NotNull String fieldName, boolean system, boolean fromManifest) { myClassName = className; myFieldName = fieldName; mySystem = system; myFromManifest = fromManifest; } @NotNull public String getClassName() { return myClassName; } @NotNull public String getFieldName() { return myFieldName; } public boolean isSystem() { return mySystem; } public boolean isFromManifest() { return myFromManifest; } } @NotNull public static XmlFile createFileResource(@NotNull String fileName, @NotNull PsiDirectory resSubdir, @NotNull String rootTagName, @NotNull String resourceType, boolean valuesResourceFile) throws Exception { FileTemplateManager manager = FileTemplateManager.getInstance(); String templateName = getTemplateName(resourceType, valuesResourceFile, rootTagName); FileTemplate template = manager.getJ2eeTemplate(templateName); Properties properties = new Properties(); if (!valuesResourceFile) { properties.setProperty(ROOT_TAG_PROPERTY, rootTagName); } if (ResourceType.LAYOUT.getName().equals(resourceType)) { final Module module = ModuleUtilCore.findModuleForPsiElement(resSubdir); final AndroidPlatform platform = module != null ? AndroidPlatform.getInstance(module) : null; final int apiLevel = platform != null ? platform.getApiLevel() : -1; final String value = apiLevel == -1 || apiLevel >= 8 ? "match_parent" : "fill_parent"; properties.setProperty(LAYOUT_WIDTH_PROPERTY, value); properties.setProperty(LAYOUT_HEIGHT_PROPERTY, value); } PsiElement createdElement = FileTemplateUtil.createFromTemplate(template, fileName, properties, resSubdir); assert createdElement instanceof XmlFile; return (XmlFile)createdElement; } private static String getTemplateName(String resourceType, boolean valuesResourceFile, String rootTagName) { if (valuesResourceFile) { return AndroidFileTemplateProvider.VALUE_RESOURCE_FILE_TEMPLATE; } if (ResourceType.LAYOUT.getName().equals(resourceType)) { return AndroidUtils.TAG_LINEAR_LAYOUT.equals(rootTagName) ? AndroidFileTemplateProvider.LAYOUT_RESOURCE_VERTICAL_FILE_TEMPLATE : AndroidFileTemplateProvider.LAYOUT_RESOURCE_FILE_TEMPLATE; } return AndroidFileTemplateProvider.RESOURCE_FILE_TEMPLATE; } @NotNull public static String getFieldNameByResourceName(@NotNull String fieldName) { return fieldName.replace('.', '_').replace('-', '_').replace(':', '_'); } }