/*******************************************************************************
* Copyright (c) 2005-2009, G. Weirich and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* G. Weirich - initial implementation
*
*******************************************************************************/
package ch.rgw.tools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
/**
* Eine Baumförmige rekursive Datenstruktur. Ein Tree ist gleicheitig ein node. Ein Tree hat
* children (die allerdings auch null sein können) und Geschwister, (die ebenfalls null sein
* können), sowie ein Parent, welches ebenfalls null sein kann (dann ist dieses Tree-Objekt die
* Wurzel des Baums) Jeder Tree trägt ein beliebiges Datenobjekt (contents).
*/
public class Tree<T> {
public IFilter filter;
protected Tree<T> parent;
protected Tree<T> first;
protected Tree<T> next;
// protected Tree<T> last;
public T contents;
/**
* Eine neues Tree-Objekt erstellen
*
* @param p
* der Parent, oder null, wenn dies die Wurzel werden soll.
* @param elem
* das zugeordnete Datenobjekt
*/
public Tree(Tree<T> p, T elem){
contents = elem;
parent = p;
first = null;
// last=null;
filter = null;
if (parent != null) {
next = parent.first;
parent.first = this;
}
}
/**
* Ein neues Tree-Objekt innerhalb der Geschwisterliste sortiert einfügen
*
* @param parent
* Parent
* @param elem
* Datenobjekt
* @param comp
* Ein Comparator für das Fatenobjekt
*/
public Tree(Tree<T> parent, T elem, Comparator<T> comp){
this.parent = parent;
contents = elem;
if (parent != null) {
next = parent.first;
Tree<T> prev = null;
while ((next != null) && (comp.compare(next.contents, elem) < 0)) {
prev = next;
next = next.next;
}
if (prev == null) {
parent.first = this;
} else {
prev.next = this;
}
}
}
/**
* Ein neues Tree-Objekt mit einem Filter erstellen. Wenn ein Filter gesetzt wird, dann werden
* von getChildren() nur die geliefert, die dem Filter entsprechen
*
* @param p
* Parent-Element
* @param elem
* Datenobjekt
* @param f
* Filter
*/
public Tree(Tree<T> p, T elem, IFilter f){
this(p, elem);
filter = f;
}
/**
* Filter nachträglich setzen. Der Filter wird für dieses und alle Children gesetzt.
*
* @param f
* der Filter
*/
public void setFilter(IFilter f){
filter = f;
Tree<T> cursor = first;
while (cursor != null) {
cursor.setFilter(f);
cursor = cursor.next;
}
}
/**
* Ein Datenobjekt als Kind-element zufügen. Dies (Das Datenobjekt wird implizit in ein
* Tree-Objekt gepackt. obj.add(t) ist dasselbe wie new Tree(obj,t))
*
* @param elem
* Das Datenobjekt
* @return das erzeugte Tree-Objekt
*/
public Tree<T> add(T elem){
Tree<T> ret = new Tree<T>(this, elem, filter);
return ret;
}
/**
* Ein Kind-Element samt dessen Unterelementen entfernen
*
* @param subtree
* das Kindelement
*/
public void remove(Tree<T> subtree){
if (first == null) {
return;
}
if (first.equals(subtree)) {
first = subtree.next;
return;
}
Tree<T> runner = first;
while (!runner.next.equals(subtree)) {
runner = runner.next;
if (runner == null) {
return;
}
}
runner.next = subtree.next;
}
/**
* An einen anderen Parenet-Node oder Tree zügeln (Mitsamt allen Kindern)
*
* @param newParent
* der neue Elter
*/
public synchronized Tree<T> move(Tree<T> newParent){
Tree<T> oldParent = parent;
if (oldParent != null) {
oldParent.remove(this);
}
parent = newParent;
next = newParent.first;
newParent.first = this;
return this;
}
/**
* Ähnlich wie add, aber wenn das übergebene Child schon existiert, werden nur dessen Kinder mit
* den Kindern des existenten childs ge'merged' (Also im Prinzip ein add mit Vermeidung von
* Dubletten
*/
public synchronized void merge(Tree<T> newChild){
Tree<T> tExist = find(newChild.contents, false);
if (tExist != null) {
for (Tree<T> ts = newChild.first; ts != null; ts = ts.next) {
tExist.merge(ts);
}
if (newChild.first == null) {
newChild.getParent().remove(newChild);
}
} else {
newChild.move(this);
}
}
/**
* Alle Kind-Elemente entfernen
*
*/
@SuppressWarnings("unchecked")//$NON-NLS-1$
public synchronized void clear(){
for (Tree t : getChildren()) {
remove(t);
}
}
/**
* Alle Kind-Elemente liefern
*
* @return eine Collection mit den Kind-Trees
*/
public Collection<Tree<T>> getChildren(){
ArrayList<Tree<T>> al = new ArrayList<Tree<T>>();
Tree<T> cursor = first;
while (cursor != null) {
if (filter == null) {
al.add(cursor);
} else {
if (filter.select(cursor.contents) || cursor.hasChildren()) {
al.add(cursor);
}
}
cursor = cursor.next;
}
return al;
}
/**
* Das Elternobjekt liefern
*
* @return das parent
*/
public Tree<T> getParent(){
return parent;
}
/**
* Erstes Kind-element liefern. Null, wenn keine Kinder. Dies macht im Gegensatz zu
* hasChildren() keine synchronisation!
*
* @return
*/
public Tree<T> getFirstChild(){
return first;
}
/**
* Nächstes Geschwister liefern oder null wenn keine mehr da sind. getParent().getFirstChild()
* liefert den Start der Geschwisterliste.
*
* @return
*/
public Tree<T> getNextSibling(){
return next;
}
/**
* Fragen, ob Kinder vorhanden sind
*
* @return true wenn dieses Objekt Children hat.
*/
public boolean hasChildren(){
if (filter == null) {
return (first != null);
}
Tree<T> cursor = first;
while (cursor != null) {
if (filter.select(cursor.contents) || cursor.hasChildren()) {
return true;
}
cursor = cursor.next;
}
return false;
}
/**
* Ein Array mit allen Elementen des Baums liefern
*
* @return
*/
@SuppressWarnings("unchecked")//$NON-NLS-1$
public Tree<T>[] toArray(){
return (Tree<T>[]) getAll().toArray();
}
/**
* Eine Liste mit allen Elementen des Baums liefern
*
* @return
*/
public Collection<Tree<T>> getAll(){
ArrayList<Tree<T>> al = new ArrayList<Tree<T>>();
Tree<T> child = first;
while (child != null) {
al.add(child);
al.addAll(child.getAll());
child = child.next;
}
return al;
}
public Tree<T> find(Object o, boolean deep){
for (Tree<T> t : getChildren()) {
if (t.contents.equals(o)) {
return t;
}
if (deep) {
Tree<T> ct = t.find(o, true);
if (ct != null) {
return ct;
}
}
}
return null;
}
}