blob: 94a35842baa6326ac1f8bec71849f9dc6940cc78 [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.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;
}
}
}