//{HEADER
/**
* This class is part of jnex 'Nexirius Application Framework for Java'
*
* Copyright (C) Nexirius GmbH, CH-4450 Sissach, Switzerland (www.nexirius.ch)
*
* <p>This library is free software; you can redistribute it and/or<br>
* modify it under the terms of the GNU Lesser General Public<br>
* License as published by the Free Software Foundation; either<br>
* version 2.1 of the License, or (at your option) any later version.</p>
*
* <p>This library is distributed in the hope that it will be useful,<br>
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
* Lesser General Public License for more details.</p>
*
* <p>You should have received a copy of the GNU Lesser General Public<br>
* License along with this library; if not, write to the Free Software<br>
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA</p>
* </blockquote>
*
* <p>
* Nexirius GmbH, hereby disclaims all copyright interest in<br>
* the library jnex' 'Nexirius Application Framework for Java' written<br>
* by Marcel Baumann.</p>
*/
//}HEADER
package com.nexirius.framework.datamodel;
import com.nexirius.framework.FWLog;
import com.nexirius.framework.application.ErrorMessageException;
import com.nexirius.util.CopyPairs;
import com.nexirius.util.Sorter;
import com.nexirius.util.TextToken;
import com.nexirius.util.assertion.Assert;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
/**
* Superclass of all DataModel classes which contain children DataModel items.
*
* @author Marcel Baumann
*/
public abstract class DataModelContainer extends DataModel
{
public static final String TYPE = "TYPE";
public static final char OPENB = '{';
public static final char CLOSEB = '}';
DataModelEditListener myDataModelEditListener = null;
/**
* Create a new empty DataModelContainer
*/
public DataModelContainer() {
this(new DataModelVector());
}
/**
* Create a new DataModelContainer with an initial list of children
*
* @param v The vector which is used as the value of the new container
*/
public DataModelContainer(DataModelVector v) {
super(v);
}
/**
* Try to create a new member by class name. The member is no appended but only returned
*
* @param className The full class name which is needed to instanciate a new child instanc
* @param fieldName The field name of the new child
* @throws Exception If the class name cannot be instanciated
*/
public DataModel createMember(String className, String fieldName)
throws Exception {
DataModel member = (DataModel) Class.forName(className).newInstance();
member.setFieldName(fieldName);
return member;
}
/**
* Create and return a new iterator
*
* @return Always returns a valid iterator instance (eventually empty)
*/
public DataModelEnumeration getEnumeration() {
if (hasChildren()) {
DataModelVector v = getChildren();
synchronized (v) {
return new DataModelEnumeration(v.size(), v.iterator());
}
}
return new DataModelEnumeration();
}
/**
* Reads the persistent data from an input stream
*
* @param in The input where the data is read from
* @throws Exception When the input stream is not readable or corrupt
*/
public synchronized void readDataFrom(PushbackInputStream in)
throws Exception {
super.readDataFrom(in);
TextToken token = TextToken.nextToken(in);
if (!token.isChar(OPENB)) {
throw new Exception("DataModelContainer is not initialized with:'" + OPENB + "' have:" + token);
}
while (true) {
token = TextToken.nextToken(in);
if (token.isChar(CLOSEB)) {
break;
} else if (token.isString()) {
String name = token.getString();
try {
DataModel m = getChild(name);
m.readDataFrom(in);
} catch (IOException ioex) {
throw ioex;
} catch (Exception ex) {
ex.printStackTrace();
ignoreChild(name, in);
}
} else {
throw new Exception("Expecting " + CLOSEB + " or String literal but have:" + token);
}
}
}
/**
* Writes the whole container recursively to the output stream (persistence).
* Children which are transient (TRANSIENT flag) are skipped (so are their sub-children).
* ArrayModels ignore the TRANSIENT flags on their children.
*
* @param out The output stream
* @throws Exception If the stream is not writeable
*/
public synchronized void writeDataTo(OutputStream out)
throws Exception {
super.writeDataTo(out);
DataModelEnumeration e = getEnumeration();
out.write('{');
out.write('\n');
while (e.hasMore()) {
DataModel child = e.next();
if (!child.isTransient()) {
TextToken name = new TextToken(child.getFieldName());
name.writeTo(out);
out.write(' ');
child.writeDataTo(out);
out.write('\n');
}
}
out.write('}');
out.write('\n');
}
public void ignoreChild(String name, PushbackInputStream in)
throws Exception {
TextToken token;
int nest = 0;
FWLog.debug("Ignoring child " + name);
while (true) {
token = TextToken.nextToken(in);
if (token == null) {
break;
}
if (token.isChar(OPENB)) {
++nest;
} else if (token.isChar(CLOSEB)) {
--nest;
}
if (nest == 0) {
break;
}
}
}
/**
* Reads the persistent data from an input stream and creates new members
*
* @param in The input where the data is read from
* @throws Exception When the input stream is not readable or corrupt
*/
public synchronized void readInitDataFrom(PushbackInputStream in)
throws Exception {
TextToken token = TextToken.nextToken(in);
removeChildren();
if (!token.isChar(OPENB)) {
throw new Exception("DataModelContainer is not initialized with:'" + OPENB + "' have:" + token);
}
while (true) {
token = TextToken.nextToken(in);
if (token.isIdentifier(TYPE)) {
token = TextToken.nextToken(in);
String type = token.getString();
token = TextToken.nextToken(in);
String name = token.getString();
DataModel m = createMember(type, name);
if (m instanceof DataModelContainer) {
((DataModelContainer) m).readInitDataFrom(in);
} else {
m.readDataFrom(in);
}
append(m);
} else if (token.isChar(CLOSEB)) {
break;
} else {
throw new Exception("Expecting " + TYPE + " or " + CLOSEB + " but have:" + token);
}
}
}
/**
* Create a persistent text representation of this instance (used to initialize)
*
* @param out The stream to which the data is written
* @throws Exception If the stream can not be accessed (write access)
*/
public void writeInitDataTo(OutputStream out)
throws Exception {
TextToken type = new TextToken(TYPE, TextToken.IDENTIFIER);
DataModelEnumeration e = getEnumeration();
out.write('{');
out.write('\n');
while (e.hasMore()) {
DataModel child = e.next();
TextToken classname = new TextToken(child.getClass().getName());
TextToken name = new TextToken(child.getFieldName());
type.writeTo(out);
out.write(' ');
classname.writeTo(out);
out.write(' ');
name.writeTo(out);
out.write(' ');
if (child instanceof DataModelContainer) {
((DataModelContainer) child).writeInitDataTo(out);
} else {
child.writeDataTo(out);
}
out.write('\n');
}
out.write('}');
out.write('\n');
}
/**
* Get a tree hook instance which helps to display the hierarchie in a swing tree.
*
* @return a new (only on the first call) ContainerTreeHook instance (never null)
*/
public DataModelTreeNode getTreeHook() {
if (treeHook == null) {
treeHook = new ContainerTreeHook(this);
}
return treeHook;
}
/**
* A method which is not defined by the container but which is very useful for some user defined subclassed.
* It is called every time a DataModel change event is fired by one of the containers children.
*/
public void childValueChanged(DataModel child) {
//FWLog.debug("CHILD CHANGED parent = " + getFieldName() + " child = " + (child == null ? "null" : child.getFieldName()));
}
/**
* DataModel listener interface. (Do not redefine unless you call the parent method from the rdefined function)
*/
public void dataModelChangeValue(DataModelEvent event) {
/*
if (event.fromChild()) {
propagateChildEvent(event);
childValueChanged(event.getTransmitter());
} else {
fireChildValueChange(event.getDataModel());
childValueChanged(event.getDataModel());
}
*/
}
/**
* DataModel listener interface. (Do not redefine unless you call the parent method from the rdefined function)
*/
public void dataModelChangeStructure(DataModelEvent event) {
propagateChildEvent(event);
}
/**
* DataModel listener interface. (Do not redefine unless you call the parent method from the rdefined function)
*/
public void dataModelEdit(DataModelEvent event) {
// propagateChildEvent(event);
}
/**
* DataModel listener interface. (Do not redefine unless you call the parent method from the rdefined function)
*/
public void dataModelGrabFocus(DataModelEvent event) {
/*
//System.out.println("dataModelGrabFocus on " + getFieldName() + " f child:" + event.fromChild());
if (event.fromChild()) {
propagateChildEvent(event);
} else {
fireGrabChildFocus(event.getDataModel());
}
*/
}
protected void renumber(int from, int to) {
// empty
}
/**
* Accept a new DataModelVector as the new value.
*/
public synchronized void setValue(Object newValue) {
setFireEvents(false);
DataModelEnumeration e = getEnumeration();
int index = 0;
while (e.hasMore()) {
looseItem(e.next(), index++);
}
super.setValue(newValue);
e = getEnumeration();
index = 0;
while (e.hasMore()) {
gainItem(e.next(), index++);
}
setFireEvents(true);
fireStructureChange();
}
/**
* Accept a new child (define its new parent) and add myself to its listener list.
*/
private void gainItem(DataModel item, int index) {
if (item == null) {
return;
}
item.setParentDataModelContainer(this);
if (this instanceof ArrayModel) {
fireValueChange(this, new DataModelEvent(this, DataModelEvent.VALUE_CHANGE, DataModelEvent.SUBTYPE_ITEM_ADDED, index, item));
} else {
fireStructureChange();
}
}
/**
* Release a child (undefine its parent) and remove myself fromits listener list.
*/
private void looseItem(DataModel item, int index) {
item.setParentDataModelContainer(null);
if (this instanceof ArrayModel) {
fireValueChange(this, new DataModelEvent(this, DataModelEvent.VALUE_CHANGE, DataModelEvent.SUBTYPE_ITEM_REMOVED, index, item));
} else {
fireStructureChange();
}
}
/**
* Append a new child (to the end of the list) and set its parent data model.
*
* @param item The new child which is added
*/
public synchronized void append(DataModel item) {
insertItemAt(item, getChildren().size());
}
/**
* Insert a new child at a sorted position (Only works if the Sorter interface is defined properly)
*
* @param item The new child
* @return the index of the new child
*/
public synchronized int sortInsert(DataModel item) {
return sortInsert(item, item);
}
public synchronized int sortInsert(DataModel item, Sorter sorter) {
if (item == null) {
return -1;
}
int index = getChildren().sortInsert(item, sorter);
gainItem(item, index);
renumber(index, getChildren().size() - 1);
return index;
}
/**
* Sort members (Only works if the Sorter interface is defined properly)
*/
public synchronized void sort() {
if (getChildren().size() < 2) {
return;
}
DataModel item = getChildren().firstItem();
if (getChildren().sort(item)) {
renumber(0, getChildren().size() - 1);
fireStructureChange();
}
}
public synchronized void sort(Sorter sorter) {
if (getChildren().size() < 2) {
return;
}
getChildren().sort(sorter);
renumber(0, getChildren().size() - 1);
fireStructureChange();
}
/**
* Inserts a child at a specified position
*
* @param item The new child
* @param index The index where it is inserted
*/
public synchronized void insertItemAt(DataModel item, int index) {
if (item == null) {
return;
}
getChildren().insertElementAt(item, index);
gainItem(item, index);
renumber(index, getChildren().size() - 1);
}
/**
* Remove a child from this container and reset its parent and listener list.
*
* @param child The child which looses its parent
*/
public synchronized boolean removeItem(DataModel child) {
if (hasChildren()) {
int index = getChildren().indexOf(child);
return null != removeItemAt(index);
}
return false;
}
/**
* Remove a child at a specified position
*
* @param index The index where the child will be removed
*/
public synchronized DataModel removeItemAt(int index) {
if (index >= 0 && hasChildren()) {
DataModel item = getChildren().getItem(index);
getChildren().removeElementAt(index);
looseItem(item, index);
renumber(index, getChildren().size() - 1);
return item;
}
return null;
}
/**
* Get rid of all the children at once. This method does not reset the childrens parents and listener lists.
* On ArrayModel this method throws a DataModelEvent.VALUE_CHANGE, DataModelEvent.SUBTYPE_CLEAR
*/
public void removeChildren() {
// FIX this function does not reset the childrens parent field and listener list
getChildren().removeAllElements();
if (this instanceof ArrayModel) {
fireValueChange(this, new DataModelEvent(this, DataModelEvent.VALUE_CHANGE, DataModelEvent.SUBTYPE_CLEAR, -1, null));
((ArrayModel) this).setHighlightedItem(-1);
} else {
fireStructureChange();
}
}
/**
* Does this container have one or more children
*/
public boolean hasChildren() {
return getChildren().size() > 0;
}
/**
* Fire a grab focus from a specified child (at index)
*
* @param index The index position of the child which will fire the grab focus event
*/
public void grabFocus(int index) {
DataModelVector v = getChildren();
DataModel child = null;
if (v != null && v.size() > index) {
child = v.getItem(index);
}
if (child != null) {
child.grabFocus();
}
}
/**
* Compares two containers recursively
*/
public synchronized boolean equals(Object other) {
if (other == this) {
return true;
}
return false;
}
/**
* Compare two field names
*
* @param m1 The first DataModel from which the fieldname is taken
* @param m2 The second DataModel from which the fieldname is taken
*/
public static boolean sameFieldName(DataModel m1, DataModel m2) {
String s1 = m1.getFieldName();
String s2 = m2.getFieldName();
if (s1 == s2) {
return true;
}
if (s1 != null) {
return s1.equals(s2);
}
return false;
}
/**
* Assign a new value to this container. If the structures of both containers are
* the same (same children with same names) then the target structure is not changed
* only the values which are not equal are overwritten. When the source container has additional
* elements the these elements are duplicated and added to the target container. When the source
* container has less elements than the target container then the children, which are not
* in both containers are removed from the target container.
*
* @param newvalue The value of the source container (target.assignValue(source.getValue())
*/
public synchronized void assignValue(Object newvalue) {
Assert.assertion(newvalue != null, "newvalue may not be null");
Assert.assertion(newvalue instanceof DataModelVector, "Can't assign " + newvalue.getClass().getName() + " to a DataModelContainer");
if (value == null) {
value = newvalue;
return;
}
if (!value.equals(newvalue)) {
DataModelVector nv = new DataModelVector((DataModelVector) newvalue);
DataModelVector ov = new DataModelVector(getChildren());
DataModel nm = nv.firstItem();
DataModel om = ov.firstItem();
boolean needFire = false;
int index = 0;
while (nm != null && om != null) {
if (sameFieldName(om, nm)) {
if (!om.equals(nm)) {
om.assignValue(nm.getValue());
om.setStatus(nm.getStatus());
}
} else {
needFire = true;
setFireEvents(false);
try {
setChild(index, nm);
} catch (Exception ex) {
// this exception should never be fired
ex.printStackTrace();
}
setFireEvents(true);
}
++index;
nm = nv.nextItem();
om = ov.nextItem();
}
// if this is an array we have to add new intances to
// the existing array if needed or delete instances if needed
if (om != null || nm != null) {
needFire = true;
setFireEvents(false);
while (om != null) {
getChildren().removeItem(om);
om = ov.nextItem();
}
while (nm != null) {
getChildren().append(nm);
nm = nv.nextItem();
}
setFireEvents(true);
}
if (needFire) {
fireStructureChange();
}
fireValueChange();
} else {
// values are equal
}
}
public synchronized void assignDuplicate(DataModel duplicate, CopyPairs copyPairs) {
DataModelEnumeration en = getEnumeration();
while (en.hasMore()) {
DataModel child = en.next();
DataModel other = (DataModel) copyPairs.getCopy(child);
if (other == null) {
System.out.println("copyPairs = " + copyPairs);
throw new ErrorMessageException("No duplicate value of child " + child.getFieldName(), null, null);
}
child.assignDuplicate(other, copyPairs);
}
setStatus(duplicate.getStatus());
}
/**
* Replace an existing child with a new instance
*
* @param fieldName The name of the existing child
* @param newChild The replacing new child
* @throws Exception If the child was not found
*/
public void setChild(String fieldName, DataModel newChild)
throws Exception {
setChild(getChild(fieldName), newChild);
}
/**
* Override an existing child by a new instance
*
* @param oldChild The reference of the existing child
* @param newChild The replacing new child
* @throws Exception If the child was not found
*/
public synchronized void setChild(DataModel oldChild, DataModel newChild)
throws Exception {
if (oldChild == null) {
append(newChild);
} else {
if (oldChild != newChild) {
setChild(getChildIndex(oldChild), newChild);
}
}
}
/**
* Override an existing child by a new instance
*
* @param index The position of the existing child
* @param newChild The replacing new child
* @throws Exception If there is no child at the specified index
*/
public synchronized void setChild(int index, DataModel newChild)
throws Exception {
if (newChild == null) {
return;
}
DataModel oldChild = (DataModel) getChildren().set(index, newChild);
looseItem(oldChild, index);
gainItem(newChild, index);
renumber(index, index);
}
/**
* Recursively creates a string representation (not portable) for debugging
*/
public String toString() {
StringBuffer b = new StringBuffer();
StringBuffer offset = new StringBuffer();
int level = getLevel();
while (level-- > 0) {
offset.append("\t");
}
b.append(offset + getTreeHook().getLabel());
if (hasChildren()) {
b.append(" {\n");
for (DataModel i = getChildren().firstItem(); i != null; i = getChildren().nextItem()) {
b.append(i);
b.append("\n");
}
b.append(offset + "}");
}
b.append("\n");
return b.toString();
}
/**
* Get a list reference to the actual children (Do not change this list from outside)
*/
public DataModelVector getChildren() {
return (DataModelVector) getValue();
}
/**
* Create a deep copy of the children list
*
* @param copyPairs A list which avoids creating two copies of the same instance (not null)
*/
public Object cloneValue(CopyPairs copyPairs) {
return getChildren().duplicate(copyPairs);
}
/**
* Sets the parents of all children to this
*/
public synchronized void reassignParent() {
DataModelEnumeration e = getEnumeration();
while (e.hasMore()) {
DataModel m = e.next();
m.setParentDataModelContainer(this);
// m.addDataModelListener(getDataModelEditListener());
}
}
// public DataModelEditListener getDataModelEditListener() {
// return new MyDataModelEditListener();
// }
//
// protected DataModelEditListener getChildDataModelEditListener() {
// if (myDataModelEditListener == null) {
// myDataModelEditListener = new MyDataModelEditListener();
// }
//
// return myDataModelEditListener;
// }
//
// class MyDataModelEditListener implements DataModelEditListener {
// public void dataModelChangeValue(DataModelEvent event) {
// DataModelContainer.this.dataModelChangeValue(event);
// }
//
// public void dataModelChangeStructure(DataModelEvent event) {
// DataModelContainer.this.dataModelChangeStructure(event);
// }
//
// public void dataModelGrabFocus(DataModelEvent event) {
// DataModelContainer.this.dataModelGrabFocus(event);
// }
//
// public void dataModelEdit(DataModelEvent event) {
// DataModelContainer.this.dataModelEdit(event);
// }
// }
}