/**
* This file is part of Archiv-Editor.
*
* The software Archiv-Editor serves as a client user interface for working with
* the Person Data Repository. See: pdr.bbaw.de
*
* The software Archiv-Editor was developed at the Berlin-Brandenburg Academy
* of Sciences and Humanities, Jägerstr. 22/23, D-10117 Berlin.
* www.bbaw.de
*
* Copyright (C) 2010-2013 Berlin-Brandenburg Academy
* of Sciences and Humanities
*
* The software Archiv-Editor was developed by @author: Christoph Plutte.
*
* Archiv-Editor 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 3 of the License, or
* (at your option) any later version.
*
* Archiv-Editor 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 Archiv-Editor.
* If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package org.bbaw.pdr.ae.export.logic;
import java.util.Collections;
import java.util.HashSet;
import java.util.Vector;
import org.bbaw.pdr.ae.export.swt.preview.PdrSelectionFilterPreview;
import org.bbaw.pdr.ae.model.Aspect;
import org.bbaw.pdr.ae.model.Person;
import org.bbaw.pdr.ae.model.Reference;
import org.bbaw.pdr.ae.model.ReferenceMods;
import org.bbaw.pdr.ae.model.ValidationStm;
import org.bbaw.pdr.ae.model.view.OrderingHead;
public class StructNode implements Comparable<StructNode> {
private String type;
private String label;
private Object content;
private Vector<StructNode> children;
private StructNode parent;
private boolean selected;
private boolean expanded;
private PdrObjectsPreviewStructure struct;
private StructNode root;
/**
* <strike>This constructor calls itself recursively for every object
* related to the object passed as content in a way that the
* tree structure maintained by {@link PdrObjectsPreviewStructure}
* represents as a child node.</strike>
* <p>Instantiates a new StructNode as a child of the passed one and
* labels it according to the type of the object to represent.</p>
* <p>Does <i>not</i> add the new instance to the children of the given
* parent node.</p>
* @param parent parent {@link StructNode}. This is what the new
* instance will know as its parent and what will determine the root
* node it memorizes. Can be null in case a root is to be created.
* @param content The content the new instance will contain. Determines
* the values of its {@link #getType()} and {@link #getLabel()} fields.
* @param structure the {@link PdrObjectsPreviewStructure} the instance
* will understand itself being a part of. Unless no parent it given,
* this can be null.
*/
public StructNode(StructNode parent, Object content, PdrObjectsPreviewStructure structure){
// TODO: best practice:
//http://www.eclipsezone.com/eclipse/forums/t53983.html
this.parent = parent;
if (this.parent != null) {
this.root = this.parent.root;
this.struct = this.parent.struct;
} else {
this.root = this;
this.struct = structure;
}
this.children = new Vector<StructNode>();
this.label="";
this.type="";
this.selected=true;
this.expanded=false;
if (content instanceof OrderingHead) {
OrderingHead group = ((OrderingHead)content);
label = group.getLabel();
this.content = group;
type = getStructure().getClassifier() // hopefully won't happen if no struct has been set yet
+"."+group.getValue().replace(' ', '_').toLowerCase();
//setChildren(group.getAspects().toArray());
} else if (content instanceof Person) {
Person p = ((Person)content);
label = p.getDisplayName();
this.content = p;
type = "pdr.person";
//Vector<PdrId> aspectIds = new Vector<PdrId>(p.getAspectIds());
//Vector<Aspect> aspects = new Vector<Aspect>();
//for (PdrId pid : aspectIds)
//aspects.add(Facade.getInstanz().getAspect(pid));
//setChildren(aspects.toArray());
} else if (content instanceof Aspect) {
Aspect a = ((Aspect)content);
label = a.getDisplayName();
this.content = a;
type = "pdr.aspect";
if (a.getSemanticDim().getSemanticLabelByProvider("PDR").contains("NormName_DE"))
type+=".normname";
//setChildren(a.getValidation().getValidationStms().toArray());
} else if (content instanceof ValidationStm) {
ValidationStm validation = ((ValidationStm)content);
Reference reference = validation.getReference();
//TODO: WTF?
label = "";
this.content = reference;
type = "pdr.reference";
} else if (content instanceof ReferenceMods) {
ReferenceMods mods = (ReferenceMods)content;
this.content = mods;
label = mods.getDisplayName();
type = "pdr.reference.mods."+mods.getGenre().getGenre();
}
/* System.out.print(" created node "+label+" of type "+type);
if (parent != null) {
System.out.println(" with parent "+parent.label);
} else System.out.println();*/
}
/**
* Creates a new root node in the given {@link PdrObjectsPreviewStructure}.
* Calls {@link #StructNode(StructNode, Object, PdrObjectsPreviewStructure)}
* with null as a parent node.
* @param struct
* @param content
*/
public StructNode(Object content, PdrObjectsPreviewStructure struct) {
this(null, content, struct);
}
/**
* Instantiates new nodes for every object in the given array
* and links the results as children of this node.
* @see #addChild(Object)
*/
public void addChildren(Object[] objects){
if (objects.length < 1)
return;
for (Object obj : objects)
addChild(obj);
}
/**
* Tells whether this node has any children.
* @return
*/
public boolean hasChildren() {
return (this.children.size()>0);
}
/**
* Creates a new node containing the given object and links
* it as a child of this one.
* @param obj
* @return
*/
public StructNode addChild(Object obj){
StructNode child = getStructure().createNode(this, obj);
this.children.add(child);
return child;
}
/**
* Determines if given node is a sibling of this one, i.e.
* if both are either orphans or children of node with equivalent content.
* @param node candidate node to test
* @return true if both parents represent the same object
*/
public boolean isSiblingOf(StructNode node) {
if (this.parent == null)
return (node.parent == null);
else if (node.parent != null) {
if (this.parent.content instanceof OrderingHead)
if (node.parent.content instanceof OrderingHead)
return this.parent.type.equals(node.parent.type);
return this.parent.content.equals(node.parent.content);
}
return false;
}
/**
* Changes selection state of this node. When becoming selected,
* ancestors get selected as well, when de-selected, descendants
* are updated consistently.
* <p>Returns a list of those {@link StructNode}s whose selection
* flag is being changed during this operation, i.e. relatives of
* this node.</p>
* @param state
*/
public HashSet<StructNode> setSelected(boolean state) {
HashSet<StructNode> res = this.selectDescendants(state);
if (state) {
// walk up to root and select all nodes on the way
StructNode anc = this.parent;
while (anc != null && anc.selected != true) {
anc.selected = true;
res.add(anc);
anc = anc.parent;
}
}
return res;
}
/**
* Updates selection status of this node, maintains selection restrictions
* along ancestors and descendants and returns a list of all nodes that get their
* selection flag changed during this.
* @param state
* @return
*/
private HashSet<StructNode> selectDescendants(boolean state) {
//if (state != selected) {
// affected nodes
HashSet<StructNode> aff = new HashSet<StructNode>();
if (state != selected) {
// if selection state changed, register this node as being affected
selected = state;
aff.add(this);
// update child nodes accordingly
for (StructNode chld : this.children)
aff.addAll(chld.setSelected(state));
// consistency maintenance / invariant
// node is reference node, parent is aspect node
if (state == false)
if (this.content instanceof ReferenceMods && parent.content instanceof Aspect) {
if (parent.selected)
aff.add(this.parent);
parent.selected = false;
}
}
return aff;
}
/**
* Tells if this node is flagged as selected.
* @return
*/
public boolean isSelected() {
boolean res = selected;
// TODO: mögliches feature: angezeigter checkstate ist nicht der tatsächliche, so
//TODO daß bei deselektieren und dann wieder selektieren eines knotens die vorherige
//TODO markierungssituation wieder hergestellt würde. problem: laufzeit; oder speicher
/*StructNode anc = this.parent;
while (anc != null) {
res &= anc.selected;
anc = anc.parent;
}*/
return res;
}
/**
* Updates this node's expansion flag.
* Depending on if it is set to true or false,
* either all ancestors or all descendants are updated respectively
* to maintain
* consistency. This attribute may primarily be of concern when this node's tree
* is to be rendered and stuff.
* <p>Note that 'expanded' means that this node exposes its children,
* not only that it is shown itself.</p>
* @param state does node show its children or not?
* @see PdrSelectionFilterPreview
*/
public void setExpanded(boolean state) {
if (this.expanded != state) {
if (state) {
if (this.parent != null)
this.parent.setExpanded(true);
} else
if (this.children.size()>0) {
for (StructNode chld : this.children)
chld.setExpanded(false);
}
this.expanded = state;
}
}
/**
* Returns the current expansion state of this node.
* @return true if this node exposes its children, false
* if it is collapsed.
* @see PdrSelectionFilterPreview
*/
public boolean isExpanded() {
return expanded;
}
/**
* Returns a {@link Vector} of either all selected descendants of this node
* that do not have any selected descendants themselves, or only this node
* in case it is selected unlike any of its descendants. If it is not
* selected itself, its descendants are ignored entirely
* (since {@link #setSelected(boolean)} attempts to prevent orphan selections).
* @return
*/
protected Vector<StructNode> getSelectedDescendants() {
Vector<StructNode> sel = new Vector<StructNode>();
if (this.selected) {
if (this.children.size()>0)
for (StructNode chld : this.children)
sel.addAll(chld.getSelectedDescendants());
if (sel.size()<1)
sel.add(this);
}
return sel;
}
/**
* Returns a list of all nodes of this subtree that are flagged
* as expanded and don't have any expanded children. Apart from that,
* behaves exactly like
* {@link #getSelectedDescendants()}.
* @return all expanded nodes of the subtree rooted at this node
* which do not know expanded descendants themselves.
*/
protected Vector<StructNode> getExpandedDescendants() {
Vector<StructNode> exp = new Vector<StructNode>();
if (this.expanded) {
if (this.children.size()>0)
for (StructNode chld : this.children)
exp.addAll(chld.getExpandedDescendants());
if (exp.size()<1)
exp.add(this);
}
return exp;
}
/**
* Looks up which category/type the tree containing this node is of.
* To do so, this method recursively calls itself on the parent node
* until a root is arrived at.
* @return String identifier denoting what kind of group this node is part of
*/
public String getRootCategory() {
if (this.parent != null) {
if (this.type.startsWith("grouped."))
if (this.parent.type.startsWith("grouped."))
return this.parent.getRootCategory()+
this.type.substring(this.type.lastIndexOf("."));
else
return this.parent.getRootCategory()+"."+
this.type;
return this.parent.getRootCategory();
}
return this.type;
}
@Override
public int compareTo(StructNode o) {
return this.label.compareTo(o.label);
}
public StructNode getParent() {
return this.parent;
}
public StructNode getRoot() {
return this.root;
}
/**
* Find the {@link PdrObjectsPreviewStructure} this node belongs to by looking up the
* corresponding private field at the root node.
* @return {@link #getRoot()}.struct
*/
public PdrObjectsPreviewStructure getStructure() {
return getRoot().struct;
}
public Vector<StructNode> getChildren() {
return this.children;
}
public Object getContent() {
return this.content;
}
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
/**
* <p>Creates a deep copy of this {@link StructNode} attached to the given {@link PdrObjectsPreviewStructure}.
* <p>If this is a root node, this causes the modification of the copy's private <i>struct</i>
* and <i>root</i> fields as well as recursive copying of all descendants.</p>
*
* <p>If it's not, its ascendants will also be copied all the way up to the root, but not the siblings
* of the originally targeted node. If any ascendants appear to be already represented in the target
* structure, the copy will be attached to those.
* Calls {@link #interstructAnchor(PdrObjectsPreviewStructure)}.</p>
*
* <p>All newly created instances are automatically registered at the target structure, but
* none of them will be listed as root nodes at target structure.</p> TODO: why not? won"t hurt.
*
* @param structure a {@link PdrObjectsPreviewStructure} of which the node's copy is to be a root of.
* @return deep copy of this node, with all its parameters being the same as the original's,
* except for its {@link #getStructure()} and {@link #getRoot()} values (and inferred information).
* Returns <code>null</code> if this node is not a root.
*/
public StructNode copyIntoStructure(PdrObjectsPreviewStructure structure) {
// if not a root, don't even try
StructNode pn = (this.parent != null) ? interstructAnchor(structure) : null;
/*// potentially available parent node to attach copy to
StructNode pn = null;
// if this is not a root node
if (this.parent!=null) {
Vector<StructNode> desc = new Vector<StructNode>();
desc.add(this.parent);
// walk up and copy
while (desc.firstElement() != null) {
Vector<StructNode> r = structure.getNodesFor(desc.firstElement().content);
if (!r.isEmpty()) {
pn = r.firstElement();
break;
}
desc.add(0, desc.firstElement().parent);
}
for (StructNode d : desc) {
}
}*/
StructNode clone = structure.createNode(pn, this.content);
// recursively copy child nodes
for (StructNode child : this.children)
clone.children.add(child.copyIntoStructure(structure));
clone.expanded=this.expanded;
clone.label=this.label;
//clone.parent = pn;
//clone.root=this;
clone.selected=this.selected;
clone.type=this.type;
return clone;
}
/**
* Transfer a path of nested nodes from one structure to another
* @param structure
* @return
*/
private StructNode interstructAnchor(PdrObjectsPreviewStructure structure) {
Vector<StructNode> to_copy = new Vector<StructNode>();
StructNode anchor = null;
StructNode pn = this.parent;
// unless there is an anchor on the way, walk up to the root and copy whole path
while (pn != null) {
Vector<StructNode> pcandidates = structure.getNodesFor(pn.content);
// if still no anchor in sight, keep walking
if (pcandidates.isEmpty()) {
to_copy.add(0, pn);
pn = pn.parent;
} else // if potential anchors are availabnle, attach to first one coming up
anchor = pn;
}
// transfer path of unattached ancestors to target structure
// list of nodes to transfer begins with the one on highest level
pn = anchor;
for (StructNode node : to_copy) {
StructNode copy = structure.createNode(pn, node.content);
pn = copy;
}
return pn;
}
/**
*
* @param comp
*/
public void sort(NodeComparator ncomp) {
Collections.sort(this.children, ncomp);
}
}