blob: a951032b1b036f60613656a59803ddf817bc8293 [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.util.xml.converters;
import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.DelimitedListProcessor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.xml.*;
import com.intellij.xml.util.XmlTagUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public abstract class DelimitedListConverter<T> extends ResolvingConverter<List<T>> implements CustomReferenceConverter<List<T>> {
protected final static Object[] EMPTY_ARRAY = ArrayUtil.EMPTY_OBJECT_ARRAY;
private final String myDelimiters;
public DelimitedListConverter(@NonNls @NotNull String delimiters) {
myDelimiters = delimiters;
}
@Nullable
protected abstract T convertString(final @Nullable String string, final ConvertContext context);
@Nullable
protected abstract String toString(@Nullable final T t);
protected abstract Object[] getReferenceVariants(final ConvertContext context, GenericDomValue<List<T>> genericDomValue);
@Nullable
protected abstract PsiElement resolveReference(@Nullable final T t, final ConvertContext context);
protected abstract String getUnresolvedMessage(String value);
@Override
@NotNull
public Collection<? extends List<T>> getVariants(final ConvertContext context) {
return Collections.emptyList();
}
public static <T> void filterVariants(List<T> variants, GenericDomValue<List<T>> genericDomValue) {
final List<T> list = genericDomValue.getValue();
if (list != null) {
for (Iterator<T> i = variants.iterator(); i.hasNext(); ) {
final T variant = i.next();
for (T existing : list) {
if (existing.equals(variant)) {
i.remove();
break;
}
}
}
}
}
protected char getDefaultDelimiter() {
return myDelimiters.charAt(0);
}
@Override
public List<T> fromString(@Nullable final String str, final ConvertContext context) {
if (str == null) {
return null;
}
List<T> values = new ArrayList<T>();
for (String s : StringUtil.tokenize(str, myDelimiters)) {
final T t = convertString(s.trim(), context);
if (t != null) {
values.add(t);
}
}
return values;
}
@Override
public String toString(final List<T> ts, final ConvertContext context) {
final StringBuilder buffer = new StringBuilder();
final char delimiter = getDefaultDelimiter();
for (T t : ts) {
final String s = toString(t);
if (s != null) {
if (buffer.length() != 0) {
buffer.append(delimiter);
}
buffer.append(s);
}
}
return buffer.toString();
}
@Override
@NotNull
public PsiReference[] createReferences(final GenericDomValue<List<T>> genericDomValue,
final PsiElement element,
final ConvertContext context) {
final String text = genericDomValue.getRawText();
if (text == null) {
return PsiReference.EMPTY_ARRAY;
}
final ArrayList<PsiReference> references = new ArrayList<PsiReference>();
new DelimitedListProcessor(myDelimiters) {
@Override
protected void processToken(final int start, final int end, final boolean delimitersOnly) {
references.add(createPsiReference(element, start + 1, end + 1, context, genericDomValue, delimitersOnly));
}
}.processText(text);
return references.toArray(new PsiReference[references.size()]);
}
@NotNull
protected PsiReference createPsiReference(final PsiElement element,
int start,
int end,
final ConvertContext context,
final GenericDomValue<List<T>> genericDomValue,
final boolean delimitersOnly) {
return new MyPsiReference(element, getTextRange(genericDomValue, start, end), context, genericDomValue, delimitersOnly);
}
protected TextRange getTextRange(GenericDomValue value, int start, int end) {
if (value instanceof GenericAttributeValue) {
return new TextRange(start, end);
}
TextRange tagRange = XmlTagUtil.getTrimmedValueRange(value.getXmlTag());
return new TextRange(tagRange.getStartOffset() + start - 1, tagRange.getStartOffset() + end - 1);
}
@Override
public String toString() {
return super.toString() + " delimiters: " + myDelimiters;
}
protected class MyPsiReference extends PsiReferenceBase<PsiElement> implements EmptyResolveMessageProvider {
protected final ConvertContext myContext;
protected final GenericDomValue<List<T>> myGenericDomValue;
private final boolean myDelimitersOnly;
public MyPsiReference(final PsiElement element,
final TextRange range,
final ConvertContext context,
final GenericDomValue<List<T>> genericDomValue,
final boolean delimitersOnly) {
this(element, range, context, genericDomValue, true, delimitersOnly);
}
public MyPsiReference(final PsiElement element,
final TextRange range,
final ConvertContext context,
final GenericDomValue<List<T>> genericDomValue,
boolean soft,
final boolean delimitersOnly) {
super(element, range, soft);
myContext = context;
myGenericDomValue = genericDomValue;
myDelimitersOnly = delimitersOnly;
}
@Override
@Nullable
public PsiElement resolve() {
if (myDelimitersOnly) {
return getElement();
}
final String value = getValue();
return resolveReference(convertString(value, myContext), myContext);
}
@Override
@NotNull
public Object[] getVariants() {
return getReferenceVariants(myContext, myGenericDomValue);
}
@Override
public PsiElement handleElementRename(final String newElementName) throws IncorrectOperationException {
final Ref<IncorrectOperationException> ref = new Ref<IncorrectOperationException>();
PsiElement element = referenceHandleElementRename(this, newElementName, getSuperElementRenameFunction(ref));
if (!ref.isNull()) {
throw ref.get();
}
return element;
}
@Override
public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
final Ref<IncorrectOperationException> ref = new Ref<IncorrectOperationException>();
PsiElement bindElement =
referenceBindToElement(this, element, getSuperBindToElementFunction(ref), getSuperElementRenameFunction(ref));
if (!ref.isNull()) {
throw ref.get();
}
return bindElement;
}
@Override
public String toString() {
return super.toString() + " converter: " + DelimitedListConverter.this;
}
private Function<PsiElement, PsiElement> getSuperBindToElementFunction(final Ref<IncorrectOperationException> ref) {
return new Function<PsiElement, PsiElement>() {
@Override
public PsiElement fun(final PsiElement s) {
try {
return MyPsiReference.super.bindToElement(s);
}
catch (IncorrectOperationException e) {
ref.set(e);
}
return null;
}
};
}
private Function<String, PsiElement> getSuperElementRenameFunction(final Ref<IncorrectOperationException> ref) {
return new Function<String, PsiElement>() {
@Override
public PsiElement fun(final String s) {
try {
return MyPsiReference.super.handleElementRename(s);
}
catch (IncorrectOperationException e) {
ref.set(e);
}
return null;
}
};
}
@Override
@NotNull
public String getUnresolvedMessagePattern() {
return getUnresolvedMessage(getValue());
}
}
protected PsiElement referenceBindToElement(final PsiReference psiReference, final PsiElement element,
final Function<PsiElement, PsiElement> superBindToElementFunction,
final Function<String, PsiElement> superElementRenameFunction)
throws IncorrectOperationException {
return superBindToElementFunction.fun(element);
}
protected PsiElement referenceHandleElementRename(final PsiReference psiReference,
final String newName,
final Function<String, PsiElement> superHandleElementRename)
throws IncorrectOperationException {
return superHandleElementRename.fun(newName);
}
}