| /* |
| * 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.impl; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiAnchor; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.xml.*; |
| import com.intellij.util.xml.reflect.AbstractDomChildrenDescription; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| /** |
| * @author peter |
| */ |
| public abstract class DomAnchorImpl<T extends DomElement> implements DomAnchor<T> { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.util.xml.impl.DomAnchorImpl"); |
| |
| public static <T extends DomElement> DomAnchor<T> createAnchor(@NotNull T t) { |
| return createAnchor(t, true); |
| } |
| |
| public static <T extends DomElement> DomAnchor<T> createAnchor(@NotNull T t, boolean usePsi) { |
| DomInvocationHandler handler = DomManagerImpl.getNotNullHandler(t); |
| if (handler.getStub() != null) { |
| return new StubAnchor<T>(handler); |
| } |
| |
| if (usePsi) { |
| final XmlElement element = t.getXmlElement(); |
| if (element != null) { |
| return new PsiBasedDomAnchor<T>(PsiAnchor.create(element), element.getProject()); |
| } |
| } |
| |
| |
| final DomElement parent = t.getParent(); |
| if (parent == null) { |
| LOG.error("Parent null: " + t); |
| } |
| |
| if (parent instanceof DomFileElementImpl) { |
| final DomFileElementImpl fileElement = (DomFileElementImpl)parent; |
| //noinspection unchecked |
| return new RootAnchor<T>(fileElement.getFile(), fileElement.getRootElementClass()); |
| } |
| |
| final DomAnchor<DomElement> parentAnchor = createAnchor(parent); |
| final String name = t.getGenericInfo().getElementName(t); |
| final AbstractDomChildrenDescription description = t.getChildDescription(); |
| final List<? extends DomElement> values = description.getValues(parent); |
| if (name != null) { |
| int i = 0; |
| for (DomElement value : values) { |
| if (value.equals(t)) { |
| return new NamedAnchor<T>(parentAnchor, description, name, i); |
| } |
| if (name.equals(value.getGenericInfo().getElementName(value))) { |
| i++; |
| } |
| } |
| } |
| |
| final int index = values.indexOf(t); |
| if (index < 0) { |
| diagnoseNegativeIndex2(t, parent, description, values); |
| } |
| return new IndexedAnchor<T>(parentAnchor, description, index); |
| } |
| |
| private static <T extends DomElement> void diagnoseNegativeIndex2(T t, |
| DomElement parent, |
| AbstractDomChildrenDescription description, |
| List<? extends DomElement> values) { |
| final XmlTag parentTag = parent.getXmlTag(); |
| StringBuilder diag = new StringBuilder("Index<0: description=" + description + "\nparent=" + parent + "\nt=" + t + "\nvalues=" + values + "\n"); |
| for (int i = 0, size = values.size(); i < size; i++) { |
| DomElement value = values.get(i); |
| if (value.toString().equals(t.toString())) { |
| final XmlElement tElement = t.getXmlElement(); |
| final XmlElement valElement = value.getXmlElement(); |
| diag.append(" hasSame, i=" + i + |
| "; same=" + (value == t) + |
| ", equal=" + value.equals(t) + |
| ", equal2=" + t.equals(value) + |
| ", t.physical=" + (tElement == null ? "null" : String.valueOf(tElement.isPhysical())) + |
| ", value.physical=" + (valElement == null ? "null" : String.valueOf(valElement.isPhysical())) + |
| ", sameElements=" + (tElement == value.getXmlElement()) + |
| "\n"); |
| if (tElement != null && valElement != null) { |
| diag.append(" sameFile=" + (tElement.getContainingFile() == valElement.getContainingFile()) + |
| ", sameParent=" + (tElement.getParent() == valElement.getParent()) + |
| "\n"); |
| } |
| } |
| } |
| |
| if (parentTag != null) { |
| diag.append("Parent tag: ").append(parentTag.getName()).append("\n"); |
| if (t instanceof GenericAttributeValue) { |
| for (XmlAttribute attribute : parentTag.getAttributes()) { |
| diag.append(", attr: ").append(attribute.getName()); |
| } |
| diag.append("\n"); |
| } else { |
| for (XmlTag tag : parentTag.getSubTags()) { |
| diag.append("\n subtag: ").append(tag.getName()); |
| } |
| diag.append("\n"); |
| } |
| } |
| diag.append("Child name: ").append(t.getXmlElementName()).append(";").append(t.getXmlElementNamespaceKey()); |
| LOG.error(diag); |
| } |
| |
| |
| @Override |
| public PsiElement getPsiElement() { |
| T t = retrieveDomElement(); |
| return t == null ? null : t.getXmlElement(); |
| } |
| |
| @Override |
| @Nullable |
| public abstract T retrieveDomElement(); |
| |
| @Override |
| @NotNull |
| public abstract XmlFile getContainingFile(); |
| |
| private static class NamedAnchor<T extends DomElement> extends DomAnchorImpl<T> { |
| private final DomAnchor myParent; |
| private final AbstractDomChildrenDescription myDescr; |
| private final String myName; |
| private final int myIndex; |
| |
| private NamedAnchor(final DomAnchor parent, final AbstractDomChildrenDescription descr, final String id, int index) { |
| myParent = parent; |
| myDescr = descr; |
| myName = id; |
| myIndex = index; |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) return true; |
| if (!(o instanceof NamedAnchor)) return false; |
| |
| final NamedAnchor that = (NamedAnchor)o; |
| |
| if (myDescr != null ? !myDescr.equals(that.myDescr) : that.myDescr != null) return false; |
| if (myName != null ? !myName.equals(that.myName) : that.myName != null) return false; |
| if (myParent != null ? !myParent.equals(that.myParent) : that.myParent != null) return false; |
| if (myIndex != that.myIndex) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result; |
| result = (myParent != null ? myParent.hashCode() : 0); |
| result = 31 * result + (myDescr != null ? myDescr.hashCode() : 0); |
| result = 31 * result + (myName != null ? myName.hashCode() : 0); |
| result = 31 * result + myIndex; |
| return result; |
| } |
| |
| @Override |
| public T retrieveDomElement() { |
| final DomElement parent = myParent.retrieveDomElement(); |
| if (parent == null) return null; |
| |
| final List<? extends DomElement> list = myDescr.getValues(parent); |
| int i = 0; |
| for (final DomElement element : list) { |
| final String s = element.getGenericInfo().getElementName(element); |
| if (myName.equals(s)) { |
| if (i == myIndex) { |
| //noinspection unchecked |
| return (T)element; |
| } |
| i++; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| @NotNull |
| public XmlFile getContainingFile() { |
| return myParent.getContainingFile(); |
| } |
| } |
| |
| private static class IndexedAnchor<T extends DomElement> extends DomAnchorImpl<T> { |
| private final DomAnchor myParent; |
| private final AbstractDomChildrenDescription myDescr; |
| private final int myIndex; |
| |
| private IndexedAnchor(final DomAnchor parent, final AbstractDomChildrenDescription descr, final int index) { |
| myParent = parent; |
| myDescr = descr; |
| myIndex = index; |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) return true; |
| if (!(o instanceof IndexedAnchor)) return false; |
| |
| final IndexedAnchor that = (IndexedAnchor)o; |
| |
| if (myIndex != that.myIndex) return false; |
| if (myDescr != null ? !myDescr.equals(that.myDescr) : that.myDescr != null) return false; |
| if (myParent != null ? !myParent.equals(that.myParent) : that.myParent != null) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result; |
| result = (myParent != null ? myParent.hashCode() : 0); |
| result = 31 * result + (myDescr != null ? myDescr.hashCode() : 0); |
| result = 31 * result + myIndex; |
| return result; |
| } |
| |
| @Override |
| public T retrieveDomElement() { |
| final DomElement parent = myParent.retrieveDomElement(); |
| if (parent == null) return null; |
| |
| final List<? extends DomElement> list = myDescr.getValues(parent); |
| if (myIndex < 0 || myIndex >= list.size()) return null; |
| |
| //noinspection unchecked |
| return (T)list.get(myIndex); |
| } |
| |
| @Override |
| @NotNull |
| public XmlFile getContainingFile() { |
| return myParent.getContainingFile(); |
| } |
| } |
| |
| private static class RootAnchor<T extends DomElement> extends DomAnchorImpl<T> { |
| private final XmlFile myFile; |
| private final Class<T> myClass; |
| |
| private RootAnchor(final XmlFile file, final Class<T> aClass) { |
| myFile = file; |
| myClass = aClass; |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) return true; |
| if (!(o instanceof RootAnchor)) return false; |
| |
| final RootAnchor that = (RootAnchor)o; |
| |
| if (myClass != null ? !myClass.equals(that.myClass) : that.myClass != null) return false; |
| if (myFile != null ? !myFile.equals(that.myFile) : that.myFile != null) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result; |
| result = (myFile != null ? myFile.hashCode() : 0); |
| result = 31 * result + (myClass != null ? myClass.hashCode() : 0); |
| return result; |
| } |
| |
| @Override |
| public T retrieveDomElement() { |
| final DomFileElement<T> fileElement = DomManager.getDomManager(myFile.getProject()).getFileElement(myFile, myClass); |
| return fileElement == null ? null : fileElement.getRootElement(); |
| } |
| |
| @Override |
| @NotNull |
| public XmlFile getContainingFile() { |
| return myFile; |
| } |
| } |
| |
| |
| private static class PsiBasedDomAnchor<T extends DomElement> extends DomAnchorImpl<T> { |
| private final PsiAnchor myAnchor; |
| private final Project myProject; |
| |
| public PsiBasedDomAnchor(PsiAnchor anchor, Project project) { |
| myAnchor = anchor; |
| myProject = project; |
| } |
| |
| @Override |
| public T retrieveDomElement() { |
| PsiElement psi = myAnchor.retrieve(); |
| if (psi == null) return null; |
| |
| if (psi instanceof XmlTag) { |
| return (T)DomManager.getDomManager(myProject).getDomElement((XmlTag)psi); |
| } |
| if (psi instanceof XmlAttribute) { |
| return (T)DomManager.getDomManager(myProject).getDomElement((XmlAttribute)psi); |
| } |
| return null; |
| } |
| |
| @NotNull |
| @Override |
| public XmlFile getContainingFile() { |
| return (XmlFile)myAnchor.getFile(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| PsiBasedDomAnchor anchor = (PsiBasedDomAnchor)o; |
| |
| if (myAnchor != null ? !myAnchor.equals(anchor.myAnchor) : anchor.myAnchor != null) return false; |
| if (myProject != null ? !myProject.equals(anchor.myProject) : anchor.myProject != null) return false; |
| |
| return true; |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| int result = myAnchor != null ? myAnchor.hashCode() : 0; |
| result = 31 * result + (myProject != null ? myProject.hashCode() : 0); |
| return result; |
| } |
| } |
| |
| private static class StubAnchor<T extends DomElement> implements DomAnchor<T> { |
| |
| private final DomInvocationHandler myHandler; |
| |
| private StubAnchor(DomInvocationHandler handler) { |
| myHandler = handler; |
| } |
| |
| @Nullable |
| @Override |
| public T retrieveDomElement() { |
| return (T)myHandler.getProxy(); |
| } |
| |
| @NotNull |
| @Override |
| public XmlFile getContainingFile() { |
| return myHandler.getFile(); |
| } |
| |
| @Nullable |
| @Override |
| public PsiElement getPsiElement() { |
| return myHandler.getXmlElement(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| StubAnchor anchor = (StubAnchor)o; |
| |
| if (myHandler != null ? !myHandler.equals(anchor.myHandler) : anchor.myHandler != null) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return myHandler != null ? myHandler.hashCode() : 0; |
| } |
| } |
| } |