/*
org.org.lib.model is a java library/OSGI Bundle
Providing a tree model utility.
Copyright (C) 2007 Pierre-Antoine Grégoire
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.org.model;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* This is an abstract definition of a model item. It should be implemented.
*
* @author pagregoire
*/
@SuppressWarnings("rawtypes")
public abstract class AbstractModelItem<P extends IModelItem, C extends IModelItem> implements IModelItem<P, C>, Comparable<IModelItem> {
private volatile int hashcode = 0;
/**
* The children of the model item.
*/
protected Map<String, C> children = new ConcurrentHashMap<String, C>();
/**
* the listeners registered in this RootModelItem
*/
private List<IModelItemListener> listeners = new CopyOnWriteArrayList<IModelItemListener>();
/**
* the flag toggling on and off the listeners.
*/
private boolean listenersToggled = true;
/**
* This method registers a listener.
*
* @param listener
* an implementation of IModelListener
*/
public void addListener(IModelItemListener listener) {
listeners.add(listener);
}
/**
* This method de-registers a listener.
*
* @param listener
* an implementation of IModelListener
*/
public void removeListener(IModelItemListener listener) {
listeners.remove(listener);
}
/**
* This method toggle the listeners' triggering ON
*/
public void toggleListenersOn() {
listenersToggled = true;
}
/**
* This method toggle the listeners' triggering OFF
*/
public void toggleListenersOff() {
listenersToggled = false;
}
/**
* This method is used by AbstractModelItem implementors to trigger the listeners from this RootModelItem.
*
* @param event
*/
protected void triggerListeners(ModelItemEvent event) {
if (listenersToggled) {
for (IModelItemListener listener : listeners) {
listener.changeOccured(event);
}
}
}
/**
* The parent of the model item.
*/
protected P parent;
/**
* This method propagates a given event to the parent model item or triggers the listeners if the parent element is a RootModelItem.
*
* @param modelItemEvent
* the event to propagate
* @see RootModelItem#triggerListeners(ModelItemEvent)
*/
protected void propagateEvent(ModelItemEvent modelItemEvent) {
if (this.parent != null) {
if (this.parent instanceof AbstractModelItem) {
((AbstractModelItem) parent).propagateEvent(modelItemEvent);
}
}
if (this.listeners.size() != 0) {
triggerListeners(modelItemEvent);
}
}
/**
* @see org.org.model.IModelItem#addChild(org.org.model.IModelItem)
*/
@SuppressWarnings("unchecked")
public void addChild(C child) {
boolean isAnUpdate = children.containsKey(child.getUID());
if (isAnUpdate) {
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.PRE_UPDATE_CHILD));
} else {
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.PRE_ADD_CHILD));
}
children.put(child.getUID(), child);
if (child instanceof AbstractModelItem) {
if (child.getParent() == null) {
child.setParent(this);
}
}
if (isAnUpdate) {
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.POST_UPDATE_CHILD));
} else {
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.POST_ADD_CHILD));
}
}
/**
* @see org.org.model.IModelItem#setParent(org.org.model.IModelItem)
*/
@SuppressWarnings("unchecked")
public void setParent(P parent) {
if (parent instanceof AbstractModelItem && !parent.hasChild(this.getUID())) {
parent.addChild(this);
}
this.parent = parent;
propagateEvent(new ModelItemEvent(this, parent, ModelItemEvent.EventType.ITEM_PROPERTY_CHANGED));
}
/**
* @see org.org.model.IModelItem#clearChildren()
*/
public void clearChildren() {
for (String key : children.keySet()) {
removeChild(key);
}
}
/**
* @see org.org.model.IModelItem#removeChild(java.lang.String)
*/
public void removeChild(String UID) {
IModelItem child = (IModelItem) children.get(UID);
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.PRE_REMOVE_CHILD));
children.remove(UID);
propagateEvent(new ModelItemEvent(this, child, ModelItemEvent.EventType.POST_REMOVE_CHILD));
}
/**
* @see org.org.model.IModelItem#getChildren()
*/
public Set<C> getChildren() {
return new TreeSet<C>(children.values());
}
/**
* @see org.org.model.IModelItem#hasChildren()
*/
public boolean hasChildren() {
return !children.isEmpty();
}
/**
* @see org.org.model.IModelItem#hasChild(java.lang.String)
*/
public boolean hasChild(String UID) {
return children.containsKey(UID);
}
/**
* @see org.org.model.IModelItem#getChild(java.lang.String)
*/
public C getChild(String UID) {
return children.get(UID);
}
/**
* @see org.org.model.IModelItem#getParent()
*/
public P getParent() {
return parent;
}
/**
* @see org.org.model.IModelItem#getUID()
*/
public abstract String getUID();
/**
* @return
*/
public abstract StringBuilder toStringBuilderDescription();
/**
* This is an utility method that allows a simple tabbed display of the toString() representation of the objects' tree.
*
* @param level
* an integer representing the number of tabulations to display before the current element.
* @return a StringBuffer containing a representation of this model item and of its children.
*/
protected StringBuilder toStringBuilderChildren(int level) {
StringBuilder result = new StringBuilder();
result.append(toStringBuilderDescription());
for (C next : children.values()) {
result.append("\n");
for (int i = 0; i < level; i++) {
result.append("\t");
}
result.append(((AbstractModelItem) next).toStringBuilderChildren(level + 1));
}
return result;
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(IModelItem o) {
int result = 0;
if (o instanceof AbstractModelItem) {
result = getUID().compareTo(((AbstractModelItem) o).getUID());
} else {
result = 1;
}
return result;
}
public void accept(IModelItemVisitor visitor) {
boolean shouldContinue = visitor.visit(this);
if (shouldContinue) {
for (Iterator<C> it = getChildren().iterator(); it.hasNext();) {
it.next().accept(visitor);
}
}
if (visitor instanceof IModelItemAdvancedVisitor) {
((IModelItemAdvancedVisitor) visitor).aftervisit(this, shouldContinue);
}
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("[\n");
result.append(toStringBuilderChildren(1));
result.append("\n]");
return result.toString();
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
if (hashcode == 0) {
hashcode = 37;
hashcode = 17 * hashcode + getUID().hashCode();
}
return hashcode;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
boolean result = false;
if ((obj != null) && (obj.getClass().equals(this.getClass()))) {
result = ((IModelItem) obj).getUID().equals(this.getUID());
}
return result;
}
}