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