| /* |
| * 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); |
| } |
| } |