| /* |
| * 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.LocaleQualifier; |
| 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); |
| // Ensure that items *just* created are processed by the resource repository |
| repository.sync(); |
| List<ResourceItem> items = repository.getResourceItem(ResourceType.STRING, key); |
| if (items == null) { |
| return null; |
| } |
| |
| for (ResourceItem item : items) { |
| FolderConfiguration config = item.getConfiguration(); |
| LocaleQualifier qualifier = config == null ? null : config.getLocaleQualifier(); |
| |
| if (qualifier == null) { |
| if (locale == null) { |
| return item; |
| } |
| else { |
| continue; |
| } |
| } |
| |
| Locale l = Locale.create(qualifier); |
| 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.setLocaleQualifier(locale.qualifier); |
| } |
| 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; |
| } |
| } |