/*
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.io;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.StringTokenizer;
import com.jstatcom.component.TopFrameReference;
import com.jstatcom.model.JSCConstants;
import com.jstatcom.model.JSCData;
import com.jstatcom.model.JSCDate;
import com.jstatcom.model.JSCNArray;
import com.jstatcom.model.JSCSArray;
import com.jstatcom.model.JSCString;
import com.jstatcom.model.JSCTypeDef;
import com.jstatcom.model.SymbolTable;
import com.jstatcom.ts.TS;
import com.jstatcom.ts.TSDate;
import com.jstatcom.ts.TSDateRange;
import com.jstatcom.util.UString;
/**
* Import handler for ASCII data files that either contain only the data points
* or are in the DAT format. It is designed as a singleton.
*
* An example DAT file looks like:
* <1960 Q1>
* invest income cons
* 180 451 415
* 179 465 421
* 185 485 434
*
* In line 1 the first number is number of variables, then the date of the first
* observation and the periodicity.
* Line 2 contains the variable names. The remaining lines are the ordered
* observations. Any Description is added as a comment and must be included in / *
* ... * /.
*
* @author Markus Kraetzig
*/
public final class TSASCIIHandler implements DataHandler {
private boolean noDialog = false;
private String contents = null;
private ArrayList observations = null;
private TSDate startDate = null;
private TSDataDialog dialog = null;
private double[] startInfo = new double[3];
private String[] names = null;
private String description = null;
private boolean observationsOnly = false;
private String fileName = null;
private boolean canceled = false;
private final SymbolTable symbolTable = new SymbolTable("TSASCIIHandler",
false);
private static final TSASCIIHandler handler = new TSASCIIHandler();
/**
* Creates a TSASCIIHandler.
*/
private TSASCIIHandler() {
super();
}
/**
* Gets the singleton instance of this handler.
*
* @return instance
*/
public static TSASCIIHandler getInstance() {
return handler;
}
/**
* Initiates parsing of data file.
*
* @param dataFile
* the file to read in
* @return true of import was ok, false if
* canceled or erroneous
* @throws IllegalArgumentException
* if (dataFile == null || !dataFile.isFile())
*/
public boolean importData(File dataFile) {
if (dataFile == null || !dataFile.isFile())
throw new IllegalArgumentException("Invalid data file " + dataFile
+ ".");
symbolTable.clear();
startDate = null;
contents = "";
startInfo = new double[3];
try {
parseContents(readFile(dataFile), dataFile);
} catch (Throwable t) {
// try Gauss .dat
TSGaussDATHandler handler = TSGaussDATHandler.getInstance();
boolean rtn = false;
try {
rtn = handler.importData(dataFile, false);
} catch (Throwable e) {
throw new RuntimeException(
t.getMessage()
+ "\nAlso tried to parse it as Gauss data set (binary) without success.");
}
if (rtn) {
symbolTable.set(TSImportTypes.DATA.name(), handler
.getData(TSImportTypes.DATA));
symbolTable.set(TSImportTypes.STARTDATE.name(), handler
.getData(TSImportTypes.STARTDATE));
symbolTable.set(TSImportTypes.VARNAMES.name(), handler
.getData(TSImportTypes.VARNAMES));
} else {
// .dat file import was ok but has been canceled
return false;
}
}
if (symbolTable.isEmpty())
return false;
return true;
}
/**
* Gets the imported data according to an instance of
* TSImportTypes.
*
* @param type
* the elementtype of a time series dataset
* @return the data stored as JSCData
* @throws IllegalArgumentException
* if (type == null)
* @throws IllegalStateException
* if nothing was imported before
*/
public JSCData getData(ImportTypes type) {
if (symbolTable == null)
throw new IllegalStateException("There was nothing imported yet.");
if (type == null)
throw new IllegalArgumentException("Argument was null.");
return symbolTable.getJSCData(new JSCTypeDef(type.name(), type.type()));
}
/**
* Gets the dialog used for raw ASCII files.
*/
private TSDataDialog getDataDialog() {
if (dialog == null)
dialog = new TSDataDialog(TopFrameReference.getTopFrameRef(), true);
dialog.setLocationRelativeTo(TopFrameReference.getTopFrameRef());
return dialog;
}
/**
* @throws RuntimeException
* if something went wrong
*/
private void parseTokens(String token, int tokenIndex) {
token = token.replaceAll(",", ".");
// we are in new style <1960 Q1>
if (startDate != null)
parseTokensDAT2(token, tokenIndex);
else
parseTokensDAT1_RAW(token, tokenIndex);
}
/**
* New style .dat files.
*
* @param token
* @param tokenIndex
*/
private void parseTokensDAT2(String token, int tokenIndex) {
Double val = UString.parseToNumber(token);
if (tokenIndex == 0) {
// check whether there are variable names, if not: start dialog
// there seem to be no names...it is a raw ASCII file, ask for some
// information about start, period, number of variables, names
if (val != null)
observationsOnly = true;
// read first line
String numTest = contents.substring(0, contents.indexOf("\n"));
StringTokenizer st0 = new StringTokenizer(numTest);
startInfo[0] = st0.countTokens();
// Initialize names with defaults.
if (startInfo[0] < 1)
throw new RuntimeException("Invalid number of variables "
+ startInfo[0] + ".");
names = new String[(int) startInfo[0]];
if (fileName.indexOf('.') > -1)
fileName = fileName.substring(0, fileName.lastIndexOf("."))
+ "_";
if (JSCConstants.isValidName(fileName) != null)
fileName = "var_";
for (int i = 0; i < names.length; i++)
names[i] = fileName + (i + 1);
}
if (observationsOnly) {
if (val == null)
throw new RuntimeException("Token \"" + token
+ "\" could not be parsed to a number.");
observations.add(val);
return;
}
if (!observationsOnly && tokenIndex >= 0 && tokenIndex < startInfo[0]) {
if (val == null) {
if (JSCConstants.isValidName(token) == null)
names[tokenIndex] = token;
} else {
observationsOnly = true;
observations.add(val);
return;
}
if (tokenIndex == startInfo[0] - 1)
// reading names finished
observationsOnly = true;
}
}
/**
* Old style .dat files and pure ASCII.
*
* @param token
* @param tokenIndex
*/
private void parseTokensDAT1_RAW(String token, int tokenIndex) {
Double val = UString.parseToNumber(token);
if (tokenIndex < 3) {
if (val == null)
throw new RuntimeException("Token:\n\""
+ token.substring(0, (token.length() < 128) ? token
.length() : 64)
+ "\"\ncould not be parsed to a number.");
startInfo[tokenIndex] = val.doubleValue();
return;
}
if (tokenIndex == 3) {
// check whether there are variable names, if not: start dialog
// there seem to be no names...it is a raw ASCII file, ask for some
// information about start, period, number of variables, names
if (val != null) {
observationsOnly = true;
observations.add(new Double(startInfo[0]));
observations.add(new Double(startInfo[1]));
observations.add(new Double(startInfo[2]));
if (!noDialog) {
getDataDialog().setVisible(true);
if (getDataDialog().isCanceled()) {
canceled = true;
return;
}
}
startDate = getDataDialog().getTSDate();
startInfo[1] = -1;
startInfo[2] = -1;
// read first line in case of raw ASCII file
String numTest = contents.substring(0, contents.indexOf("\n"));
StringTokenizer st0 = new StringTokenizer(numTest);
startInfo[0] = st0.countTokens();
}
// Initialize names with defaults.
if (startInfo[0] < 1)
throw new RuntimeException("Invalid number of variables "
+ startInfo[0] + ".");
names = new String[(int) startInfo[0]];
if (fileName.indexOf('.') > -1)
fileName = fileName.substring(0, fileName.lastIndexOf("."))
+ "_";
if (JSCConstants.isValidName(fileName) != null)
fileName = "var_";
for (int i = 0; i < names.length; i++)
names[i] = fileName + (i + 1);
// Set names from dialog.
if (observationsOnly)
setNamesFromDialog(getDataDialog().getVariableNames());
}
// this method will be called, if either only raw data is read in,
// (this is assumed, when no variable names are specified)
// or when the reading of the variablenames is finished.
if (observationsOnly) {
if (val == null)
throw new RuntimeException("Token \"" + token
+ "\" could not be parsed to a number.");
observations.add(val);
return;
}
// read variable names (if there are less names than variables the
// missing ones are set to defaults)
// always check, whether variable names contain only numbers, if yes,
// switch to observation reading
// and leave the remaining variables at their default name (the filename
// plus index)
if (!observationsOnly && tokenIndex > 2
&& tokenIndex < startInfo[0] + 3) {
if (val == null) {
if (JSCConstants.isValidName(token) == null)
names[tokenIndex - 3] = token;
} else {
observationsOnly = true;
observations.add(val);
return;
}
if (tokenIndex == startInfo[0] + 2)
// reading names finished
observationsOnly = true;
}
}
/**
* Creates the names either from index, from file or from dialog.
*/
private void setNamesFromDialog(String namesFromDialog) {
// start to fill it filename info
StringTokenizer st = new StringTokenizer(namesFromDialog);
String[] stringArray = new String[st.countTokens()];
for (int i = 0; i < stringArray.length; i++)
stringArray[i] = st.nextToken();
int length = stringArray.length;
if (names.length < stringArray.length)
length = names.length;
for (int i = 0; i < length; i++) {
if (JSCConstants.isValidName(stringArray[i]) == null)
names[i] = stringArray[i];
}
}
/**
* This method parses the contents of the vector contents.
*
* @param x
* java.util.Vector contents Vector of strings (the file line by
* line).
* @param x
* java.io.File file The input file.
*/
private void parseContents(String[] stringArray, File file) {
observations = new ArrayList();
observationsOnly = false;
names = null;
fileName = file.getName();
// parse every token and check whether it is a comment
// break the comments with the linebreaks in the file
canceled = false;
for (int i = 0, index = 0; i < stringArray.length; i++) {
if (stringArray[i] != null) {
stringArray[i] = stringArray[i].trim();
if (stringArray[i].length() > 0)
parseTokens(stringArray[i], index++);
if (canceled)
return;
}
}
// either from the file or from user input
if (startDate == null) // old style .dat format
startDate = new TSDate(startInfo[1], (int) startInfo[2]);
if (observations.size() == 0)
throw new RuntimeException("File contains no numbers.");
if (startInfo[0] < 1)
throw new RuntimeException("Invalid number of variables "
+ startInfo[0] + ".");
double[][] values = new double[(int) (observations.size() / startInfo[0])][(int) startInfo[0]];
for (int i = 0, index = 0; i < values.length; i++)
for (int j = 0; j < values[0].length; j++)
values[i][j] = observations.get(index++).doubleValue();
symbolTable.set(new JSCNArray(TSImportTypes.DATA.name(), values));
symbolTable.set(new JSCDate(TSImportTypes.STARTDATE.name(), startDate));
symbolTable.set(new JSCSArray(TSImportTypes.VARNAMES.name(), names));
symbolTable.set(new JSCString(TSImportTypes.DESCRIPTION.name(),
description.trim()));
}
/**
* This method reads the contents of an ASCII file line by line and returns
* in a List object of Strings.
*
* @param x
* java.io.File File to read.
* @return string array
*/
private String[] readFile(File file) {
String str = null;
// Read the file line by line and append each line as a string
// to the Vector 'contents'
StringBuffer buffer = new StringBuffer();
try {
BufferedReader in = new BufferedReader(new FileReader(file));
while ((str = in.readLine()) != null)
if (str.length() > 0)
buffer.append(str.trim() + "\n");
in.close();
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
// extract description from buffer and delete it afterwards
int descriptionStart = buffer.indexOf("/*");
StringBuffer des = new StringBuffer();
while (descriptionStart > -1) {
int descriptionEnd = buffer.indexOf("*/");
if (descriptionEnd < descriptionStart + 2)
throw new RuntimeException("There is an open comment /*.");
des.append(buffer.substring(descriptionStart + 2, descriptionEnd));
buffer = buffer.delete(descriptionStart, descriptionEnd + 2);
descriptionStart = buffer.indexOf("/*");
}
description = des.toString();
contents = buffer.toString().trim() + "\n";
buffer = new StringBuffer(contents);
String dateString = "";
if (contents.startsWith("<")) {
int index = contents.indexOf(">");
dateString = contents.substring(1, index);
contents = contents.substring(index + 1).trim() + "\n";
startDate = TSDate.valueOf(dateString);
}
StringTokenizer st = new StringTokenizer(contents);
String[] stringArray = new String[st.countTokens()];
for (int i = 0; i < stringArray.length; i++)
stringArray[i] = st.nextToken();
return stringArray;
}
public void exportTS(File file, TS[] ts, String description) {
if (file == null)
throw new IllegalArgumentException("File argument was null.");
if (ts == null)
throw new IllegalArgumentException("Time series argument was null.");
if (ts.length == 0)
return;
if (description == null)
description = "";
// description
StringBuffer buffer = new StringBuffer();
if (description.length() > 0)
buffer.append("/*\r\n" + description + "\r\n*/\r\n");
TSDateRange mergedRange = TSDateRange.getMergedRange(ts);
// header for old .dat file
/*
* buffer.append(sel.length + " ");
* buffer.append(mergedRange.lowerBound().doubleValue() + " ");
* buffer.append(mergedRange.lowerBound().subPeriodicity());
*/
buffer.append("<" + mergedRange.lowerBound() + ">");
buffer.append("\n");
for (int i = 0; i < ts.length; i++)
buffer.append(ts[i].name() + " ");
// buffer.append(Format.sprintf("%-30s ", new
// Parameters(sel[i].getName())));
buffer.append("\n");
// data
for (int i = 0; i < mergedRange.numOfObs(); i++) {
TSDate currentDate = mergedRange.dateForIndex(i);
for (int j = 0; j < ts.length; j++)
buffer.append(ts[j].valueAt(currentDate) + " ");
// Format.sprintf("%-30s ", new
// Parameters(sel[j].getDataAt(currentDate) + "")));
buffer.append("\n");
}
String toWrite = buffer.toString().replaceAll("\n", "\r\n");
FileSupport.getInstance().saveStringToFile(file, toWrite, false);
}
/**
* Exports data to a file in ASCII format. This method uses the
* display method on the data parameter.
*
* Usage Note:
* To store NARRAY data with its full precision, make sure to
* call data.setFormatString("%-s ") before. Otherwise the
* default formatter is used, which only stores 4 digits. Nothing happens if
* data is empty.
*
* @param file
* the file to store the data to
* @param data
* the data object to store, all JSCData types are
* supported
* @param description
* an optional description that handler implementations might
* use, can be null
* @throws IllegalArgumentException
* if file or data is
* null
* @throws RuntimeException
* if file writing fails
*/
public void exportData(File file, JSCData data, String description) {
if (file == null)
throw new IllegalArgumentException("File argument was null.");
if (data == null)
throw new IllegalArgumentException("Data argument was null.");
if (data.isEmpty())
return;
// because the display method is used, it works for all
// data types
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
String toSave = data.display();
PrintStream pStream = new PrintStream(out);
if (description != null)
pStream.print("/*\r\n" + description + "\r\n*/\r\n");
pStream.print(toSave);
out.flush();
out.close();
} catch (Throwable ex) {
throw new RuntimeException(ex.getMessage());
}
}
/**
* Sets this handler in test mode. No user dialog is shown, default values
* are used instead. This is helpful for running automated unit tests.
*
* @param noDialog
*/
public void setTestMode(boolean noDialog) {
this.noDialog = noDialog;
}
}
Ads help maintain this website.