/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Markus Kraetzig
This library 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 2.1 of the
License, or (at your option) any later version.
This library 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 this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javolution37.javolution.xml.XmlElement;
import javolution37.javolution.xml.XmlFormat;
/**
* The Symbol class represents a variable with a certain type and
* name. After initialization it is only possible to store references to
* JSCData objects of type. Two symbols are
* considered equal if their names are equal, independent of the case.
*
* SymbolListener can savely use Symbol objects to
* obtain notification about changes in the underlying JSCData
* within the Swing EventDispatchingThread. This is not ensured
* if they register themselves as listeners directly to JSCData
* objects.
*
* This class is thread-save. All access to mutable data is synchronized.
*
* Subclasses might add new accessor methods for newly declared
* JSCData types. The main functionality of a symbol should not
* be changed, therefore all methods are declared final.
*
* @author Markus Kraetzig
*/
public class Symbol implements JSCDataListener, Comparable {
private SymbolEventSupport eventSupport = null;
/**
* Identifier of this symbol.
*/
public final String name;
/**
* Upper case identifier of this symbol.
*/
public final String NAME;
/**
* Type of this symbol.
*/
public final JSCTypes type;
private final int hashCode;
private JSCData data = null;
private String description = "";
/**
* XML format field for (de)serialization.
*/
public static final XmlFormat Symbol_XML = new XmlFormat(
Symbol.class) {
public void format(Symbol data, XmlElement xml) {
xml.setAttribute("name", data.name);
synchronized (this) {
xml.add(data.data);
}
}
public Symbol parse(XmlElement xml) {
String nam = xml.getAttribute("name") + "";
JSCData data = (JSCData) xml.getNext();
return new Symbol(nam, data);
}
};
/**
* Creates a Symbol with data.getName a
* reference to data. The type of this Symbol
* is set to data.type.
*
* @param data
* the JSCData object for this Symbol
*
* @throws IllegalArgumentException
* if (data == null)
*/
public Symbol(JSCData data) {
this(data.name(), data);
}
/**
* Creates a Symbol with name and a reference
* to data. The type of this Symbol is set to
* data.type.
*
* @param name
* the identifier of the symbol, must satisfy conventions for
* variable names defined in JSCConstants
* @param data
* the JSCData object for this Symbol
*
* @throws IllegalArgumentException
* if (name == null || data == null) or if
* name is not valid
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public Symbol(String name, JSCData data) {
super();
if (name == null || data == null)
throw new IllegalArgumentException("Argument was null.");
JSCConstants.checkNameThrowEx(name);
this.name = name;
NAME = name.toUpperCase();
this.type = data.type();
hashCode = NAME.hashCode();
setJSCData(data);
}
/**
* Creates a Symbol with an empty object of the given type.
*
* @param def
* the type definition
* @throws NullPointerException
* if (def == null)
* @see com.jstatcom.model.JSCConstants#isValidName
*/
public Symbol(JSCTypeDef def) {
this(def.name, def.type.valueOfEmpty(def.name));
description = def.description;
}
/**
* Convenience method that adds a listener that is notified when changes of
* type VALUE_CHANGED occur in this object. All other events
* that are fired by this object are not dispatched to
* evtListener. This is most common for
* SymbolListeners.
*
* @param evtListener
* the listener object that gets notified when an event of type
* evtType gets fired
* @throws IllegalArgumentException
* if (evtListener == null)
*/
public final synchronized void addSymbolListener(SymbolListener evtListener) {
getEventSupport().addListener(evtListener,
SymbolEventTypes.VALUE_CHANGED);
}
/**
* Adds a listener that is notified when changes of type
* evtType occur in this object. All other events that are
* fired by this object are not dispatched to evtListener.
*
* @param evtListener
* the listener object that gets notified when an event of type
* evtType gets fired
* @param evtType
* the type of event that evtListener wants to
* listen to
* @throws IllegalArgumentException
* if (evtListener == null || evtType == null)
*/
public final synchronized void addSymbolListener(
SymbolListener evtListener, SymbolEventTypes evtType) {
getEventSupport().addListener(evtListener, evtType);
}
/**
* Clears the underlying JSCData object. This fires a
* EMPTY_STATE and VAL_CHANGED event, if the
* data was not empty before.
*/
public final void clear() {
JSCData ref;
synchronized (this) {
ref = data;
}
if (ref != null)
ref.clear();
}
/**
* Displays the underlying JSCData object.
*/
public final synchronized String display() {
return data.display();
}
/**
* Compares two Symbol objects by their uppercase names.
*
* @param obj
* the symbol to be compared, must another Symbol
* @return 0 if equal, < 0 if this smaller, > 0 if this is greater
* @throws IllegalArgumentException
* if (!(obj instanceof Symbol))
*/
public final int compareTo(Object obj) {
if (!(obj instanceof Symbol))
throw new IllegalArgumentException("Argument " + obj
+ " not a Symbol.");
if (equals(obj))
return 0;
return NAME.compareTo(((Symbol) obj).NAME);
}
/**
* Returns true if obj is a
* Symbol and has the same name as this, without regarding
* the case. This is to ensure that there is only one Symbol
* of a given name in a SymbolTable.
*
* @param obj
* the object to compare with
* @return true if these object are equal, false
* otherwise.
*/
public final boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Symbol))
return false;
return NAME.equals(((Symbol) obj).NAME);
}
/**
* Gets the description of this symbol.
*
* @return the descriptive string or "" if no description has
* been set
*/
public final String getDescription() {
return description;
}
/**
* Lazily initializes eventSupport
*
* @return SymbolEventSupport instance
*/
private SymbolEventSupport getEventSupport() {
if (eventSupport == null)
eventSupport = new SymbolEventSupport();
return eventSupport;
}
/**
* Gets a reference to the underlying data object.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return JSCData object of type type
*/
public synchronized final JSCData getJSCData() {
return data;
}
/**
* Gets the data as an instance of JSCDate.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCDate
* @throws RuntimeException
* if (type != JSCTypes.DATE)
*/
public final JSCDate getJSCDate() {
JSCTypes toGet = JSCTypes.DATE;
if (type != toGet)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + toGet + " in " + toString() + ".");
synchronized (this) {
return (JSCDate) data;
}
}
/**
* Gets the data as an instance of JSCDRange.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCDRange
* @throws RuntimeException
* if (type != JSCTypes.DRANGE)
*/
public final JSCDRange getJSCDRange() {
JSCTypes toGet = JSCTypes.DRANGE;
if (type != toGet)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + toGet + " in " + toString() + ".");
synchronized (this) {
return (JSCDRange) data;
}
}
/**
* Gets the data as an instance of JSCNArray. If the data is
* an instance of JSCNumber or JSCInt a new
* 1x1 JSCNArray with the single element is returned or an
* empty JSCNArray.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCNArray
* @throws RuntimeException
* if (type != JSCTypes.NARRAY &&
* type != JSCTypes.INT && type != JSCTypes.NUMBER)
*/
public final JSCNArray getJSCNArray() {
if (type != JSCTypes.NARRAY && type != JSCTypes.INT
&& type != JSCTypes.NUMBER)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + JSCTypes.NARRAY + " or "
+ JSCTypes.NUMBER + " or " + JSCTypes.INT + " in "
+ toString() + ".");
synchronized (this) {
if (type == JSCTypes.NARRAY)
return (JSCNArray) data;
if (data.isEmpty())
return new JSCNArray(data.name());
if (type == JSCTypes.NUMBER)
return new JSCNArray(data.name(), ((JSCNumber) data)
.doubleVal());
return new JSCNArray(data.name(), ((JSCInt) data).intVal());
}
}
/**
* Gets the data as an instance of JSCNumber. If the data is
* of type INT a new JSCNumber with the
* element is returned.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCNumber
* @throws RuntimeException
* if (type != JSCTypes.NUMBER && type != JSCTypes.INT)
*/
public final JSCNumber getJSCNumber() {
if (type != JSCTypes.NUMBER && type != JSCTypes.INT)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + JSCTypes.NUMBER + " or " + JSCTypes.INT
+ " in " + toString() + ".");
synchronized (this) {
if (type == JSCTypes.NUMBER)
return (JSCNumber) data;
if (data.isEmpty())
return new JSCNumber(data.name());
return new JSCNumber(data.name(), ((JSCInt) data).intVal());
}
}
/**
* Gets the data as an instance of JSCInt.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCInt
* @throws RuntimeException
* if (type != JSCTypes.INT)
*/
public final JSCInt getJSCInt() {
JSCTypes toGet = JSCTypes.INT;
if (type != toGet)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + toGet + " in " + toString() + ".");
synchronized (this) {
return (JSCInt) data;
}
}
/**
* Gets the data as an instance of JSCSArray. If the data is
* an instance of JSCString, a new 1x1
* JSCSArray with the single element is returned or an empty
* JSCSArray.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCSArray
* @throws RuntimeException
* if (type != JSCTypes.SARRAY && type != JSCTypes.STRING)
*/
public final JSCSArray getJSCSArray() {
if (type != JSCTypes.SARRAY && type != JSCTypes.STRING)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + JSCTypes.SARRAY + " or "
+ JSCTypes.STRING + " in " + toString() + ".");
synchronized (this) {
if (type == JSCTypes.SARRAY)
return (JSCSArray) data;
if (data.isEmpty())
return new JSCSArray(data.name());
return new JSCSArray(data.name(), ((JSCString) data).string());
}
}
/**
* Gets the data as an instance of JSCString. If the data is
* an instance of JSCSArray, a JSCString with
* the 0:0 element is returned or an empty JSCString.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCString
* @throws RuntimeException
* if (type != JSCTypes.STRING && type != JSCTypes.SARRAY)
*/
public final JSCString getJSCString() {
if (type != JSCTypes.STRING && type != JSCTypes.SARRAY)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + JSCTypes.STRING + " or "
+ JSCTypes.SARRAY + " in " + toString() + ".");
synchronized (this) {
if (type == JSCTypes.STRING)
return (JSCString) data;
if (data.isEmpty())
return new JSCString(data.name());
return new JSCString(data.name(), ((JSCSArray) data).stringAt(0, 0));
}
}
/**
* Gets the data as an instance of JSCVoid.
*
* Usage note:
* The returned data is a reference to the underlying JSCData
* object only until a new data object has been set to this symbol. It
* should be used read only. Changes that should not affect this symbol must
* be made on a copy of the returned object.
*
* @return a JSCVoid
* @throws RuntimeException
* if (type != JSCTypes.VOID)
*/
public final JSCVoid getJSCVoid() {
JSCTypes toGet = JSCTypes.VOID;
if (type != toGet)
throw new RuntimeException("Symbol type is " + type
+ " instead of " + toGet + " in " + toString() + ".");
synchronized (this) {
return (JSCVoid) data;
}
}
/**
* Generates a hash code for the receiver. This method is supported
* primarily for hash tables, such as those provided in java.util.
*
* @return an integer hash code for the receiver
*/
public final int hashCode() {
return hashCode;
}
/**
* Gets whether the underlying JSCData object is empty.
*
* @return true if empty, false otherwise
*/
public final synchronized boolean isEmpty() {
return data.isEmpty();
}
/**
* Removes all SymbolListener objects from this symbol.
*/
public synchronized void removeAllSymbolListeners() {
eventSupport = null;
}
/**
* Removes evtListener from all listener lists.
*
* @param evtListener
* the listener to remove
*/
public final synchronized void removeSymbolListener(
SymbolListener evtListener) {
if (eventSupport == null)
return;
eventSupport.removeListener(evtListener);
}
/**
* Gets a list with all registered listeners for evtType.
*
* @param evtType
* the event type
* @return a copy of the respective list or an initialized list instance
* with size == 0 if no listeners are registered for
* evtType
*/
public List getListenersForType(SymbolEventTypes evtType) {
if (eventSupport == null)
return new ArrayList();
return eventSupport.getListenersForType(evtType);
}
/**
* Sets the description for this symbol.
*
* @param newDescription
* a descriptive string
* @throws IllegalArgumentException
* if description contains / * or * /
*/
public final void setDescription(String newDescription) {
if (newDescription == null)
newDescription = "";
if (newDescription.indexOf("/*") > -1
|| newDescription.indexOf("*/") > -1)
throw new IllegalArgumentException("Description \""
+ newDescription + "\" contains /* or */.");
description = newDescription.trim();
}
/**
* Sets the reference of this symbol to a new data object.
* SymbolListener objects are notified via the events:
*
* SymbolEventTypes.REF_CHANGED
* SymbolEventTypes.VAL_CHANGED only
* if (!newData.isEqual(oldData))
*
* The REF_CHANGED event is necessary for tables that listen
* to changes in a certain Symbol. They need to change their
* model data to newData, otherwise they would continue to
* listen to changes in the old data object.
*
* This symbol is removed from the listener list of the previous data
* object.
*
* @param newData
* reference to the new data object, which must be of type
* type
* @throws IllegalArgumentException
* if (newData == null) or
* if (newData.type() != type)
*/
public final void setJSCData(JSCData newData) {
if (newData == null)
throw new IllegalArgumentException("Argument was null.");
if (newData.type() != type)
throw new IllegalArgumentException("Argument type ("
+ newData.type() + ") different from symbol type (" + type
+ ") for symbol \"" + name + "\".");
JSCData oldData = null;
boolean wasEmpty = false;
synchronized (this) {
if (data == newData)
return;
// data == null only when called from contructor.
wasEmpty = data == null || data.isEmpty();
oldData = data;
data = newData;
if (oldData != null)
oldData.removeJSCDataListener(this);
// Add this as listener to all JSCDataEventTypes.
JSCDataEventTypes[] types = JSCDataEventTypes.getTypes();
for (int i = 0; i < types.length; i++) {
// here a wrapper listener is created that
// follows the standard equals contract;
// Symbol objects are equal by NAME -
// this leads to the situation that
// 2 symbols with the same name could not
// be added as a listener to the same JSCData object,
// because it would treat them as equal and would
// only notify the first that has been added
JSCDataListener lst = new JSCDataListener() {
public void valueChanged(JSCDataEvent evt) {
Symbol.this.valueChanged(evt);
}
};
data.addJSCDataListener(lst, types[i]);
}
}
if (eventSupport == null)
return;
// Inform listeners about REF_CHANGED.
// eventSupport.dispatchEvent(
// SymbolEvent.valueOfRefChanged(this, oldData, newData));
// Inform listeners about EMPTY_STATE.
if (wasEmpty && !isEmpty())
eventSupport.dispatchEvent(SymbolEvent.valueOfEmptyState(this,
false));
// Inform listeners about EMPTY.
if (!wasEmpty && isEmpty())
eventSupport.dispatchEvent(SymbolEvent
.valueOfEmptyState(this, true));
// Inform listeners about VAL_CHANGED, test whether check is necessary
// because it might be expensive.
if (eventSupport.getListenersForType(SymbolEventTypes.VALUE_CHANGED)
.size() == 0)
return;
if (!oldData.isEqual(newData))
eventSupport.dispatchEvent(SymbolEvent.valueOfValChanged(this,
oldData, newData));
}
/**
* Returns a string that represents the value of this object.
*
* @return a string representation of the receiver
*/
public final String toString() {
return super.toString() + " [name=" + name + ",type=" + type + "]";
}
/**
* Transforms evt to the respective SymbolEvent
* and dispatches it to the SymbolListeners.
*
* @param evt
* the data event
*/
public final void valueChanged(JSCDataEvent evt) {
if (eventSupport == null)
return;
// Inform listeners about evt
eventSupport.dispatchEvent(SymbolEvent.valueOfJSCDataEvent(this, evt));
}
/**
* Moves data and listeners from one symbol to the other. The data is ONLY
* copied, if the source is not empty. The source will be cleared and all
* listeners will be removed. Nothing happens
* if (source == target).
*
* @param source
* to copy from
* @param target
* to copy to
* @throws IllegalArgumentException
* if an argument is null
*/
public static void copySymbol(Symbol source, Symbol target) {
if (source == null || target == null)
throw new IllegalArgumentException("Argument was null");
if (source == target)
return;
synchronized (source) {
if (!source.isEmpty())
target.setJSCData(source.getJSCData());
synchronized (target) {
// copy listeners
for (Iterator it = SymbolEventTypes.iterator(); it.hasNext();) {
SymbolEventTypes evtType = (SymbolEventTypes) it.next();
List list = source.getListenersForType(evtType);
for (int i = 0; i < list.size(); i++) {
target.addSymbolListener((SymbolListener) list.get(i),
evtType);
}
}
if (target.getDescription().length() == 0)
target.setDescription(source.getDescription());
}
source.removeAllSymbolListeners();
}
}
}
Ads help maintain this website.