blob: 8db5b7eec99f20bddce926bd22b76351f798ee72 [file] [log] [blame]
/*
* 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;
}
}