package org.mevenide.idea.psi.util;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiTreeChangeAdapter;
import com.intellij.psi.PsiTreeChangeEvent;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import com.jgoodies.binding.beans.ExtendedPropertyChangeSupport;
import java.beans.PropertyChangeListener;
import org.mevenide.idea.util.event.PropertyObservable;
/**
* This class manages a list of XML tag paths, and corresponding property names. It listens to PSI
* changes and maps them to appropriate registered tag paths. If a PSI change is found to be inside
* a registered tag path, a property-change event is triggered with the corresponding property
* name.
*
* @author Arik
*/
public abstract class PsiPropertyChangeListener extends PsiTreeChangeAdapter
implements PropertyObservable {
/**
* Property change listeners container.
*/
private final ExtendedPropertyChangeSupport props = new ExtendedPropertyChangeSupport(
this);
/**
* Is the current set of PSI events generated by the XML tag path, or by an outside source?
*/
protected boolean ignoreEvents = false;
/**
* Registers the given property change listener for all property change events.
*
* @param pListener the listener to register
*/
public final void addPropertyChangeListener(final PropertyChangeListener pListener) {
props.addPropertyChangeListener(pListener);
}
/**
* Unregisters the given property change listener from all property change events.
*
* @param pListener the listener to unregister
*/
public final void removePropertyChangeListener(final PropertyChangeListener pListener) {
props.removePropertyChangeListener(pListener);
}
/**
* Registers the given property change listener for the given property.
*
* @param pListener the listener to register
*/
public final void addPropertyChangeListener(final String pPropertyName,
final PropertyChangeListener pListener) {
props.addPropertyChangeListener(pPropertyName, pListener);
}
/**
* Unregisters the given property change listener from events for the given property.
*
* @param pListener the listener to unregister
*/
public final void removePropertyChangeListener(final String pPropertyName,
final PropertyChangeListener pListener) {
props.removePropertyChangeListener(pPropertyName, pListener);
}
@Override
public final void childAdded(final PsiTreeChangeEvent pEvent) {
if (ignoreEvents)
return;
final PsiElement child = pEvent.getChild();
final String changedProperty = getPropertyForElement(child);
if (changedProperty != null) {
if (child instanceof XmlTag)
notifyAddition(changedProperty);
else if (child instanceof XmlText)
notifyChange(changedProperty);
}
}
@Override
public final void childRemoved(final PsiTreeChangeEvent pEvent) {
if (ignoreEvents)
return;
final PsiElement child = pEvent.getChild();
final XmlTag parent = PsiTreeUtil.getParentOfType(pEvent.getParent(),
XmlTag.class,
false);
if (parent == null)
return;
if (child instanceof XmlText) {
final String changedProperty = getPropertyForElement(parent);
if (changedProperty != null)
notifyChange(changedProperty);
}
else if (child instanceof XmlTag) {
final XmlTag tag = (XmlTag) child;
final String[] tagPath = PsiUtils.getPathAndConcat(parent, tag.getName());
final String changedProperty = getPropertyForPath(tagPath);
if (changedProperty != null)
notifyRemoval(changedProperty);
}
}
@Override
public final void childReplaced(final PsiTreeChangeEvent pEvent) {
if (ignoreEvents)
return;
final PsiElement oldChild = pEvent.getOldChild();
final XmlTag parent = PsiTreeUtil.getParentOfType(pEvent.getParent(),
XmlTag.class,
false);
if (parent == null)
return;
String oldChangedProperty = null;
String newChangedProperty = null;
if (oldChild instanceof XmlText)
oldChangedProperty = getPropertyForElement(parent);
else if (oldChild instanceof XmlTag) {
final XmlTag tag = (XmlTag) oldChild;
final String[] tagPath = PsiUtils.getPathAndConcat(parent, tag.getName());
oldChangedProperty = getPropertyForPath(tagPath);
}
final PsiElement newChild = pEvent.getNewChild();
if (newChild instanceof XmlText)
newChangedProperty = getPropertyForElement(newChild);
else if (newChild instanceof XmlTag) {
final XmlTag tag = (XmlTag) newChild;
final String[] tagPath = PsiUtils.getPathAndConcat(parent, tag.getName());
newChangedProperty = getPropertyForPath(tagPath);
}
if (oldChangedProperty == null && newChangedProperty == null)
return;
if (oldChangedProperty != null && oldChangedProperty.equals(newChangedProperty))
notifyChange(newChangedProperty);
else {
if (oldChangedProperty != null)
notifyRemoval(oldChangedProperty);
if (newChangedProperty != null)
notifyAddition(newChangedProperty);
}
}
protected void notifyAddition(final String pPropertyName) {
notifyChange(pPropertyName);
}
protected void notifyRemoval(final String pPropertyName) {
notifyChange(pPropertyName);
}
/**
* @todo currently all propertyChange events' old value is null - due to limitation of IDEA api
*/
protected void notifyChange(final String pProperty) {
final XmlTagPath path = getPropertyTagPath(pProperty);
final String value = path == null ? null : path.getStringValue();
props.firePropertyChange(pProperty, null, value);
}
public final String getValue(final String pPropertyName) {
return getPropertyTagPath(pPropertyName).getStringValue();
}
public final void setValue(final String pPropertyName, final Object pValue) {
ignoreEvents = true;
try {
final String value = pValue == null ? null : pValue.toString();
getPropertyTagPath(pPropertyName).setValueProtected(value);
}
finally {
ignoreEvents = false;
}
}
protected abstract String getPropertyForElement(final PsiElement pElement);
protected abstract String getPropertyForPath(final String[] pPath);
protected abstract XmlTagPath getPropertyTagPath(final String pProperty);
}