blob: 46cf86912e072f7e197f2746bbc52780534ec440 [file] [log] [blame]
package org.jetbrains.android.dom.resources;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.tools.idea.rendering.ResourceNameValidator;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.LocalQuickFixProvider;
import com.intellij.lang.java.lexer.JavaLexer;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.util.xml.*;
import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.inspections.CreateValueResourceQuickFix;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.resourceManagers.ValueResourceInfoImpl;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class ResourceNameConverter extends ResolvingConverter<String> implements CustomReferenceConverter<String> {
@Override
public String fromString(@Nullable @NonNls String s, ConvertContext context) {
if (s == null) {
return null;
}
String fieldName = AndroidResourceUtil.getFieldNameByResourceName(s);
return StringUtil.isJavaIdentifier(fieldName) && !JavaLexer.isKeyword(fieldName, LanguageLevel.JDK_1_5) ? s : null;
}
@Override
public String toString(@Nullable String s, ConvertContext context) {
return s;
}
@Override
public String getErrorMessage(@Nullable String s, ConvertContext context) {
if (s != null) {
String message = ResourceNameValidator.create(false, ResourceFolderType.VALUES).getErrorText(s);
if (message != null) {
return message;
}
}
return AndroidBundle.message("invalid.resource.name.error", s);
}
@NotNull
@Override
public Collection<? extends String> getVariants(ConvertContext context) {
final DomElement element = context.getInvocationElement();
if (!(element instanceof GenericAttributeValue)) {
return Collections.emptyList();
}
if (element.getParent() instanceof Style) {
return getStyleNameVariants(context, (GenericAttributeValue)element);
}
return Collections.emptyList();
}
private static Collection<? extends String> getStyleNameVariants(ConvertContext context, GenericAttributeValue element) {
final Module module = context.getModule();
if (module == null) {
return Collections.emptyList();
}
final LocalResourceManager manager = LocalResourceManager.getInstance(module);
if (manager == null) {
return Collections.emptyList();
}
final Collection<String> styleNames = manager.getResourceNames(ResourceType.STYLE.getName());
final List<String> result = new ArrayList<String>();
final String currentValue = element.getStringValue();
for (String name : styleNames) {
if (currentValue == null || !currentValue.startsWith(name)) {
result.add(name + '.');
}
}
return result;
}
@NotNull
@Override
public PsiReference[] createReferences(GenericDomValue<String> value, PsiElement element, ConvertContext context) {
final Module module = context.getModule();
if (module == null) {
return PsiReference.EMPTY_ARRAY;
}
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
return PsiReference.EMPTY_ARRAY;
}
final DomElement parent = value.getParent();
if (parent instanceof Style) {
return getReferencesInStyleName((Style)parent, value, facet);
}
return PsiReference.EMPTY_ARRAY;
}
private static PsiReference[] getReferencesInStyleName(@NotNull Style style,
@NotNull GenericDomValue<String> value,
@NotNull AndroidFacet facet) {
final String s = value.getStringValue();
if (s == null) {
return PsiReference.EMPTY_ARRAY;
}
final String[] ids = s.split("\\.");
if (ids.length < 2 ||
style.getParentStyle().getStringValue() != null) {
return PsiReference.EMPTY_ARRAY;
}
final List<PsiReference> result = new ArrayList<PsiReference>(ids.length - 1);
int offset = s.length();
for (int i = ids.length - 1; i >= 0; i--) {
if (i < ids.length - 1) {
final String parentStyleName = s.substring(0, offset);
final ResourceValue val = ResourceValue.referenceTo((char)0, null, ResourceType.STYLE.getName(), parentStyleName);
result.add(new MyParentStyleReference(value, new TextRange(1, 1 + offset), val, facet));
if (hasExplicitParent(facet, parentStyleName)) {
break;
}
}
offset = offset - ids[i].length() - 1;
}
return result.toArray(new PsiReference[result.size()]);
}
public static boolean hasExplicitParent(@NotNull AndroidFacet facet, @NotNull String localStyleName) {
final List<ValueResourceInfoImpl> styles = facet.getLocalResourceManager().
findValueResourceInfos(ResourceType.STYLE.getName(), localStyleName, true, false);
if (styles.size() == 0) {
return false;
}
// all resolved styles have explicit parents
for (ValueResourceInfoImpl info : styles) {
final ResourceElement domElement = info.computeDomElement();
if (!(domElement instanceof Style) || ((Style)domElement).getParentStyle().getStringValue() == null) {
return false;
}
}
return true;
}
public static class MyParentStyleReference extends AndroidResourceReferenceBase implements LocalQuickFixProvider {
public MyParentStyleReference(@NotNull GenericDomValue value,
@Nullable TextRange range,
@NotNull ResourceValue resourceValue,
@NotNull AndroidFacet facet) {
super(value, range, resourceValue, facet);
}
@Override
public LocalQuickFix[] getQuickFixes() {
final String resourceName = getValue();
if (resourceName.length() == 0) {
return LocalQuickFix.EMPTY_ARRAY;
}
final PsiFile psiFile = getElement().getContainingFile();
if (psiFile == null) {
return LocalQuickFix.EMPTY_ARRAY;
}
return new LocalQuickFix[] {new CreateValueResourceQuickFix(myFacet, ResourceType.STYLE, resourceName, psiFile, false)};
}
}
}