/* * Copyright (C) 2014 The Android Open Source Project * * 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 com.android.tools.idea.editors.strings; import com.android.SdkConstants; import com.android.ide.common.res2.ResourceItem; import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.LanguageQualifier; import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.tools.idea.rendering.LocalResourceRepository; import com.android.tools.idea.rendering.Locale; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.util.AndroidResourceUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Set; public class StringsWriteUtils { /** * Sets the value of an attribute for resource items. If SdkConstants.ATTR_NAME is set to null or "", the items are deleted. * @param attribute The attribute whose value we wish to change * @param value The desired attribute value * @param items The resource items * @return True if the value was successfully set, false otherwise */ public static boolean setAttributeForItems(@NotNull Project project, @NotNull final String attribute, @Nullable final String value, @NotNull List<ResourceItem> items) { if (items.isEmpty()) { return false; } final List<XmlTag> tags = Lists.newArrayListWithExpectedSize(items.size()); final Set<PsiFile> files = Sets.newHashSetWithExpectedSize(items.size()); for (ResourceItem item : items) { XmlTag tag = StringResourceData.resourceToXmlTag(item); if (tag == null) { return false; } tags.add(tag); files.add(tag.getContainingFile()); } final boolean deleteTag = attribute.equals(SdkConstants.ATTR_NAME) && (value == null || value.isEmpty()); new WriteCommandAction.Simple(project, "Setting attribute " + attribute, files.toArray(new PsiFile[files.size()])) { @Override public void run() { for (XmlTag tag : tags) { if (deleteTag) { tag.delete(); } else { // XmlTagImpl handles a null value by deleting the attribute, which is our desired behavior //noinspection ConstantConditions tag.setAttribute(attribute, value); } } } }.execute(); return true; } /** * Sets the text value of a resource item. If the value is the empty string, the item is deleted. * @param item The resource item * @param value The desired text * @return True if the text was successfully set, false otherwise */ public static boolean setItemText(@NotNull Project project, @NotNull ResourceItem item, @NotNull final String value) { if (value.isEmpty()) { // Deletes the tag return setAttributeForItems(project, SdkConstants.ATTR_NAME, null, Collections.singletonList(item)); } final XmlTag tag = StringResourceData.resourceToXmlTag(item); if (tag != null) { new WriteCommandAction.Simple(project, "Setting value of " + item.getName(), tag.getContainingFile()) { @Override public void run() { tag.getValue().setEscapedText(value); } }.execute(); return true; } return false; } /** * Creates a string resource in the specified locale. * * @return the resource item that was created, null if it wasn't created or could not be read back */ @Nullable public static ResourceItem createItem(@NotNull final AndroidFacet facet, @NotNull VirtualFile resFolder, @Nullable final Locale locale, @NotNull final String name, @NotNull final String value, final boolean translatable) { Project project = facet.getModule().getProject(); XmlFile resourceFile = getStringResourceFile(project, resFolder, locale); if (resourceFile == null) { return null; } final XmlTag root = resourceFile.getRootTag(); if (root == null) { return null; } new WriteCommandAction.Simple(project, "Creating string " + name, resourceFile) { @Override public void run() { // AndroidResourceUtil.createValueResource tries to format the value it is passed (e.g., by escaping quotation marks) // We want to save the text exactly as entered by the user, so we create and add the XML tag directly XmlTag child = root.createChildTag(ResourceType.STRING.getName(), root.getNamespace(), value, false); child.setAttribute(SdkConstants.ATTR_NAME, name); // XmlTagImpl handles a null value by deleting the attribute, which is our desired behavior //noinspection ConstantConditions child.setAttribute(SdkConstants.ATTR_TRANSLATABLE, translatable ? null : SdkConstants.VALUE_FALSE); root.addSubTag(child, false); } }.execute(); if (ApplicationManager.getApplication().isReadAccessAllowed()) { return getStringResourceItem(facet, name, locale); } else { return ApplicationManager.getApplication().runReadAction(new Computable<ResourceItem>() { @Override public ResourceItem compute() { return getStringResourceItem(facet, name, locale); } }); } } @Nullable private static ResourceItem getStringResourceItem(@NotNull AndroidFacet facet, @NotNull String key, @Nullable Locale locale) { LocalResourceRepository repository = facet.getModuleResources(true); List<ResourceItem> items = repository.getResourceItem(ResourceType.STRING, key); if (items == null) { return null; } for (ResourceItem item : items) { FolderConfiguration config = item.getConfiguration(); LanguageQualifier languageQualifier = config == null ? null : config.getLanguageQualifier(); if (languageQualifier == null) { if (locale == null) { return item; } else { continue; } } Locale l = Locale.create(languageQualifier, config.getRegionQualifier()); if (l.equals(locale)) { return item; } } return null; } @Nullable private static XmlFile getStringResourceFile(@NotNull Project project, @NotNull final VirtualFile resFolder, @Nullable Locale locale) { FolderConfiguration configuration = new FolderConfiguration(); if (locale != null) { configuration.setLanguageQualifier(locale.language); if (locale.hasRegion()) { configuration.setRegionQualifier(locale.region); } } PsiManager manager = PsiManager.getInstance(project); final String valuesFolderName = configuration.getFolderName(ResourceFolderType.VALUES); VirtualFile valuesFolder = resFolder.findChild(valuesFolderName); if (valuesFolder == null) { valuesFolder = new WriteCommandAction<VirtualFile>(project, "Creating directory " + valuesFolderName, manager.findFile(resFolder)) { @Override public void run(@NotNull Result<VirtualFile> result) { try { result.setResult(resFolder.createChildDirectory(this, valuesFolderName)); } catch (IOException ex) { // Immediately after this, we handle the case where the result is null //noinspection ConstantConditions result.setResult(null); } } }.execute().getResultObject(); if (valuesFolder == null) { return null; } } String resourceFileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STRING); if (resourceFileName == null) { return null; } VirtualFile resourceVirtualFile = valuesFolder.findChild(resourceFileName); XmlFile resourceFile; if (resourceVirtualFile == null) { PsiDirectory valuesDir = manager.findDirectory(valuesFolder); if (valuesDir == null) { return null; } try { resourceFile = AndroidResourceUtil.createFileResource(resourceFileName, valuesDir, "", ResourceType.STRING.getName(), true); } catch (Exception ex) { return null; } } else { PsiFile resourcePsiFile = manager.findFile(resourceVirtualFile); if (!(resourcePsiFile instanceof XmlFile)) { return null; } resourceFile = (XmlFile) resourcePsiFile; } return resourceFile; } }