blob: 3dba990044c8dfc31b9ba02b57a81f0685a1830a [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.diagnostic.LogMessageEx;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlEntityRef;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author peter
*/
public class PhysicalDomParentStrategy implements DomParentStrategy {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.xml.impl.PhysicalDomParentStrategy");
private XmlElement myElement;
private final DomManagerImpl myDomManager;
public PhysicalDomParentStrategy(@NotNull final XmlElement element, DomManagerImpl domManager) {
myElement = element;
myDomManager = domManager;
}
@Override
public DomInvocationHandler getParentHandler() {
final XmlTag parentTag = getParentTag(myElement);
assert parentTag != null;
return myDomManager.getDomHandler(parentTag);
}
public static XmlTag getParentTag(final XmlElement xmlElement) {
return (XmlTag)getParentTagCandidate(xmlElement);
}
public static PsiElement getParentTagCandidate(final XmlElement xmlElement) {
final PsiElement parent = xmlElement.getParent();
return parent instanceof XmlEntityRef ? parent.getParent() : parent;
}
@Override
@NotNull
public final XmlElement getXmlElement() {
return myElement;
}
@Override
@NotNull
public DomParentStrategy refreshStrategy(final DomInvocationHandler handler) {
return this;
}
@Override
@NotNull
public DomParentStrategy setXmlElement(@NotNull final XmlElement element) {
myElement = element;
return this;
}
@Override
public String toString() {
return "Physical:" + myElement;
}
@Override
@NotNull
public DomParentStrategy clearXmlElement() {
final DomInvocationHandler parent = getParentHandler();
assert parent != null : "write operations should be performed on the DOM having a parent, your DOM may be not very fresh";
return new VirtualDomParentStrategy(parent);
}
@Override
public String checkValidity() {
return myElement.isValid() ? null : "Invalid PSI";
}
@Override
public XmlFile getContainingFile(DomInvocationHandler handler) {
return DomImplUtil.getFile(handler);
}
@Override
public boolean isPhysical() {
return true;
}
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(final Object o) {
return strategyEquals(this, o);
}
public static boolean strategyEquals(DomParentStrategy strategy, final Object o) {
if (strategy == o) return true;
if (!(o instanceof DomParentStrategy)) return false;
final XmlElement thatElement = ((DomParentStrategy)o).getXmlElement();
if (thatElement == null) return false;
XmlElement element = strategy.getXmlElement();
if (element == null) return false;
if (xmlElementsEqual(element, thatElement)) {
if (element != thatElement) {
final PsiElement nav1 = element.getNavigationElement();
final PsiElement nav2 = thatElement.getNavigationElement();
if (nav1 != nav2) {
PsiElement curContext = findIncluder(element);
PsiElement navContext = findIncluder(nav1);
LOG.error(LogMessageEx.createEvent(
"x:include processing error",
"nav1,nav2=" + nav1 + ", " + nav2 + ";\n" +
nav1.getContainingFile() + ":" + nav1.getTextRange().getStartOffset() + "!=" + nav2.getContainingFile() + ":" + nav2.getTextRange().getStartOffset() + ";\n" +
(nav1 == element) + ";" + (nav2 == thatElement) + ";\n" +
"contexts equal: " + (curContext == navContext) + ";\n" +
"curContext?.physical=" + (curContext != null && curContext.isPhysical()) + ";\n" +
"navContext?.physical=" + (navContext != null && navContext.isPhysical()) + ";\n" +
"myElement.physical=" + element.isPhysical() + ";\n" +
"thatElement.physical=" + thatElement.isPhysical() + "\n" + DebugUtil.currentStackTrace(),
new Attachment("Including tag text 1.xml", curContext == null ? "null" : curContext.getText()),
new Attachment("Including tag text 2.xml", navContext == null ? "null" : navContext.getText())
));
throw new AssertionError();
}
}
return true;
}
return false;
}
@Nullable
private static PsiElement findIncluder(PsiElement cur) {
while (cur != null && !cur.isPhysical()) {
cur = cur.getParent();
}
return cur;
}
private static boolean xmlElementsEqual(@NotNull final PsiElement fst, @NotNull final PsiElement snd) {
if (fst.equals(snd)) return true;
if (fst.isValid() && fst.isPhysical() || snd.isValid() && snd.isPhysical()) return false;
if (fst.getTextLength() != snd.getTextLength()) return false;
if (fst.getStartOffsetInParent() != snd.getStartOffsetInParent()) return false;
PsiElement nav1 = fst.getNavigationElement();
PsiElement nav2 = snd.getNavigationElement();
return nav1 != null && nav1.equals(nav2);
}
public int hashCode() {
if (!myElement.isPhysical()) {
return myElement.getNavigationElement().hashCode();
}
return myElement.hashCode();
}
}