/*
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;
import com.jstatcom.model.control.SymbolTableTreeNode;
import com.jstatcom.model.control.SymbolTree;
import com.jstatcom.model.control.SymbolTreeNode;
/**
* This class contains a sorted collection of Symbol objects that
* can easily be accessed.
*
* This class is thread-save. All access to mutable data is synchronized.
*
* @author Markus Kraetzig
*/
public class SymbolTable {
private SymbolTableTreeNode symbolTableNode = null;
private final String name;
private final SymbolTable parentTable;
/**
* Identifier of this table (always upper case).
*/
public final String NAME;
private final List symbolList = new ArrayList();
/**
* XML format field for (de)serialization.
*/
public static final XmlFormat SymbolTable_XML = new XmlFormat(
SymbolTable.class) {
public void format(SymbolTable data, XmlElement xml) {
xml.setAttribute("name", data.name);
synchronized (data) {
for (Iterator it = data.symbolList.iterator(); it.hasNext();) {
Object o = it.next();
xml.add(o);
}
}
}
public SymbolTable parse(XmlElement xml) {
String name = xml.getAttribute("name") + "";
SymbolTable sTable = new SymbolTable(name, false);
while (xml.hasNext())
sTable.setSymbol((Symbol) xml.getNext());
return sTable;
}
};
/**
* Creates an empty symbol table with name, and no parent.
* It will be added to the symbol tree.
*
* @param name
* the identifier of that SymbolTable
*/
public SymbolTable(String name) {
this(name, null, true);
}
/**
* Creates an empty symbol table with name, and no parent.
* It can optionally be added to the symbol tree.
*
* @param name
* the identifier of that SymbolTable
* @param addToTree
* if true then this table will be added to the
* symbol tree, otherwise it will not be added
*/
public SymbolTable(String name, boolean addToTree) {
this(name, null, addToTree);
}
/**
* Creates an empty symbol table with name and a parent
* table. It will be added to the symbol tree.
*
* @param name
* the identifier of that SymbolTable
* @param parentTable
* the parent table, will be parent node in symbol tree
*/
public SymbolTable(String name, SymbolTable parentTable) {
this(name, parentTable, true);
}
/**
* Creates an empty symbol table with name and a parent
* table. It can optionally be added to the symbol tree.
*
* @param name
* the identifier of that SymbolTable
* @param parentTable
* the parent table, will be parent node in symbol tree
* @param addToTree
* if true then this table will be added to the
* symbol tree, otherwise it will not be added
*/
public SymbolTable(String name, SymbolTable parentTable, boolean addToTree) {
super();
if (name == null)
name = "null";
this.parentTable = parentTable;
this.name = name;
this.NAME = name.toUpperCase();
if (addToTree)
SymbolTree.getInstance().addSymbolTable(this);
}
/**
* Adds an array of JSCData objects to the collection.
*
* @param data
* the JSCData array to put
*
* @throws IllegalArgumentException
* if (data == null)
* or if a symbol with data[i].getName() but with
* a different type already exists
*/
public final void set(JSCData[] data) {
if (data == null)
throw new IllegalArgumentException("Argument was null.");
if (data.length == 0) // Fast fail.
return;
// not synchronized because in set the events are fired
for (int i = 0; i < data.length; i++)
set(data[i]);
}
/**
* Adds a new JSCData object to the collection. If a symbol
* with the same name as data is already contained,
* data will be set for that symbol. If no symbol of
* data.getName() is contained, then a new symbol will be
* created and added.
*
* @param data
* the JSCData object to put
*
* @throws IllegalArgumentException
* if (data == null)
* or if a symbol with data.getName() but with a
* different type already exists
*/
public final void set(JSCData data) {
if (data == null)
throw new IllegalArgumentException("Argument was null.");
Symbol toPut = new Symbol(data);
Symbol symbol = null;
boolean contains = false;
synchronized (this) {
if (contains = symbolList.contains(toPut)) {
symbol = symbolList.get(symbolList.indexOf(toPut));
}
}
// set symbol outside synched block, because events might be fired
// and listeners might need access to this table from a new thread
if (contains)
symbol.setJSCData(data);
else
setSymbol(toPut);
}
/**
* Adds a new JSCData object to the collection. If a symbol
* with the name symbolName is already contained,
* data will be set for that symbol. If no symbol of
* symbolName is contained, then a new symbol will be created
* and added.
*
* @param symbolName
* the name of the symbol to be created
* @param data
* the JSCData object to put
*
* @throws IllegalArgumentException
* if (data == null || symbolName == null)
* or if a symbol with symbolName but with a
* different type already exists
* or if symbolName is not a valid name as
* defined in JSCConstants
*
*/
public final void set(String symbolName, JSCData data) {
if (data == null || symbolName == null)
throw new IllegalArgumentException("Argument was null.");
Symbol toPut = new Symbol(symbolName, data);
synchronized (this) {
if (symbolList.contains(toPut)) {
Symbol symbol = symbolList.get(symbolList.indexOf(toPut));
symbol.setJSCData(data);
} else
setSymbol(toPut);
}
}
/**
* Puts a new symbol into the collection. The symbol is put on a position
* that keeps the underlying collection alphabetically sorted without
* respect to the case. If a symbol with the same name is alread contained,
* then the data and listeners are copied to the existing symbol.
*
* @param symbol
* the Symbol object to put
* @throws IllegalArgumentException
* if (symbol == null)
*/
public final void setSymbol(Symbol symbol) {
if (symbol == null)
throw new IllegalArgumentException("Argument was null.");
synchronized (this) {
if (symbolList.contains(symbol)) {
Symbol.copySymbol(symbol, getSymbol(symbol.NAME));
return;
}
boolean inserted = false;
for (int i = 0; i < symbolList.size(); i++) {
if (symbolList.get(i).compareTo(symbol) > 0) {
symbolList.add(i, symbol);
inserted = true;
break;
}
}
if (!inserted)
symbolList.add(symbol);
// update the symbol table node
if (symbolTableNode != null)
symbolTableNode.addSymbol(symbol);
}
}
/**
* Removes all elements from this collection.
*
* Usage Note:
* Removing a symbol from the SymbolTable does not affect any
* SymbolListeners attached to that symbol. They are still
* notified about changes in the removed symbol and not informed if a new
* symbol with name is added. You might consider calling
* removeAllSymbolListeners before that symbol is removed if
* this is not what you want.
*/
public final synchronized void clear() {
symbolList.clear();
if (symbolTableNode != null)
symbolTableNode.clear();
}
/**
* Gets whether this symbol table contains a symbol with name
* symbolName.
*
* @param symbolName
* the name of the symbol to check for
* @return true if contained, false otherwise
*/
public final synchronized boolean contains(String symbolName) {
return symbolList.contains(new Symbol(new JSCTypeDef(symbolName,
JSCTypes.NUMBER)));
}
/**
* Gets the symbol with the given name or null
* if none is contained with the given name.
*
* Usage Note:
* Pleae notice the difference to the typesave get method.
* This does not create a new symbol if it is not contained yet, because it
* is not known, which type the new symbol should have.
*
* @param name
* the identifier for the symbol
* @return the symbol that was stored or a new empty symbol
*
* @throws IllegalArgumentException
* if (name == null)
*/
public final Symbol getSymbol(String name) {
if (name == null)
throw new IllegalArgumentException("Argument was null.");
Symbol symbol = new Symbol(new JSCTypeDef(name, JSCTypes.NUMBER));
// type is arbitrary
synchronized (this) {
if (symbolList.contains(symbol))
return symbolList.get(symbolList.indexOf(symbol));
// Not contained, null.
return null;
}
}
/**
* Gets the symbol with def.name. If a symbol with that name
* does not yet exist an empty symbol of the given type is created, added
* and returned.
*
* @param def
* the type definition for the symbol
* @return the symbol that was stored or a new empty symbol
*
* @throws IllegalArgumentException
* if (def == null)
* @throws RuntimeException
* if the retrieved symbol has a different type
*/
public final Symbol get(JSCTypeDef def) {
if (def == null)
throw new IllegalArgumentException("Argument was null.");
Symbol symbol = new Symbol(def);
synchronized (this) {
if (symbolList.contains(symbol)) {
symbol = symbolList.get(symbolList.indexOf(symbol));
if (symbol.type != def.type)
throw new RuntimeException("The symbol \"" + def.name
+ "\" to be retrieved is of type " + symbol.type
+ " instead of " + def.type + ".");
if (symbol.getDescription().length() == 0
&& def.description.length() > 0) {
symbol.setDescription(def.description);
}
return symbol;
}
// Not contained, new empty symbol is created.
setSymbol(symbol);
return symbol;
}
}
/**
* Gets the JSCData stored in the symbol with given
* def.name. If a symbol with that name does not yet exist,
* a symbol of the given type is created, added and an empty
* JSCData object is returned.
*
* Instead of calling this method, one of the convenience methods for
* special JSCDataTypes can be used.
*
* @param def
* the type definition for the symbol
* @return the JSCData that was stored or a new empty data
* object
*
* @throws IllegalArgumentException
* if (def == null)
* @throws RuntimeException
* if the retrieved symbol has a different type
*/
public final synchronized JSCData getJSCData(JSCTypeDef def) {
if (def == null)
throw new IllegalArgumentException("Typedef was null.");
return get(def).getJSCData();
}
/**
* Gets the name of this symbol table.
*
* @return the name
*/
public final String getName() {
return name;
}
/**
* Gets whether this list is empty.
*
* @return true if no elements are contained,
* false otherwise
*/
public final synchronized boolean isEmpty() {
return symbolList.size() == 0;
}
/**
* Gets an Iterator for the symbols stored in this
* collection. The iterator is guaranteed to walk through all stored symbols
* in alphabetical order, starting with the lowest.
*
* Usage Note:
* Clients must deal with synchronization while traversing this iterator,
* because this can change during that operation. The
* simplest way is to lock this symbol table during the entire traversal. If
* clients can make sure that no other threads access this symbol table
* during traversal, locking can be ommitted.
*
* @return an Iterator for all symbols
*/
public final Iterator iterator() {
return symbolList.iterator();
}
/**
* Removes the symbol with name from this collection. The
* case of the name makes no difference.
*
* Usage Note:
* Removing a symbol from the SymbolTable does not affect any
* SymbolListeners attached to that symbol. They are still
* notified about changes in the removed symbol and not informed if a new
* symbol with name is added. You might consider calling
* removeAllSymbolListeners before that symbol is removed if
* this is not what you want.
*
* @param name
* the identifier for the symbol to remove.
* @return true if this list contained the specified element
* @throws IllegalArgumentException
* if (name == null)
* @see com.jstatcom.model.Symbol#equals
*/
public final boolean remove(String name) {
if (name == null)
throw new IllegalArgumentException("Argument was null.");
synchronized (this) {
// Type makes no difference here, just to find the symbol.
boolean ret = symbolList.remove(new Symbol(new JSCTypeDef(name,
JSCTypes.DATE)));
// update the symbol table node
if (ret && symbolTableNode != null)
symbolTableNode.removeSymbol(name);
return ret;
}
}
/**
* Gets the number of stored symbols in this collection.
*
* @return the size
*/
public final synchronized int size() {
return symbolList.size();
}
/**
* Returns a string that represents the value of this object.
*
* @return a string representation of the receiver
*/
public final synchronized String toString() {
return super.toString() + ",name=" + name + ",values=" + symbolList;
}
/**
* Gets the symbol table that is referenced as a parent of this table.
*
* @return a symbol table
*/
public final SymbolTable getParentTable() {
return parentTable;
}
/**
* Sets the symbol table node that represents this table in a
* SymbolTree.
*
* @param node
* to representthis table
*/
public void setSymbolTableNode(SymbolTableTreeNode node) {
symbolTableNode = node;
symbolTableNode.clear();
// put all symbols into node
for (Iterator iter = iterator(); iter.hasNext();) {
Symbol element = (Symbol) iter.next();
symbolTableNode.add(new SymbolTreeNode(element));
}
}
/**
* Gets the symbol table node that represents this table in a
* SymbolTree.
*
* @return node representing this table
*/
public SymbolTableTreeNode getSymbolTableNode() {
return symbolTableNode;
}
/**
* Sets the contents of table to this symbol table by
* applying the following rules:
*
* - if a symbol is only part of
table but not of
* this, then a new symbol with the same name is created in
* this table and the reference to the underlying JSCData
* instance is copied
* - if
table and this contain symbols with
* the same name, then the reference to the underlying JSCData
* instance is copied from the symbol of the argument table to this table
* - all symbols that are part of this table but not of the argument
* table are left untouched
*
*
* This operation does not change the argument symbol table, but by copying
* references its symbols might be affected by operations on this table
* afterwards.
*
* This method does not copy any listeners from table to
* this. Listeners of this table are not changed by this
* operation, except that they are notified via events in case of relevant
* changes.
*
* @param table
* the symbol table from which symbol are copied to this table
* @throws IllegalArgumentException
* if (table == null)
*/
public void setSymbolTable(SymbolTable table) {
if (table == null)
throw new IllegalArgumentException("Argument was null.");
synchronized (this) {
synchronized (table) {
for (Iterator iter = table.iterator(); iter.hasNext();) {
Symbol element = (Symbol) iter.next();
get(new JSCTypeDef(element.NAME, element.type)).setJSCData(
element.getJSCData());
}
}
}
}
}
Ads help maintain this website.