//{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.datamodel.parser.DataModelClasses;
import com.nexirius.util.CopyPairs;
import com.nexirius.util.Sorter;
import com.nexirius.util.StringVector;
import com.nexirius.util.TextToken;
import com.nexirius.util.assertion.Assert;
import com.nexirius.util.textresource.TextResource;
import com.nexirius.util.textresource.TextResourceEvent;
import com.nexirius.util.textresource.TextResourceListener;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.util.Hashtable;
/**
* This class represents an array of DataModel items. The items which are appended
* to this container should all be from the same data type. The field name information
* is used to create and identify the columns which are displayed in a table.
*
* @author Marcel Baumann
*/
public class ArrayModel extends DataModelContainer {
FieldName memberFieldNames;
FieldNameVector fields;
MyTableModel tableModel;
DataModelClasses classes = null;
String memberType = null;
int highlightedItem = -1;
DataModelVector deleted = null;
private DataModel template;
/**
* Create a new ArrayModel which contains the specified member field names (used for table display).
* Normally an array is created using the getFullFieldName() method on a DataModel which represents
* the typical future member of the newly created array instance (member type).
* <pre>
* class AddressArrayModel extends ArrayModel
* {
* public AddressArrayModel()
* {
* super(new AddressModel().getFullFieldName());
* }
* }
* </pre>
*
* @param memberFieldNames used to identify column titles in a table
*/
public ArrayModel(FieldName memberFieldNames) {
setMemberFieldNames(memberFieldNames);
}
/**
* Create a new ArrayModel which contains the specified member field names (used for table display).
* Normally an array is created using the getFullFieldName() method on a DataModel which represents
* the typical future member of the newly created array instance (member type).
* <pre>
* class AddressArrayModel extends ArrayModel
* {
* public AddressArrayModel()
* {
* super(new AddressModel().getFullFieldName(), "AddressArray");
* }
* }
* </pre>
*
* @param memberFieldNames The field names of the typical member class (used for column names of default ArrayViewer)
* @param fieldName the field name of the array
*/
public ArrayModel(FieldName memberFieldNames, String fieldName) {
setMemberFieldNames(memberFieldNames);
setFieldName(fieldName);
}
/**
* Create a new ArrayModel which contains the specified template field names (used for table display).
* The array is created using the getFullFieldName() method on the DataModel which represents
* the typical future template of the newly created array instance (member type).
*
* @param template constructor calls getFullFieldName() on this instance
* @param fieldName the field name of the array
*/
public ArrayModel(DataModel template, String fieldName) {
setMemberFieldNames(template.getFullFieldName());
setFieldName(fieldName);
this.template = template;
}
/**
* creates a new instance of an empty array model.
*
* @param c the list of all available types
* @param t the type of the array members as string
*/
public ArrayModel(DataModelClasses c, String t) {
classes = c;
memberType = t;
try {
setMemberFieldNames(classes.newInstance(memberType, null).getFullFieldName());
} catch (Exception ex) {
ex.printStackTrace();
}
}
public DataModel getTemplate() {
return template;
}
public synchronized void assignDuplicate(DataModel duplicate, CopyPairs copyPairs) {
DataModelEnumeration enDuplicate = duplicate.getEnumeration();
DataModelEnumeration enOrig = getEnumeration();
CopyPairs cp = null;
boolean valueChanged = false;
CopyPairs origChildren = null;
deleted = null;
if (((ArrayModel) duplicate).getSize() != getSize()) {
valueChanged = true;
}
DataModelVector newChildren = new DataModelVector();
while (enDuplicate.hasMore()) {
DataModel duplicateChild = enDuplicate.next();
DataModel origChild = (DataModel) copyPairs.getOrig(duplicateChild);
if (origChild == null) {
valueChanged = true;
if (cp == null) {
cp = new CopyPairs();
}
newChildren.append(duplicateChild.duplicate(null, cp));
} else {
if (origChildren == null) {
origChildren = new CopyPairs();
}
origChild.assignDuplicate(duplicateChild, copyPairs);
origChildren.put(origChild, origChild);
origChild.setParentDataModelContainer(null);
newChildren.append(origChild);
}
if (!valueChanged) {
// check, whether the order has changed
DataModel origChildAtPos = enOrig.next();
if (origChildAtPos != origChild) {
valueChanged = true;
}
}
}
if (valueChanged) {
// store the deleted instances
DataModelEnumeration en = getEnumeration();
while (en.hasMore()) {
DataModel model = en.next();
if (origChildren == null || origChildren.getCopy(model) == null) {
if (deleted == null) {
deleted = new DataModelVector();
}
deleted.append(model);
}
}
value = newChildren;
reassignParent();
fireValueChange();
// clear();
// DataModelEnumeration en2 = newChildren.getEnumeration();
//
// while (en2.hasMore()) {
// DataModel item = en2.next();
// item.setParentDataModelContainer(null);
// append(item);
// }
} else {
reassignParent();
}
highlightedItem = ((ArrayModel) duplicate).highlightedItem;
}
/**
* Access the instances which have been removed apon last call to assignDuplicate (e.g. when OK was pressed in a dialog window).
*
* @return all deleted instances
*/
public DataModelVector getDeleted() {
return deleted;
}
/*
* removes all array elements
*/
public void clear() {
setHighlightedItem(-1);
removeChildren();
}
public String getCaption() {
return TextToken.quoteString(getFieldName());
}
public DataModel createDefaultChild() {
if (memberType == null) {
return null;
}
try {
return classes.newInstance(memberType, null);
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
public DataModel insertDefaultChild(int index) {
DataModel ret = createDefaultChild();
if (ret != null) {
insertItemAt(ret, index);
}
return ret;
}
public void setHighlightedItem(int index) {
if (index != highlightedItem) {
highlightedItem = index;
fireHighlightChange();
}
}
public void setHighlightedItem(DataModel child) {
int index = getChildren().indexOf(child);
if (index != highlightedItem) {
highlightedItem = index;
fireHighlightChange();
}
}
public void fireHighlightChange() {
fireEditEvent(this, DataModelEvent.HIGHLIGHT_CHANGED, null);
}
public DataModel getHighlightedItem() {
if (highlightedItem >= 0) {
return getDataModel(highlightedItem);
}
return null;
}
public int getHighlightedItemIndex() {
return highlightedItem;
}
public DataModel removeHighlightedItem() {
if (highlightedItem >= 0) {
int hi = highlightedItem;
setHighlightedItem(-1);
return removeItemAt(hi);
}
return null;
}
/**
* Set the fieldnames to represent array index
*/
protected synchronized void renumber(int from, int to) {
if (from == to) {
getChildren().getItem(from).setFieldName(from);
} else {
DataModelEnumeration e = getEnumeration();
for (int index = 0; e.hasMore() && index <= to; ++index) {
DataModel child = e.next();
if (index >= from) {
child.setFieldName(index);
}
}
}
}
/**
* 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 {
TextToken token = TextToken.nextToken(in);
setHighlightedItem(-1);
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);
m.readDataFrom(in);
append(m);
} else if (token.isChar(CLOSEB)) {
break;
} else {
throw new Exception("Expecting " + TYPE + " or " + CLOSEB + " but have:" + token);
}
}
}
/**
* Introduce the fieldnames of a typical member
*
* @param memberFieldNames used to identify column titles in a table
*/
public void setMemberFieldNames(FieldName memberFieldNames) {
this.memberFieldNames = memberFieldNames;
this.fields = new FieldNameVector();
if (this.memberFieldNames != null) {
this.memberFieldNames.getFieldNameVector(this.fields);
}
}
/**
* Get the registered member field names
*/
public FieldName getMemberFieldNames() {
return this.memberFieldNames;
}
/**
* Create a persistent text representation of this instance
*
* @param out The stream to which the data is written
* @throws Exception If the stream can not be accessed (write access)
*/
public void writeDataTo(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(' ');
child.writeDataTo(out);
out.write('\n');
}
out.write('}');
out.write('\n');
}
/**
* Get the current number of elements in the array
*
* @return 0 or bigger
*/
public int getSize() {
return getChildren().size();
}
/**
* The fieldnames of array members are not used for identification. Each array member
* is identified by its position index (0 or bigger) within the array.
*
* @param fieldName The index of a child as string (e.g. "4" for the 5th member)
* @throws Exception If the specified field was not found
*/
public synchronized DataModel getChild(String fieldName)
throws Exception {
if (fieldName.indexOf(FIELD_NAME_SEPARATOR_CHAR) >= 0) {
return super.getChild(getFieldNameArray(fieldName));
}
int index = 0;
try {
index = Integer.parseInt(fieldName);
} catch (Exception ex) {
throw new Exception("ArrayModel fields are accessed with names which are integer numbers but have: '" + fieldName + "' in " + getFieldName());
}
return getDataModel(index);
}
/**
* Get member by index
*
* @param i The index between 0 and size()-1
* @return The Model at the specified position (or an array index exception is thrown)
*/
public DataModel getDataModel(int i) {
if (i < 0 || i >= getSize()) {
return null;
}
return getChildren().getItem(i);
}
/**
* The array is an exception to the rule that all fieldnames are recursively returned
*
* @return the direct field name of the array (not members are analysed)
*/
public FieldName getFullFieldName() {
Assert.pre(this.fieldName != null, "this.fieldName is not null");
return this.fieldName;
}
/**
* Get a swing table model which represents the data in this array
*
* @param text_source The text source is needed to translate the column names into the appropriate table column title string
*/
public TableModel getTableModel(TextResource text_source) {
if (this.tableModel == null) {
this.tableModel = new MyTableModel(text_source);
// text_source.addTextResourceListener(this.tableModel);
}
return this.tableModel;
}
/**
* Child value change events are used to update the information in the table model
*
* @param child The child which has changed
*/
public void childValueChanged(DataModel child) {
if (this.tableModel != null) {
try {
this.tableModel.rowChanged(getChildIndex(child));
} catch (Exception ex) {
// ignore
}
}
}
/**
* Create a deep copy of this array instance.
*
* @param instance null (a new instance is created) or the subclass copy instance
* @param copyPairs null (copyPairs is generated internally) or the copy pairs instace of the subclass
*/
public DataModel duplicate(DataModel instance, CopyPairs copyPairs) {
if (instance == null) {
try {
instance = (DataModel) getClass().getConstructor(null).newInstance(null);
} catch (Exception ex) {
System.err.println("Need public default constructor (with no arguments) in " + getClass().getName());
if (template != null) {
instance = new ArrayModel(memberFieldNames);
} else {
instance = new ArrayModel(template, getFieldName());
}
}
}
if (copyPairs == null) {
copyPairs = new CopyPairs();
}
ArrayModel ret = (ArrayModel) super.duplicate(instance, copyPairs);
ret.highlightedItem = highlightedItem;
ret.reassignParent();
return ret;
}
/**
* only used by duplicate
*
* @param orig
* @param copyPairs
*/
protected void assignModel(DataModel orig, CopyPairs copyPairs) {
DataModelEnumeration en = orig.getEnumeration();
if (getSize() > 0) {
removeChildren();
}
while (en.hasMore()) {
DataModel origChild = en.next();
try {
DataModel newChild = origChild.duplicate(null, copyPairs);
append(newChild);
copyPairs.put(origChild, newChild);
} catch (Exception e) {
e.printStackTrace(); //TODO
}
}
setStatus(orig.getStatus());
}
/**
* Structure changed events are translated into value changed events (when the structure of an array
* changes it usually has changed its number of members)
*/
public void fireStructureChange() {
fireValueChange();
if (this.tableModel != null) {
this.tableModel.reload();
}
}
/**
* Access a table containig all entries by instance name
*/
public Hashtable getInstanceNameTable() {
Hashtable ret = new Hashtable(getSize());
DataModelEnumeration en = getEnumeration();
while (en.hasMore()) {
DataModel model = en.next();
ret.put(model.getInstanceName(), model);
}
return ret;
}
/**
* Access a child by instance name
* @return null or the first model which holds the given instance name
*/
public DataModel getChildByInstanceName(String instanceName) {
DataModelEnumeration en = getEnumeration();
while (en.hasMore()) {
DataModel model = en.next();
if (instanceName.equals(model.getInstanceName())) {
return model;
}
}
return null;
}
/**
* Set the given flag on each data model depending on the return value of the given filter
*
* @param flag
* @param filter
*/
public void filter(ModelFlag flag, DataModelFilter filter) {
DataModelEnumeration e = getEnumeration();
while (e.hasMore()) {
DataModel model = e.next();
model.setFlag(flag, filter.filter(model));
}
}
public void sortByAttribute(String childName) {
Sorter sorter = new AttributeSorter(childName);
sort(sorter);
}
public void sortInsertByAttribute(DataModel model, String childName) {
Sorter sorter = new AttributeSorter(childName);
sortInsert(model, sorter);
}
public void sortByAttributeDesc(String childName) {
Sorter sorter = new AttributeSorter(childName, true);
sort(sorter);
}
public static class AttributeSorter implements Sorter {
String childName;
boolean desc = false;
public AttributeSorter(String childName) {
this.childName = childName;
}
public AttributeSorter(String childName, boolean desc) {
this.childName = childName;
this.desc = desc;
}
public int compare(Object model1, Object model2) {
try {
int ret = 0;
DataModel m1 = ((DataModel) model1).getChild(childName);
DataModel m2 = ((DataModel) model2).getChild(childName);
if (m1 instanceof SimpleModel) {
if (((SimpleModel) m1).getSimpleType().wrap() instanceof Comparable) {
Comparable c1 = (Comparable) ((SimpleModel) m1).getSimpleType().wrap();
Comparable c2 = (Comparable) ((SimpleModel) m2).getSimpleType().wrap();
if (c1 instanceof String) {
ret = ((String) c2).compareToIgnoreCase((String) c1);
} else {
ret = c2.compareTo(c1);
}
} else {
String s1 = ((SimpleModel) m1).getSimpleType().wrap().toString();
String s2 = ((SimpleModel) m2).getSimpleType().wrap().toString();
ret = s2.compareToIgnoreCase(s1);
}
} else {
ret = m1.compare(m1, m2);
}
if (desc) {
return -ret;
} else {
return ret;
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
/**
* internally used class to communicate with swing tables
*/
class MyTableModel extends AbstractTableModel implements TextResourceListener {
TextResource m_text_source;
MyTableModel(TextResource text_source) {
m_text_source = text_source;
}
public void textResourceChanged(TextResourceEvent event) {
if (event.getType() == TextResourceEvent.LOCALE_CHANGED) {
fireTableStructureChanged();
}
}
public int getColumnCount() {
return fields.size();
}
public int getRowCount() {
return getChildren().size();
}
public Object getValueAt(int y, int x) {
try {
DataModel model = getChildren().getItem(y);
if (x < 0) {
return model;
}
FieldName fieldName = fields.getFieldName(x);
StringVector sv = new StringVector();
while (fieldName != null) {
sv.insertElementAt(fieldName.toString(), 0);
fieldName = fieldName.getParent();
}
sv.removeElementAt(0);
String names[] = sv.getArray();
return model.getChild(names);
} catch (Exception ex) {
ex.printStackTrace();
return "XXX";
}
}
public String getColumnName(int x) {
String ret = fields.getFieldName(x).toString();
try {
String s = m_text_source.getText(ret);
if (s != null) {
ret = s;
}
} catch (Exception ex) {
}
if (ret == null) {
return "xxx";
}
return ret;
}
public void rowChanged(int row) {
fireTableRowsUpdated(row, row);
}
public void reload() {
fireTableDataChanged();
}
/**
* remove item and put it to the deleted list
*
* @param child
* @return true if item was found and added to the deleted item list
*/
public boolean deleteItem(DataModel child) {
if (child == null) {
return false;
}
if (highlightedItem >= 0 && getHighlightedItem() == child) {
setHighlightedItem(-1);
}
if (removeItem(child)) {
if (deleted == null) {
deleted = new DataModelVector();
}
deleted.append(child);
return true;
}
return false;
}
class EventSender implements Runnable {
TableModelEvent event;
EventSender(TableModelEvent event) {
event = this.event;
}
public void run() {
immediatelyFireTableChanged(event);
}
}
public void fireTableChanged(TableModelEvent e) {
SwingUtilities.invokeLater(new EventSender(e));
}
public void immediatelyFireTableChanged(TableModelEvent e) {
super.fireTableChanged(e);
}
}
}