| package org.intellij.lang.xpath.xslt.psi.impl; |
| |
| import com.intellij.navigation.ItemPresentation; |
| import com.intellij.navigation.NavigationItem; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiElementVisitor; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiNamedElement; |
| import com.intellij.psi.impl.light.LightElement; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlAttributeValue; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.IncorrectOperationException; |
| import icons.XpathIcons; |
| import org.intellij.lang.xpath.completion.CompletionLists; |
| import org.intellij.lang.xpath.xslt.context.XsltNamespaceContext; |
| import org.intellij.lang.xpath.xslt.impl.references.PrefixReference; |
| import org.intellij.lang.xpath.xslt.util.QNameUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.xml.namespace.QName; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| |
| public class ImplicitModeElement extends LightElement implements PsiNamedElement, NavigationItem, ItemPresentation { |
| private final XmlAttribute myAttribute; |
| private PsiElement myNavigationElement; |
| |
| public ImplicitModeElement(XmlAttribute attribute) { |
| super(attribute.getManager(), XsltLanguage.INSTANCE); |
| myAttribute = attribute; |
| } |
| |
| @Nullable |
| public QName getQName() { |
| final String prefix = getPrefix(); |
| if (prefix != null && prefix.length() > 0) { |
| final String uri = XsltNamespaceContext.getNamespaceUriStatic(prefix, myAttribute); |
| return uri != null && uri.length() > 0 ? new QName(uri, getName(), prefix) : QNameUtil.UNRESOLVED; |
| } else { |
| return new QName(getName()); |
| } |
| } |
| |
| @Nullable |
| private String getPrefix() { |
| return hasPrefix() ? PrefixReference.getPrefixRange(myAttribute).substring(myAttribute.getValue()) : null; |
| } |
| |
| @Override |
| public Icon getIcon(int i) { |
| return XpathIcons.Template; |
| } |
| |
| public String getName() { |
| return getModeRange().substring(myAttribute.getValue()); |
| } |
| |
| public PsiElement setName(@NotNull String name) throws IncorrectOperationException { |
| // name is calculated dynamically from attached attribute. actual renaming is done by each reference |
| return this; |
| } |
| |
| @Override |
| public String toString() { |
| return "Mode: " + getName(); |
| } |
| |
| public void accept(@NotNull PsiElementVisitor visitor) { |
| } |
| |
| public PsiElement copy() { |
| return this; |
| } |
| |
| @SuppressWarnings({"ConstantConditions"}) |
| public String getText() { |
| return getName(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| final ImplicitModeElement that = (ImplicitModeElement)o; |
| |
| return QNameUtil.equal(getQName(), that.getQName()); |
| } |
| |
| @Override |
| public int hashCode() { |
| final QName qName = getQName(); |
| return qName != null ? qName.hashCode() : 0; |
| } |
| |
| @Override |
| public boolean isPhysical() { |
| return myAttribute.isPhysical(); |
| } |
| |
| @Override |
| public boolean isWritable() { |
| return myAttribute.isWritable(); |
| } |
| |
| public boolean isValid() { |
| return myAttribute.isValid(); |
| } |
| |
| @Override |
| public int getTextOffset() { |
| final XmlAttributeValue value = myAttribute.getValueElement(); |
| return value != null ? value.getTextOffset() + getModeRange().getStartOffset() : 0; |
| } |
| |
| @Override |
| public TextRange getTextRange() { |
| final XmlAttributeValue value = myAttribute.getValueElement(); |
| return value != null ? TextRange.from(value.getTextOffset() + getModeRange().getStartOffset(), getModeRange().getLength()) : TextRange.from(0, 0); |
| } |
| |
| @Override |
| public ItemPresentation getPresentation() { |
| return this; |
| } |
| |
| @Nullable |
| public Icon getIcon(boolean open) { |
| return getIcon(0); |
| } |
| |
| @Nullable |
| public String getLocationString() { |
| return null; |
| } |
| |
| @Nullable |
| public String getPresentableText() { |
| final QName qName = getQName(); |
| return qName != null ? qName.toString() : hasPrefix() ? getPrefix() + ":" + getName() : getName(); |
| } |
| |
| @NotNull |
| @Override |
| @SuppressWarnings({ "RawUseOfParameterizedType" }) |
| public PsiElement getNavigationElement() { |
| if (myNavigationElement == null && myAttribute.isValid()) { |
| final XmlTag tag = myAttribute.getParent(); |
| final Class[] allInterfaces = CompletionLists.getAllInterfaces(tag.getClass()); |
| myNavigationElement = (PsiElement)Proxy.newProxyInstance(getClass().getClassLoader(), allInterfaces, new InvocationHandler() { |
| @SuppressWarnings({"StringEquality", "AutoBoxing", "AutoUnboxing"}) |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| try { |
| final ImplicitModeElement nameElement = ImplicitModeElement.this; |
| |
| if (method.getName() == "navigate") { |
| nameElement.navigate((Boolean)args[0]); |
| //noinspection ConstantConditions |
| return null; |
| } else if (method.getName() == "canNavigate") { |
| return nameElement.canNavigate(); |
| } else if (method.getName() == "getTextOffset") { |
| return nameElement.getTextOffset(); |
| } |
| return method.invoke(tag, args); |
| } catch (InvocationTargetException e1) { |
| throw e1.getTargetException(); |
| } |
| } |
| }); |
| } |
| return myAttribute.isValid() ? myNavigationElement : this; |
| } |
| |
| |
| public boolean canNavigate() { |
| return isValid() && myAttribute.getValueElement() != null; |
| } |
| |
| @Override |
| public void navigate(boolean b) { |
| final Navigatable navigatable = ((Navigatable)myAttribute.getValueElement()); |
| if (navigatable != null) { |
| navigatable.navigate(b); |
| } |
| } |
| |
| @Override |
| public PsiElement getOriginalElement() { |
| return myAttribute; |
| } |
| |
| @Override |
| @NotNull |
| public SearchScope getUseScope() { |
| return myAttribute.getUseScope(); |
| } |
| |
| @Override |
| public PsiElement getParent() { |
| return myAttribute.getParent(); |
| } |
| |
| @Override |
| public PsiFile getContainingFile() { |
| return myAttribute.getContainingFile(); |
| } |
| |
| public TextRange getModeRange() { |
| final String value = myAttribute.getValue(); |
| final int p = value.indexOf(':'); |
| if (p == -1) { |
| return TextRange.from(0, value.length()); |
| } else if (p == value.length() - 1) { |
| return TextRange.from(0, 0); |
| } else { |
| return new TextRange(p + 1, value.length()); |
| } |
| } |
| |
| public boolean hasPrefix() { |
| return myAttribute.getValue().indexOf(':') != -1; |
| } |
| } |