/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Alexander Benkwitz, 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.ts;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.swing.DefaultListModel;
import javax.swing.JOptionPane;
import javax.swing.ListModel;
import com.jstatcom.component.TopFrameReference;
import com.jstatcom.model.JSCData;
import com.jstatcom.model.JSCDate;
import com.jstatcom.model.JSCNArray;
import com.jstatcom.model.JSCString;
import com.jstatcom.project.ProjectData;
import com.jstatcom.project.ProjectDataHandler;
import com.jstatcom.project.ProjectDataTypes;
/**
* Holds all available TS at runtime. The identifying keys are
* the names of the time series. The names are converted to lowercase, which
* makes the lookup case insensitive. It also provides methods for adding,
* removing, and reading of time series. This class is designed as a Singleton
* (Gamma et al., 1995) and can be accessed by the static
* getInstance method.
*
* Classes can register themselves as PropertyChangeListeners to
* the TSHolder if they should be notified when
* TS_ADDED or TS_REMOVED events happen.
*
* This class is designed to be thread-save for adding, removing, and reading of
* time series.
*
* @author A. Benkwitz, Markus Kraetzig
* @see TS
* @see TSList
*/
public class TSHolder implements ProjectDataHandler {
/**
* Singleton instance.
*/
private static final TSHolder tsHolder = new TSHolder();
/**
* PropertyChange values.
*/
public static final String TS_ADDED = "TS_ADDED";
public static final String TS_REMOVED = "TS_REMOVED";
public static final String ALL_TS_REMOVED = "ALL_TS_REMOVED";
/**
* Holds PropertyChangeListeners.
*/
private final Vector propertyChangeListeners = new Vector();
/**
* HashTable for storing TS.
*/
private final Map tsHashTable = new HashMap();
private final TSListModel tsListModel = new TSListModel();
/**
* For representing the map as a ListModel with ordered names.
*/
private static class TSListModel extends DefaultListModel {
/**
* TSListModel takes care of sorting, when a new TS is
* inserted.
*
* @param obj
* to add, should be the name of a TS
*/
public void addElement(Object obj) {
if (getSize() == 0) {
super.addElement(obj);
return;
}
String toAdd = obj.toString();
String toCompare = toAdd.toUpperCase();
// Add alphabetically sorted.
for (int i = 0; i < getSize(); i++) {
if (getElementAt(i).toString().toUpperCase().compareTo(
toCompare) >= 0) {
super.add(i, toAdd);
return;
}
}
// If it was not yet added, append it to the end.
super.addElement(obj);
}
/**
* Removes string element regardless for case.
*
* @param obj
* the object to be removes
*/
public boolean removeElement(Object obj) {
if (super.removeElement(obj))
return true;
String toAdd = obj.toString();
// Add alphabetically sorted.
for (int i = 0; i < getSize(); i++) {
String obj_i = getElementAt(i).toString();
if (obj_i.equalsIgnoreCase(toAdd)) {
return super.removeElement(obj_i);
}
}
return false;
}
};
/**
* TSHolder constructor.
*/
private TSHolder() {
super();
}
/**
*
*/
public synchronized void addPropertyChangeListener(
PropertyChangeListener listener) {
if (!propertyChangeListeners.contains(listener))
propertyChangeListeners.add(listener);
}
/**
* Clears all currently stored time series and adds the series contained in
* newTimeSeries.
*
* @param newTimeSeries
* array of time series to add
*/
public void setTS(TS[] newTimeSeries) {
if (newTimeSeries == null)
throw new IllegalArgumentException("Argument was null.");
synchronized (this) {
removeAllTS();
for (int i = 0; i < newTimeSeries.length; i++) {
addTS(newTimeSeries[i]);
}
}
}
/**
* Adds a new TS to this TSHolder. If a
* series has less than 2 observations, an error message is displayed. If a
* series with the same name already exists, a question is displayed,
* whether the existing series should be overwritten. This method returns
* JOptionPane.YES_OPTION if newTimeSeries was
* null.
*
* If the series was added, a TS_ADDED PropertyChangeEvent is
* fired.
*
* @param newTimeSeries
* the new TS to add
* @return One of JOptionPane.YES_OPTION,
* JOptionPane.NO_OPTION or
* JOptionPane.CANCEL_OPTION, which the calling
* method can process accordingly. If
* JOptionPane.YES_OPTION is returned, the time
* series was added successfully.
*/
public int addTS(TS newTimeSeries) {
int accept = JOptionPane.YES_OPTION;
// Do nothing if null.
if (newTimeSeries == null)
return accept;
// No Lowercase for list display.
String newNameOrig = newTimeSeries.name();
// Lowercase for lookup in holder.
String newName = newNameOrig.toLowerCase();
// Check whether minimum condition is satisfied.
if (newTimeSeries.numOfObs() < 2) {
JOptionPane
.showConfirmDialog(
TopFrameReference.getTopFrameRef(),
"Variable \""
+ newName
+ "\" contains less than 2 observations.\nIt will not be added.",
"Error", JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE);
return JOptionPane.NO_OPTION;
}
synchronized (this) {
// Look whether key is already used.
if (tsHashTable.containsKey(newName)) {
accept = JOptionPane.showConfirmDialog(TopFrameReference
.getTopFrameRef(), "Variable name \"" + newNameOrig
+ "\" already exists, overwrite?", "Question",
JOptionPane.YES_NO_CANCEL_OPTION);
}
// Add the new time series to the time series table.
if (accept == JOptionPane.YES_OPTION) {
tsListModel.removeElement(newNameOrig);
tsHashTable.put(newName, newTimeSeries);
tsListModel.addElement(newNameOrig);
}
}
// Outside the synchronized region.
if (accept == JOptionPane.YES_OPTION)
firePropertyChange(TS_ADDED, newName, newName);
return accept;
}
/**
* Returns whether the TSHolder contains a TS
* with the given name. The lookup is case insensitive.
*
* @return true if series is contained, false otherwise
* @param name
* String with the name to be looked up
*/
public boolean contains(String name) {
if (name != null)
name = name.toLowerCase();
return tsHashTable.containsKey(name);
}
/**
* Notifies all PropertyChangeListeners.
*
* @param propName
* String
* @param oldValue
* Object
* @param newValue
* Object
*/
private void firePropertyChange(String propName, Object oldValue,
Object newValue) {
PropertyChangeEvent evt = new PropertyChangeEvent(this, propName,
oldValue, newValue);
for (int i = 0; i < propertyChangeListeners.size(); i++)
propertyChangeListeners.get(i).propertyChange(evt);
}
/**
* Gets TSHolder represented as a ListModel
* with the names in alphabetical order.
*
* @return the ListModel that is in sync with this object
*/
public synchronized ListModel getAsListModel() {
return tsListModel;
}
/**
* Returns Singleton instance of a TSHolder.
*
* @return TSHolder instance to be shared
*/
public static TSHolder getInstance() {
return tsHolder;
}
/**
* Retrieves a time series for a given name.
*
* @param name
* of the time series to get
* @return TS with the given name, null if it
* does not exist
*/
public synchronized TS getTS(String name) {
if (name != null)
name = name.toLowerCase();
if (tsHashTable.containsKey(name))
return tsHashTable.get(name);
return null;
}
/**
* Gets all time series.
*
* @return array of all series
*/
public synchronized TS[] getAllTS() {
TS ts[] = new TS[tsHashTable.size()];
int i = 0;
for (TS tsEl : tsHashTable.values())
ts[i++] = tsEl;
return ts;
}
/**
* Returns names of all available time series.
*
* @return iterator with names of all hashed TS
*/
public Iterator getTSNames() {
return tsHashTable.keySet().iterator();
}
/**
*
*/
public synchronized void removePropertyChangeListener(
PropertyChangeListener listener) {
if (propertyChangeListeners.contains(listener))
propertyChangeListeners.remove(listener);
}
/**
* Removes a time series from the map and fires a
* TS_REMOVED PropertyChangeEvent. Nothing happens, if no
* series with the given name was stored.
*
* @param name
* of the time series to get
*/
public void removeTS(String name) {
boolean fireChanged = false;
synchronized (this) {
if (tsHashTable.containsKey(name.toLowerCase())) {
tsListModel.removeElement(name);
tsHashTable.remove(name.toLowerCase());
fireChanged = true;
}
}
// Outside the synchronized region.
if (fireChanged)
firePropertyChange(TS_REMOVED, name, name);
}
/**
* Removes all time series from the map and fires an
* ALL_TS_REMOVED PropertyChangeEvent if there were any
* series removed.
*/
public void removeAllTS() {
synchronized (this) {
if (tsHashTable.size() == 0)
return;
tsListModel.clear();
tsHashTable.clear();
}
// Outside the synchronized region.
firePropertyChange(ALL_TS_REMOVED, null, null);
}
/**
* String representation of this TSHolder.
*
* @return string containing name and names of all included TS
*/
public String toString() {
StringBuffer ret = new StringBuffer(super.toString() + " [");
for (String name : tsHashTable.keySet()) {
ret.append("\"" + name + "\",");
}
if (tsHashTable.size() > 0)
ret.deleteCharAt(ret.length() - 1);
ret.append("]");
return ret.toString();
}
/**
* Sets the project data to this shared data repository for time series.
*
* @param pData
* project data to set
*/
public void setProjectData(ProjectData pData) {
if (pData == null)
return;
TSHolder.getInstance().removeAllTS();
JSCData[] data = pData.getJSCData();
int tsCount = data.length / 4;
if (tsCount == 0)
return;
String description = pData.getDescription();
TSProject project = new TSProject("TS_DATA", description);
TS ts[] = new TS[tsCount];
for (int i = 0, k = 0; i < tsCount; i++) {
JSCData data_k = data[k++];
String name = ((JSCString) data_k).string();
data_k = data[k++];
TSTypes tsType = TSTypes.valueOf(((JSCString) data_k).string());
data_k = data[k++];
TSDate start = ((JSCDate) data_k).getTSDate();
data_k = data[k++];
double[] values = ((JSCNArray) data_k).getCol(0);
ts[i] = new TS(values, name, start, tsType, project);
}
setTS(ts);
}
/**
* Gets the project data from this shared data repository. Typically this
* method is used for serialization.
*
* @return project data assembled from this data repository
*/
public ProjectData getProjectData() {
TS[] ts = getAllTS();
JSCData[] rtn = new JSCData[ts.length * 4];
List projectList = new ArrayList();
StringBuffer des = new StringBuffer();
for (int i = 0, k = 0; i < ts.length; i++) {
if (!projectList.contains(ts[i].project())) {
TSProject tmp = ts[i].project();
projectList.add(tmp);
if (tmp != null)
des.append(tmp.getDescription());
}
rtn[k++] = new JSCString("ts_name_" + i, ts[i].name());
rtn[k++] = new JSCString("ts_type_" + i, ts[i].type().getName());
rtn[k++] = new JSCDate("ts_start_" + i, ts[i].start());
rtn[k++] = new JSCNArray("ts_data_" + i, ts[i].values());
}
ProjectData data = new TSProjectData();
// get description
if (ts.length > 0)
data.setDescription(des.toString());
data.setJSCData(rtn);
return data;
}
/**
* Returns time series data type.
*
* @return ProjectDataTypes.TS_DATA
*/
public ProjectDataTypes getProjectDataType() {
return ProjectDataTypes.TS_DATA;
}
}
Ads help maintain this website.