/*
 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: *

* 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.