package prefuse.data.column;
import java.util.Date;
import prefuse.data.DataTypeException;
import prefuse.data.event.ColumnListener;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ObjectParser;
import prefuse.data.parser.ParserFactory;
import prefuse.util.TypeLib;
import prefuse.util.collections.CopyOnWriteArrayList;
/**
* Abstract base class for Column implementations. Provides listener support
* and default implementations of column methods.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public abstract class AbstractColumn implements Column {
protected final Class m_columnType;
protected DataParser m_parser;
protected Object m_defaultValue;
protected boolean m_readOnly;
protected CopyOnWriteArrayList m_listeners;
/**
* Create a new AbstractColumn of type Object.
*/
public AbstractColumn() {
this(Object.class, null);
}
/**
* Create a new AbstractColumn of a given type.
* @param columnType the data type stored by this column
*/
public AbstractColumn(Class columnType) {
this(columnType, null);
}
/**
* Create a new AbstractColumn of a given type.
* @param columnType the data type stored by this column
* @param defaultValue the default data value to use
*/
public AbstractColumn(Class columnType, Object defaultValue) {
m_columnType = columnType;
DataParser p = ParserFactory.getDefaultFactory().getParser(columnType);
m_parser = ( p==null ? new ObjectParser() : p );
setDefaultValue(defaultValue);
m_readOnly = false;
m_listeners = new CopyOnWriteArrayList();
}
// ------------------------------------------------------------------------
// Column Metadata
/**
* Indicates if the values in this column are read-only.
* @return true if the values can not be edited, false otherwise
*/
public boolean isReadOnly() {
return m_readOnly;
}
/**
* Sets if the values in this column are read-only
* @param readOnly true to ensure the values can not be edited,
* false otherwise
*/
public void setReadOnly(boolean readOnly) {
m_readOnly = readOnly;
} //
/**
* Indicates if the value at the given row can be edited.
* @param row the row to check
* @return true is the value can be modified, false otherwise
*/
public boolean isCellEditable(int row) {
return !m_readOnly;
}
/**
* Returns the most specific superclass for the values in the column
* @return the Class of the column's data values
*/
public Class getColumnType() {
return m_columnType;
}
/**
* @see prefuse.data.column.Column#getParser()
*/
public DataParser getParser() {
return m_parser;
}
/**
* @see prefuse.data.column.Column#setParser(prefuse.data.parser.DataParser)
*/
public void setParser(DataParser parser) {
if ( !m_columnType.isAssignableFrom(parser.getType()) ) {
throw new IllegalArgumentException(
"Parser type ("+parser.getType().getName()+") incompatible with"
+" this column's data type ("+m_columnType.getName()+")");
}
m_parser = parser;
}
// ------------------------------------------------------------------------
// Listener Methods
/**
* Adds a listener to be notified when this column changes
* @param listener the ColumnListener to add
*/
public void addColumnListener(ColumnListener listener) {
m_listeners.add(listener);
}
/**
* Removes a listener, causing it to no longer be notified of changes
* @param listener the ColumnListener to remove
*/
public void removeColumnListener(ColumnListener listener) {
m_listeners.remove(listener);
}
/**
* Notifies all registered listeners of a column UPDATE event
*/
protected final void fireColumnEvent(int type, int start, int end) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, type, start, end);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, int prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, long prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, float prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, double prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, boolean prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
/**
* Notifies all registered listeners of a column UPDATE event
* @param idx the row index of the column that was updated
* @param prev the previous value at the given index
*/
protected final void fireColumnEvent(int idx, Object prev) {
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i )
((ColumnListener)lstnrs[i]).columnChanged(this, idx, prev);
}
// ------------------------------------------------------------------------
// Data Access Methods
/**
* Returns the default value for rows that have not been set explicitly.
*/
public Object getDefaultValue() {
return m_defaultValue;
}
/**
* Sets the default value for this column. Rows previously added
* under a different default value will not be changed as a result
* of this method; the new default will apply to newly added rows
* only.
* @param dflt
*/
public void setDefaultValue(Object dflt) {
boolean prim = m_columnType.isPrimitive();
if ( dflt != null &&
((!prim && !m_columnType.isInstance(dflt)) ||
(prim && !TypeLib.isWrapperInstance(m_columnType, dflt))) )
{
throw new IllegalArgumentException(
"Default value is not of type " + m_columnType.getName());
}
m_defaultValue = dflt;
}
/**
* Reverts the specified row back to the column's default value.
* @param row
*/
public void revertToDefault(int row) {
set(m_defaultValue, row);
}
/**
* Indicates if the get method can be called without
* an exception being thrown for the given type.
* @param type the Class of the data type to check
* @return true if the type is supported by this column, false otherwise
*/
public boolean canGet(Class type) {
if ( type == null ) return false;
if ( m_columnType.isPrimitive() ) {
boolean primTypes = type.isAssignableFrom(m_columnType) ||
(TypeLib.isNumericType(m_columnType)
&& TypeLib.isNumericType(type));
return primTypes
|| type.isAssignableFrom(TypeLib.getWrapperType(m_columnType))
|| type.isAssignableFrom(String.class);
} else {
return type.isAssignableFrom(m_columnType);
}
}
/**
* Indicates if the set method can be called without
* an exception being thrown for the given type.
* @param type the Class of the data type to check
* @return true if the type is supported by this column, false otherwise
*/
public boolean canSet(Class type) {
if ( type == null ) return false;
if ( m_columnType.isPrimitive() ) {
return m_columnType.isAssignableFrom(type)
|| TypeLib.getWrapperType(m_columnType).isAssignableFrom(type)
|| String.class.isAssignableFrom(type);
} else {
return m_columnType.isAssignableFrom(type);
}
}
// ------------------------------------------------------------------------
// Data Type Convenience Methods
// because java's type system can be tedious at times...
// -- int -----------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the int type.
* @return true if getInt is supported, false otherwise
*/
public boolean canGetInt() {
return canGet(int.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the int type.
* @return true if setInt is supported, false otherwise
*/
public boolean canSetInt() {
return canSet(int.class);
}
/**
* Get the data value at the specified row as an integer
* @param row the row from which to retrieve the value
* @return the data value as an integer
* @throws DataTypeException if this column does not
* support the integer type
*/
public int getInt(int row) throws DataTypeException {
if ( canGetInt() ) {
return ((Integer)get(row)).intValue();
} else {
throw new DataTypeException(int.class);
}
}
/**
* Set the data value at the specified row as an integer
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the integer type
*/
public void setInt(int val, int row) throws DataTypeException {
if ( canSetInt() ) {
set(new Integer(val), row);
} else {
throw new DataTypeException(int.class);
}
}
// -- long ----------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the long type.
* @return true if getLong is supported, false otherwise
*/
public boolean canGetLong() {
return canGet(long.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the long type.
* @return true if setLong is supported, false otherwise
*/
public boolean canSetLong() {
return canSet(long.class);
}
/**
* Get the data value at the specified row as a long
* @param row the row from which to retrieve the value
* @return the data value as a long
* @throws DataTypeException if this column does not
* support the long type
*/
public long getLong(int row) throws DataTypeException {
if ( canGetLong() ) {
return ((Long)get(row)).longValue();
} else {
throw new DataTypeException(long.class);
}
}
/**
* Set the data value at the specified row as a long
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the long type
*/
public void setLong(long val, int row) throws DataTypeException {
if ( canSetLong() ) {
set(new Long(val), row);
} else {
throw new DataTypeException(long.class);
}
}
// -- float ---------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the float type.
* @return true if getFloat is supported, false otherwise
*/
public boolean canGetFloat() {
return canGet(float.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the float type.
* @return true if setFloat is supported, false otherwise
*/
public boolean canSetFloat() {
return canSet(float.class);
}
/**
* Get the data value at the specified row as a float
* @param row the row from which to retrieve the value
* @return the data value as a float
* @throws DataTypeException if this column does not
* support the float type
*/
public float getFloat(int row) throws DataTypeException {
if ( canGetFloat() ) {
return ((Float)get(row)).floatValue();
} else {
throw new DataTypeException(float.class);
}
}
/**
* Set the data value at the specified row as a float
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the float type
*/
public void setFloat(float val, int row) throws DataTypeException {
if ( canSetFloat() ) {
set(new Float(val), row);
} else {
throw new DataTypeException(float.class);
}
}
// -- double --------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the double type.
* @return true if getDouble is supported, false otherwise
*/
public boolean canGetDouble() {
return canGet(double.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the double type.
* @return true if setDouble is supported, false otherwise
*/
public boolean canSetDouble() {
return canSet(double.class);
}
/**
* Get the data value at the specified row as a double
* @param row the row from which to retrieve the value
* @return the data value as a double
* @throws DataTypeException if this column does not
* support the double type
*/
public double getDouble(int row) throws DataTypeException {
if ( canGetDouble() ) {
return ((Double)get(row)).doubleValue();
} else {
throw new DataTypeException(double.class);
}
}
/**
* Set the data value at the specified row as a double
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the double type
*/
public void setDouble(double val, int row) throws DataTypeException {
if ( canSetDouble() ) {
set(new Double(val), row);
} else {
throw new DataTypeException(double.class);
}
}
// -- boolean -------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the boolean type.
* @return true if getBoolean is supported, false otherwise
*/
public boolean canGetBoolean() {
return canGet(boolean.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the boolean type.
* @return true if setBoolean is supported, false otherwise
*/
public boolean canSetBoolean() {
return canSet(boolean.class);
}
/**
* Get the data value at the specified row as a boolean
* @param row the row from which to retrieve the value
* @return the data value as a boolean
* @throws DataTypeException if this column does not
* support the boolean type
*/
public boolean getBoolean(int row) throws DataTypeException {
if ( canGetBoolean() ) {
return ((Boolean)get(row)).booleanValue();
} else {
throw new DataTypeException(boolean.class);
}
}
/**
* Set the data value at the specified row as a boolean
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the boolean type
*/
public void setBoolean(boolean val, int row) throws DataTypeException {
if ( canSetBoolean() ) {
set(new Boolean(val), row);
} else {
throw new DataTypeException(boolean.class);
}
}
// -- String --------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the String type.
* @return true if getString is supported, false otherwise
*/
public boolean canGetString() {
return true;
//return canGet(String.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the String type.
* @return true if setString is supported, false otherwise
*/
public boolean canSetString() {
return m_parser != null && !(m_parser instanceof ObjectParser);
//return canSet(String.class);
}
/**
* Get the data value at the specified row as a String
* @param row the row from which to retrieve the value
* @return the data value as a String
* @throws DataTypeException if this column does not
* support the String type
*/
public String getString(int row) throws DataTypeException {
if ( canGetString() ) {
return m_parser.format(get(row));
} else {
throw new DataTypeException(String.class);
}
}
/**
* Set the data value at the specified row as a String
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the String type
*/
public void setString(String val, int row) throws DataTypeException {
try {
set(m_parser.parse(val), row);
} catch (DataParseException e) {
throw new DataTypeException(e);
}
}
// -- Date ----------------------------------------------------------------
/**
* Indicates if convenience get method can be called without
* an exception being thrown for the Date type.
* @return true if getDate is supported, false otherwise
*/
public boolean canGetDate() {
return canGet(Date.class);
}
/**
* Indicates if convenience set method can be called without
* an exception being thrown for the Date type.
* @return true if setDate is supported, false otherwise
*/
public boolean canSetDate() {
return canSet(Date.class);
}
/**
* Get the data value at the specified row as a Date
* @param row the row from which to retrieve the value
* @return the data value as a Date
* @throws DataTypeException if this column does not
* support the Date type
*/
public Date getDate(int row) throws DataTypeException {
if ( canGetDate() ) {
return (Date)get(row);
} else {
throw new DataTypeException(Date.class);
}
}
/**
* Set the data value at the specified row as a Date
* @param val the value to set
* @param row the row at which to set the value
* @throws DataTypeException if this column does not
* support the Date type
*/
public void setDate(Date val, int row) throws DataTypeException {
set(val, row);
}
} // end of abstract class AbstractColumn