/** File: FileTable.java */ import java.io.*; /** * This class reads tab-delimited data from files into a Table and * can write data to files. The term Table (upper case 'T') denotes * the FileTable data structure. The class also parses tab-delimited * data with or without a header (1 to n-lines of header) from a * file into a Table * (tRows,tCols,tHdrRows,tPreface,tHeader,tFields,tData). *

* If there are multiple header lines, it uses the last header line as * the tFields[] data. You can optionally check for duplicate files * and ignore them otherwise it is a parsing error. *

* You can optionally search the input file for a blank line after which * the actually header appears. You can also specify a keyword in the * header line to denote where the Table starts. You can remove blank * lines at the end of the file to shorten the table length. It can be * used to create an empty table (not associated with a file). * *

*

 * List of Methods
 * FileTable() - Constructor, set some defaults. 
 * FileTable(tableName) - Constructor, set some defaults. 
 * FileTable(tableName, rows, cols) - Constructor, set some defaults.
 * clearTable() - set to empty table. Generally called from Constructor
 * cloneTable() - clone the current Table. Copy data by value.
 * setNbrTableHdrLines() - number of lines of the Table header.
 * setHasTableHeaderFlag() - set Has Table Header Flag.
 * setDuplicateFieldsFlag() - set duplicate fields Flag.
 * setHasEmptyLineBeforeTableFlag() - set look for empty line(s) BEFORE table
 * setRmvTrailingBlankLinesFlag() - remove trailing blank lines in the Table
 * setRmvTrailingEmptyColumnsFlag() - set remove empty trailing columns flag.
 * setStartTableAtColStr() - start Table at string known to be in header.
 * setColNameIndexMapKeys() - set column names keys for ftIdxMap index-map.
 * setUseOnlyLastHeaderLineFlag() - set use only last header line flag
 * readTableRowFromFile() - random access read row r tokens from indexed file.
 * readLineRowFromFile() - random access read row r data from indexed file.
 * checkForBadTable() - verify that it is a well formed table
 * checkForBadTableHeader() - verify that is a well formed tField Table
 * parseTableFromString() - convert tab-delim string to Table structure.
 * readAndParseTableIndexMap() - read tab-delim file to Table Index-Map.
 * readAndParseTableAll() - read & convert tab-delim file to Table & ftIdxMap.
 * readAndParseTable() - read & convert tab-delim file to Table structure.
 * readAndParseTableFields() - read tab-delim file to tFields Table structure.
 * readAndParseTableFieldsAndIndexMap - read tab-delim file tFields & ftIdxMap
 * readAndParseTableData() - read & convert tab-delim file to Table structure.
 * computeFileLineTermSize() - compute a file's line terminator size.
 * cvtTableToTabDelimStr() - convert table to tab delimited string
 * rmvTrailingBlankLinesFromTable() - remove Table trailing blank lines.  
 * deleteTableColumnByName() - delete a column from the Table.
 * deleteTableColumnByColIdx() - delete a column from table by column index
 * lookupFieldIdx() - lookup column index of field if exists.
 * lookupRowFromColumnData() - lookup row index from column data if exists.
 * lookupCol2DataFromCol1DataRow() - lookup col2 data from row w/col1 data match
 * appendRowToTable() - append row of data to table.
 * removeRowFromTable() - remove row of data from table.
 * deleteRowsFromTable() - delete rows from the Table and resize the Table.
 * deleteTrailingEmptyColumnsFromTable() - removes empty trailing columns
 * mapHeaderColNames() - map tField & tHeaders old to new names in map list.
 * setFieldsToTable() - set the Fields list to the Table and set tCols.
 * setHeadersToTable() - set the Headers list to the Table and set tHdrRows.
 * addEmptyColumnToExistingTable() - add empty column to existing Table.
 * addColumnToTable() - add a new column to Table with  specified field name. 
 * replaceColumnData() - replace data in a column in the Table.
 * setColumnDataByValue() - set all data in a column in Table to a value.
 * getColumnData() - lookup the column name and then return all column data.
 * reorderRowsBySortIndex() - reorder the entire table rows by the sortIdx.
 * reorderColumnsInTable() - reorder Table tFields & tData by column index
 * joinTableToCurrentTable() - join another Table ftJoin to current Table.
 * trimTableEnclWhitespace() - trim (clean) enclosing white space in Table.
 * trimArrayEnclWhitespace() - trim (clean) enclosing white space in a 1D array.
 * sortTableRowsByField() - sort table rows by field.
 * limitMaxRows() - keep max nbr rows in front of the Table.
 * limitMaxRowsSortedByField() - sort Table by field, then keep max nbr rows
 * 
* *

* This code is available at the HTMLtools project on SourceForge at * http://htmltools.sourceforge.net/ * under the "Common Public License Version 1.0" * * http://www.opensource.org/licenses/cpl1.0.php.

*

* It was derived and refactored from the open source * MAExplorer (http://maexplorer.sourceforge.net/), and * Open2Dprot (http://Open2Dprot.sourceforge.net/) Table modules. *

* $Date: 2009/11/23 11:45:56 $ $Revision: 1.37 $ *
* Copyright 2008, 2009 by Peter Lemkin * E-Mail: lemkin@users.sourceforge.net * http://lemkingroup.com/ *
*/ public class FileTable extends FileIO { /** Global utilities UtilCM instance */ public UtilCM util; /** Set to true (default) if has table header). */ public boolean hasTableHeaderRowFlag; /** Set to true if has table may have duplicate fields. */ public boolean ignoreDuplicateFieldsFlag; /** Set to true to look for empty line(s) BEFORE the table data */ public boolean hasEmptyLineBeforeTableFlag; /** Set to true if remove trailing blank lines from the table */ public boolean rmvTrailingBlankLinesFlag; /** Set to true to remove empty trailing columns defined as columns * in a Table with a header as null column names. */ public boolean rmvTrailingEmptyColumnsFlag; /** Set to string known to be in header. When parsing the * table input string, we look for this value to determine the * line where the Table starts. */ public String startTblColStr; /** Reduce the number of header lines to 1 even if there are more * than 1 header line. */ public boolean useOnlyLastHeaderLineFlag; /** size of input buffer */ public int bufSize; /** List of unmodified tField names, if not null with size * nUnmodifiedFieldNames. This is the maximum size of tFields[], * i.e., tCols and may be setup by other methods in other classes * (e.g., see Convert.. */ public String unmodifiedFieldNames[]= null; /** Size of unmodifiedFieldNames[], the list of unmodified tField[] * names. */ public int nUnmodifiedFieldNames= 0; /** Line number of the first row with table data * (Table Fields or Data if no Fields) */ public int firstDataRowLineNbr; /** Char index of input buffer first row with table data * (Table Fields or Data if no Fields). */ public int firstDataRowCharIdx; /** Number of lines of the Table header "-hdr:{nbr of lines in header}" * switches. The number of Table header lines with default of 1. * If > 1 line, then the Table Fields searched for URL mapping are the * last one in the header row. All header lines are bolded with TH * rather than TD. The tokens for the last of these header lines is * also saved in lastTblHdrRow[0:tCols] to be used by the Column to * URL mapping... */ public int nbrTableHdrLines= 1; /** file to read or write I/O */ public String fileName; /** number of header rows (default is 1) */ public int tHdrRows; /** number of columns/row in the table. */ public int tCols; /** number of rows. i.e. number row Clone Id's */ public int tRows; /** Table name if any */ public String tName; /** Table preface if any. This is parsed from the initial text * prior to the actual table if it was separated by a * blank line or the table defined by a tField keyword. */ public String tPreface; /** names of table headers of size [0:tHdrRows-1][0:tCols-1]. * Some of the non-primary (i.e., tField[]) header rows may not have * tCols of data (i.e., short rows). These are filled with "". */ public String tHeader[][]; /** names of table fields. These are the primary table column names. */ public String tFields[]; /** row data cell vectors [0:tRows-1][0:tCols-1] */ public String tData[][]; /* ---- Index-maps of the data for random access of the data ---- */ /** Index-map byte pointers of the start of header lines synced * with tHeader rows. */ public long lineStartHeaderBytePtr[]; /** Index-map byte pointers of the start of header lines synced * with tHeader rows. */ public long lineEndHeaderBytePtr[]; /** Index-map byte pointers of the start of data lines synced * with tData rows. */ public long lineStartDataBytePtr[]; /** Index-map byte pointers of the start of data lines synced * with tData rows. */ public long lineEndDataBytePtr[]; /** List of column names to be used in the left side of the ftIdxMap * Table if it is built. If it is null, do not build the index-map * since these column names are the keys used to build the index-map * Table. */ public String colNameIndexMap[]= null; /** FileTable index map where save the {colNames, "StartByte", "EndByte") * The colNames is a list with arbitrary names that can not be * either "StartByte" or "EndByte". */ public FileTable ftIdxMap= null; /** [HACK] If the table is sorted by sortTableRowsByField for numeric * values with the useAbsValueFlag set true, and both + and - changes * were found, it will set the needToSortTableAgainFlag so the calling * program can resort one more time if it wants to keep the data in * numeric order. */ public boolean needToSortTableAgainFlag= false; /** This is the (sorted) full table data BEFORE the length of the table * was limited using limitMaxRowsXXX() methods. It is null unless * the Table was limited in number of rows. This is useful * if we need to get at the full Table later. */ public String tDataFull[][]= null; /** * FileTable() - generic Constructor, set some defaults to 0. */ public FileTable() { /* FileTable */ clearTable(); /* set to empty table */ util= new UtilCM(); /* set up utility package */ } /* FileTable */ /** * FileTable() - generic Constructor, set some defaults. * @param tableName String name of table * @see UtilCM * @see #setDuplicateFieldsFlag * @see #setNbrTableHdrLines * @see #setRmvTrailingBlankLinesFlag * @see #setHasEmptyLineBeforeTableFlag */ public FileTable(String tableName) { /* FileTable */ clearTable(); /* set to empty table */ util= new UtilCM(); /* set up utility package */ this.tName= tableName; } /* FileTable */ /** * FileTable() - Constructor to make empty table of known size * @param tableName String name of table * @param rows max rows in table * @param cols max columns in table * @see UtilCM * @see #setDuplicateFieldsFlag * @see #setNbrTableHdrLines * @see #setRmvTrailingBlankLinesFlag * @see #setHasEmptyLineBeforeTableFlag */ public FileTable(String tableName, int rows, int cols) { /* FileTable */ clearTable(); /* set to empty table */ util= new UtilCM(); /* set up utility package */ this.tName= tableName; this.tRows= rows; this.tCols= cols; } /* FileTable */ /** * clearTable() - set to empty table. Generally called from Constructor */ public void clearTable() { /* clearTable */ fileName= null; tName= "none"; tRows= 0; tCols= 0; tHdrRows= 0; tFields= null; tHeader= null; tData= null; tDataFull= null; tPreface= null; unmodifiedFieldNames= null; nUnmodifiedFieldNames= 0; errMsgLog= ""; lastErrMsgLog= ""; startTblColStr= null; firstDataRowLineNbr= 0; firstDataRowCharIdx= 0; lineStartHeaderBytePtr= null; lineEndHeaderBytePtr= null; lineStartDataBytePtr= null; lineEndDataBytePtr= null; /* Set the rest using set methods */ setHasTableHeaderFlag(true); setDuplicateFieldsFlag(false); setNbrTableHdrLines(1); setHasEmptyLineBeforeTableFlag(false); setRmvTrailingBlankLinesFlag(false); setRmvTrailingEmptyColumnsFlag(false); setStartTableAtColStr(null); useOnlyLastHeaderLineFlag= false; needToSortTableAgainFlag= false; } /* clearTable */ /** * cloneTable() - clone the current Table. Copy data by value. * @param cloneTableName is new tName of the clone * @param return FileTable instance of clone if successful, else null. */ public FileTable cloneTable(String cloneTableName) { /* cloneTable */ FileTable ftC= new FileTable(cloneTableName); ftC.setHasTableHeaderFlag(this.hasTableHeaderRowFlag); ftC.setDuplicateFieldsFlag(this.ignoreDuplicateFieldsFlag); ftC.setNbrTableHdrLines(this.nbrTableHdrLines); ftC.setRmvTrailingBlankLinesFlag(this.rmvTrailingBlankLinesFlag); ftC.setRmvTrailingEmptyColumnsFlag(this.rmvTrailingEmptyColumnsFlag); ftC.setHasEmptyLineBeforeTableFlag(this.hasEmptyLineBeforeTableFlag); ftC.setStartTableAtColStr(this.startTblColStr); ftC.setUseOnlyLastHeaderLineFlag(this.useOnlyLastHeaderLineFlag); ftC.setFieldsToTable(this.tFields); ftC.setHeadersToTable(this.tHeader, this.tHdrRows, this.tCols); ftC.tRows= this.tRows; if(this.tData==null) ftC.tData= null; else { /* Copy the data by value */ ftC.tData= new String[tRows][]; for(int r=0;r 1 line, * then the Table Fields searched for URL mapping are the last one * in the header row. * The tokens for the last of these header lines is also saved in * lastTblHdrRow[0:tCols] to be used by the Column to URL mapping. * The last header row is also saved in tFields[0:tcols-1]. * All headers are saved in tHeader[0:tHdrRows-1][0:tCols] where * tCols is determined by the last header row. * Must have at least 1 line. * @param nbrTableHdrLines value */ public void setNbrTableHdrLines(int nbrTableHdrLines) { /* setNbrTableHdrLines */ this.nbrTableHdrLines= (nbrTableHdrLines<1)? 1 : nbrTableHdrLines; tHdrRows= this.nbrTableHdrLines; } /* setNbrTableHdrLines */ /** * setHasTableHeaderFlag() - set Has Table Header Flag. * @param hasTableHeaderRowFlag value */ public void setHasTableHeaderFlag(boolean hasTableHeaderRowFlag) { /* setHasTableHeaderFlag */ this.hasTableHeaderRowFlag= hasTableHeaderRowFlag; } /* setHasTableHeaderFlag */ /** * setDuplicateFieldsFlag() - set duplicate fields Flag. * @param ignoreDuplicateFieldsFlag value */ public void setDuplicateFieldsFlag(boolean ignoreDuplicateFieldsFlag) { /* setDuplicateFieldsFlag */ this.ignoreDuplicateFieldsFlag= ignoreDuplicateFieldsFlag; } /* setDuplicateFieldsFlag */ /** * setHasEmptyLineBeforeTableFlag() - set look for empty line(s) * BEFORE table data flag * @param hasEmptyLineBeforeTableFlag value */ public void setHasEmptyLineBeforeTableFlag( boolean hasEmptyLineBeforeTableFlag) { /* setHasEmptyLineBeforeTableFlag */ this.hasEmptyLineBeforeTableFlag= hasEmptyLineBeforeTableFlag; } /* setHasEmptyLineBeforeTableFlag */ /** * setRmvTrailingBlankLinesFlag() - remove trailing blank lines in * the table by shortening the end of the table. * @param rmvTrailingBlankLinesFlag value */ public void setRmvTrailingBlankLinesFlag( boolean rmvTrailingBlankLinesFlag) { /* setRmvTrailingBlankLinesFlag*/ this.rmvTrailingBlankLinesFlag= rmvTrailingBlankLinesFlag; } /* setRmvTrailingBlankLinesFlag */ /** * setRmvTrailingEmptyColumnsFlag() - set remove empty trailing columns * flag. Empty columns are defined as columns in a Table with a * header as null column names. If the flag is set, after the table is * created, we define tFields for the first empty cell. We then * rebuild tFields, tHeader and tData to the new size and set tCols * to the new size. * @param rmvTrailingEmptyColumnsFlag value */ public void setRmvTrailingEmptyColumnsFlag( boolean rmvTrailingEmptyColumnsFlag) { /* setRmvTrailingEmptyColumnsFlag */ this.rmvTrailingEmptyColumnsFlag= rmvTrailingEmptyColumnsFlag; } /* setRmvTrailingEmptyColumnsFlag */ /** * setStartTableAtColStr() - start Table at string known to be in header. * @param startTblColStr - value that is known to be in the header */ public void setStartTableAtColStr(String startTblColStr) { /* setStartTableAtColStr */ this.startTblColStr= startTblColStr; } /* setStartTableAtColStr */ /** * setColNameIndexMapKeys() - set column names keys for ftIdxMap index-map. * If it is null, do not build the index-map since these column names * are the keys used to build the index-map Table. * @param colNameIndexMap - list of column names keys for ftIdxMap index-map. */ public void setColNameIndexMapKeys(String colNameIndexMap[]) { /* setColNameIndexMapKeys */ this.colNameIndexMap= colNameIndexMap; } /* setColNameIndexMapKeys */ /** * setUseOnlyLastHeaderLineFlag() - set reduce number of header lines to 1 * even if there are more than 1 header lines. If the tHeader list exists, * it shrinks it to 1 row and copies the previous last tHeader to to the * new list. * @param useOnlyLastHeaderLineFlag - value to set the flag. */ public void setUseOnlyLastHeaderLineFlag(boolean useOnlyLastHeaderLineFlag) { /* setUseOnlyLastHeaderLineFlag */ this.useOnlyLastHeaderLineFlag= useOnlyLastHeaderLineFlag; if(useOnlyLastHeaderLineFlag && (tHeader!=null && tHdrRows>1 && tCols>0 && tFields!=null)) { /* Reduce the number of headers to 1 in the list */ String newTheader[][]= new String[1][]; newTheader[0]= tHeader[tHdrRows-1]; tHeader= newTheader; /* Set so only the last row of the header list */ tHdrRows= 1; } /* Reduce the number of headersto 1 in the list */ } /* setUseOnlyLastHeaderLineFlag */ /** * readTableRowFromFile() - random access read row r token list from * indexed file. This assumes that there is a ftIdxMap FileTable created * by MakeIndexFile for the file or. * @param fileName file name * @param r is the row to retrieve * @param ftIdxMap is the FileTable index previously created by * MakeIndexFile for the file. * @param nullFillStr is the string to replace all null strings * if not null. * @return string row of file if successful, else null if any errors. * @see FileIO#readRandomAccessLine */ public String[] readTableLineRowFromFile(String fileName, int r, FileTable ftIdxMap, String nullFillStr) { /* readTableLineRowFromFile */ String lineData= readLineRowFromFile(fileName, r, ftIdxMap); if(lineData==null) return(null); String tokenList[]= UtilCM.cvs2ArrayNullFill(lineData, "\t", nullFillStr); return(tokenList); } /* readTableLineRowFromFile */ /** * readLineRowFromFile() - random access read row r data from indexed file. * This assumes that there is a ftIdxMap FileTable created by * parseTableFromString(), readAndParseTableIndexMap(), readAndParseTable(). * or MakeIndexFile for the file. * @param fileName file name * @param r is the row to retrieve * @param ftIdxMap is the FileTable index previously created by * MakeIndexFile for the file. * @return string row of file if successful, else null if any errors. * @see FileIO#readRandomAccessLine */ public String readLineRowFromFile(String fileName, int r, FileTable ftIdxMap) { /* readLineRowFromFile */ if(ftIdxMap==null || !ftIdxMap.fileName.equals(fileName)) return(null); if(r>=tRows) return(null); /* Get the start and end byte pointers on the disk */ int idxStartByte= ftIdxMap.lookupFieldIdx("StartByte"), idxEndByte= ftIdxMap.lookupFieldIdx("EndByte"); if(idxStartByte==-1 || idxEndByte==-1) return(null); String mapRowData[]= ftIdxMap.tData[r], strStartByte= mapRowData[idxStartByte], strEndByte= mapRowData[idxEndByte]; long startBytePtr= UtilCM.cvs2l(strStartByte,-1), endBytePtr= UtilCM.cvs2l(strEndByte,-1); if(startBytePtr==-1 || endBytePtr==-1) return(null); /* Now read the data. Just read from startBytePtr until * end-of-line. */ String rawData= readRandomAccessLine(fileName,startBytePtr); int expectedLth= (int)(endBytePtr-startBytePtr+1); if(rawData.length()!=expectedLth) { UtilCM.logMsg("I/O error file '"+fileName+ "' read.length="+rawData.length()+ " NEQ (endBytePtr-startBytePtr+1)="+expectedLth+ " startBytePtr="+startBytePtr+ " endBytePtr="+endBytePtr+ " for row="+r+"\n"); return(null); } return(rawData); } /* readLineRowFromFile */ /** * checkForBadTable() - verify that it is a well formed Table * with all field names present and with no null, "" or duplicate * entries, and tRows EQ tData.length. * @return true if successful, also stuff error message into * "errMsgLog" to be displayed later. */ public boolean checkForBadTable() { /* checkForBadTable */ if(tData!=null && tData.length!=tRows) { errMsgLog += "FT-CFBT bad Table["+tName+ "] data row size tData.length="+ tData.length+" tRows="+tRows+"\n"; lastErrMsgLog= errMsgLog; return(false); } /* Check the header */ if(!checkForBadTableHeader()) return(false); return(true); } /* checkForBadTable */ /** * checkForBadTableHeader() - verify that is a well formed tField Table * with all field names present and with no null, "" or duplicate * entries. * @return true if successful, also stuff error message into * "errMsgLog" to be displayed later. */ public boolean checkForBadTableHeader() { /* checkForBadTableHeader */ if(hasTableHeaderRowFlag && tCols<=0) { errMsgLog += "FT-CFBTH bad Table tCols="+tCols+"\n"; return(false); } if(hasTableHeaderRowFlag && (tFields==null || tFields.length!=tCols)) { errMsgLog += "FT-CFBTH bad Table["+tName+"] tFields.length="+ tFields.length+" NEQ tCols="+tCols+"\n"; lastErrMsgLog= errMsgLog; return(false); } /* Look for errors: null, "" or duplicate Field name entries */ if(hasTableHeaderRowFlag) { /* has table - check it */ for(int c=0;c=1) { errMsgLog += "FT-CFBTH bad Table["+tName+ "] no tHeader data.\n"; lastErrMsgLog= errMsgLog; return(false); } if(tHeader!=null && !tFields[c].equals(tHeader[tHdrRows-1][c])) { errMsgLog += "FT-CFBTH bad Table["+tName+"] tFields["+c+ "] NEQ last tHeader["+(tHdrRows-1)+"]["+c+ "].\n"; lastErrMsgLog= errMsgLog; return(false); } if(!ignoreDuplicateFieldsFlag) for(int c2= 0; c2 < tCols; c2++) if(c != c2 && tFields[c].equals(tFields[c2])) { errMsgLog += "FT-CFBT bad Table["+tName+ "] dupl. fields: #" + c + " and #" + c2 + " [" + tFields[c] + "] with value='"+tFields[c]+"'\n"; lastErrMsgLog= errMsgLog; return (false); } } /* check fields */ } /* has table - check it */ return(true); } /* checkForBadTableHeader */ /** * parseTableFromString() - convert tab-delim string to Table structure. * Set up the table data structures in this instance of FileTable: * (tHeader,tPreface,tRows,tCols,tFields[],tData[][],tCols,tRows,tHdrNbr). *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* @param tblStr is String to parse and convert to Table * @return true if successful, else errMsgLog has reason failed. * @see #rmvTrailingBlankLinesFromTable * @see #deleteTrailingEmptyColumnsFromTable * @see #checkForBadTable */ public boolean parseTableFromString(String tblStr) { /* parseTableFromString */ if(tblStr==null) return(false); /* [1] Setup parser variables */ int tDataSize= 0, nbrRowsIncr= 100, lineTermSize= 0, countLines= 0, /* local line counter */ countMoreHdrLines= 0; /* used to save hdr lines before parse * after found blank line */ long startByte= 0, /* buffer ptr at start of a line */ endByte= 0; /* buffer ptr at end of a line */ /* [1.1] Find terminator as either \r, \n or \r\n */ String lineSep= this.getLineSeparatorFromString(tblStr); /* Set to the first line of tData when found */ firstDataRowLineNbr= -1; /* [2] Read and parse the tab-delimited data file into the Table * and get the tPreface as all data before the Header. */ /* Read file and parse data into Table */ /* [2.1] Get the file line separator and it's size. * It is either \r, \n or \r\n - or null if an error */ if(lineSep==null) { /* Handle errors */ errMsgLog += "MIF-RAPF no \r, \n or \r\n terminator in " + "input string.\n"; lastErrMsgLog= errMsgLog; return (false); } lineTermSize= lineSep.length(); /* [2.2] Init parse variables */ String lookbackHdrs[]= new String[nbrTableHdrLines], tokens[]= null, /* list of tokens in the inputLine */ inputLine = null; /* lines read from buffered reader */ long lookbackStartBytePtr[]= new long[nbrTableHdrLines+1], lookbackEndBytePtr[]= new long[nbrTableHdrLines+1]; boolean foundDataRowFlag= false, foundHdrRowFlag= false; /* We saw header row in file, else error */ tHdrRows= nbrTableHdrLines; tRows= 0; /* count first row */ tCols= 0; /* there is at least 1 Row if fields*/ /* [3] Read lines from the input file */ int fromIndex= 0, idx= 0; while(fromIndex!=-1 && idx!=-1) { /* process lines */ idx= tblStr.indexOf(lineSep, fromIndex); if(idx==-1) inputLine= tblStr; else { inputLine= tblStr.substring(fromIndex,idx); fromIndex= idx+lineTermSize; /* advance the line pointer */ } countLines++; /* [3.1] Parse the Header and save header lines, backwards, * for parsing into the tHeader and tFields. */ if(!foundHdrRowFlag) { /* save until parsed header */ /* [3.1.1] Save Preface with stuff falling out of lookback */ String prefaceLine= lookbackHdrs[tHdrRows-1]; if(prefaceLine!=null) { /* only push non-null lines to the tPreface */ if(tPreface==null) tPreface= prefaceLine +"\n"; else tPreface += prefaceLine +"\n"; } /* [3.1.2] Push data back into the look back list */ for(int lb=(tHdrRows-2); lb>=0; lb--) { /* lb is backwards index */ lookbackHdrs[lb+1]= lookbackHdrs[lb]; lookbackStartBytePtr[lb+1]= lookbackStartBytePtr[lb]; lookbackEndBytePtr[lb+1]= lookbackEndBytePtr[lb]; } /* [3.2] Compute the line byte pointers with terminator */ startByte= endByte+1; endByte += inputLine.length() + lineTermSize; /* [3.3] Save current line at end of look back stack. */ lookbackHdrs[0]= inputLine; lookbackStartBytePtr[0]= startByte; lookbackEndBytePtr[0]= endByte; /* [3.4] Read the rest of the header lines. */ if(countMoreHdrLines>0) { /* save more header lines until ready to parse */ if(--countMoreHdrLines<=0) foundHdrRowFlag= true; else continue; /* keep saving lines */ } /* [3.5] Look for the header starting with specific * string in the tFields (last row of the tHeader. */ if(startTblColStr!=null) { /* look for string known in header for start of Table */ int idxKeyword= inputLine.indexOf(startTblColStr); if(idxKeyword!=-1) { /* Found the header tFields row */ foundHdrRowFlag= true; } } /* [3.6] Alternately, look for empty line after which the table * will start. This can be problematic if there are multiple * blank lines. */ else if(hasEmptyLineBeforeTableFlag) { /* scan through the preface */ if(inputLine.length()==0) { /* found empty line - assume before header */ countMoreHdrLines= nbrTableHdrLines; continue; } /* found empty line - assume before header */ } else if(countLines>=tHdrRows) { /* captured header line since no empty lines before Table */ foundHdrRowFlag= true; } /* [3.7] Continue scanning for the header */ if(!foundHdrRowFlag && !foundDataRowFlag) continue; /* keep scanning */ /* [4] Found the header - save it in tFields and tHeader */ else if(foundHdrRowFlag && !foundDataRowFlag) { /* Process the header rows */ if(countLines==1) tPreface= null; /* there is no preface */ /* [4.1] Save the tFields[] */ inputLine= lookbackHdrs[0]; tFields= UtilCM.cvs2Array(inputLine, "\t"); tCols= tFields.length; /* [4.2] Now save the header rows */ tHeader= new String[tHdrRows][]; lineStartHeaderBytePtr= new long[tHdrRows]; lineEndHeaderBytePtr= new long[tHdrRows]; for(int h=0;h= tDataSize) { /* grow the arrays by nbrRowsIncr */ tDataSize += nbrRowsIncr; String tDataNew[][]= new String[tDataSize][]; long lineStartDataBytePtrNew[]= new long[tDataSize]; long lineEndDataBytePtrNew[]= new long[tDataSize]; for(int r=0;r * (tHeader,tPreface,tRows,tCols,tFields[],tCols,tRows,tHdrNbr).
* This does NOT save the tData rows. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader and * lineStartDataBytePtr[], and lineEndDataBytePtr[] for the tData. *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* @param inputFile is file to read and convert to table * @return true if successful, else errMsgLog has reason failed. * @see #readAndParseTableData */ public boolean readAndParseTableIndexMap(String inputFile) { /* readAndParseTableIndexMap */ return(readAndParseTableData(inputFile, false, false, true)); } /* readAndParseTableIndexMap */ /** * readAndParseTableAll() - read & convert tab-delim file to Table * structure. Set up the table data structures in this instance of * FileTable:
* (tHeader,tPreface,tRows,tCols,tFields[],tData[][],tCols,tRows,tHdrNbr). *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader and * lineStartDataBytePtr[], and lineEndDataBytePtr[] for the tData. * It creates the ftIdxMap index-Map Table as one of the elements * of this Table. *

* @param inputFile is file to read and convert to table * @return true if successful, else errMsgLog has reason failed. * @see #readAndParseTableData */ public boolean readAndParseTableAll(String inputFile) { /* readAndParseTableAll */ return(readAndParseTableData(inputFile, false, true, true)); } /* readAndParseTableAll */ /** * readAndParseTable() - read & convert tab-delim file to Table * structure. Set up the table data structures in this instance of * FileTable:
* (tHeader,tPreface,tRows,tCols,tFields[],tData[][],tCols,tRows,tHdrNbr). *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader and * lineStartDataBytePtr[], and lineEndDataBytePtr[] for the tData. * It does not create the ftIdxMap index-Map Table. *

* @param inputFile is file to read and convert to table * @return true if successful, else errMsgLog has reason failed. * @see #readAndParseTableData */ public boolean readAndParseTable(String inputFile) { /* readAndParseTable */ return(readAndParseTableData(inputFile, false, true, false)); } /* readAndParseTable */ /** * readAndParseTableFields() - read tab-delim file to tFields Table * structure. Set up the table data structures in this instance of * FileTable: * (tHeader,tPreface,tRows,tCols,tFields[],tCols,tRows,tHdrNbr).
* This does NOT save the tData rows. *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader. * It does not read the tData rows, but instead closes the file and returns. * So the index map is incomplete. *

* @param inputFile is file to read and convert to table * @return true if successful, else errMsgLog has reason failed. * @see #readAndParseTableData */ public boolean readAndParseTableFields(String inputFile) { /* readAndParseTableFields */ return(readAndParseTableData(inputFile, true, false, false)); } /* readAndParseTableFields */ /** * readAndParseTableFieldsAndIndexMap - read tab-delim file to tFields * and ftIdxMap Table. Set up the table data structures in this instance * of FileTable: * (tHeader,tPreface,tRows,tCols,tFields[],tCols,tRows,tHdrNbr).
* This does NOT save the tData rows. *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader. * It creates the ftIdxMap index-Map Table as one of the elements * of this Table. * It does not read the tData rows, but instead closes the file and * returns. *

* @param inputFile is file to read and convert to table * @return true if successful, else errMsgLog has reason failed. * @see #readAndParseTableData */ public boolean readAndParseTableFieldsAndIndexMap(String inputFile) { /* readAndParseTableFieldsAndIndexMap */ return(readAndParseTableData(inputFile, true, false, true)); } /* readAndParseTableFieldsAndIndexMap */ /** * readAndParseTableData() - read & convert tab-delim file to Table * structure. Set up the table data structures in this instance of * FileTable: * (tHeader,tPreface,tRows,tCols,tFields[],tData[][],tCols,tRows,tHdrNbr). *

* If the table is not at the front of the file, one option is to * find the start of the table by searching for a this.startTblColStr * string if you know a unique string in the header row. This is used * if startTblColStr is not blank (set by setStartTableAtColStr()). * If it matches, we then search backwards to the front of the string * for a '\n' which is the end of the previous row. *

* An alternate method for finding the start of the file, if the * this.hasEmptyLineBeforeTableFlag is set, is to look for the first * non-empty empty row after seeing an empty row that it is then * assumed to be the Table data (Fields + data or data) follows. * This can be problematic if there are multiple blank lines. *

* When the front of the table is found, then it sets * this.firstDataRowLineNbr to that line number. * If there is no empty row found, then it assumes there is no extra * data before the table and it sets this.firstDataRowLineNbr to 0. * If there is text before the Table, then it is put into this.tPreface. *

* It also sets the the Index-Map byte pointers * lineStartHeaderBytePtr[], lineEndHeaderBytePtr[] for the tHeader and * lineStartDataBytePtr[], and lineEndDataBytePtr[] for the tData if * making an Index-Map ftIdxMap. * If makeIndexMapFlag is set it creates the ftIdxMap index-Map Table * as one of the elements of this Table. *

* @param inputFile is file to read and convert to table * @param onlyReadHeaderFlag only read the tPreface, tFields and tHeader * and then stop reading the file unless making an index map. * @param saveTblRowDataFlag to save the tData rows, else just compute * the Index-Map for the data rows. * @param makeIndexMapFlag make an index map of Table in this.ftIdxMap * @return true if successful, else errMsgLog has reason failed. * @see #rmvTrailingBlankLinesFromTable * @see #deleteTrailingEmptyColumnsFromTable * @see #checkForBadTable */ public boolean readAndParseTableData(String inputFile, boolean onlyReadHeaderFlag, boolean saveTblRowDataFlag, boolean makeIndexMapFlag) { /* readAndParseTableData */ if(inputFile==null) return(false); /* [1] Setup parser variables */ int DBUG_NBR_LINES= -1; /* (5) [TODO] remove this DEBUG code when working fully. */ int tDataSize= 0, nbrRowsIncr= 100, lineTermSize= -1, /* will compute it */ countLines= 0, /* local line counter */ countMoreHdrLines= 0; /* used to save hdr lines before parse * after found blank line */ long startByte= 0, /* buffer ptr at start of a line */ endByte= 0; /* buffer ptr at end of a line. */ int nColNameIndexMap= 0, /* this.colNameIndexMap.length if used */ ftIMcols= 0, /* Nbr of ftIdxMap field indexes if index-map used */ idxColNames[]= null; /* ftIdxMap field indexes if index-map used */ /* Fix file separators if need to do it. */ inputFile= mapPathFileSeparators(inputFile); /* [1.1] Find terminator as either \r, \n or \r\n */ String fileLineSep= this.getLineSeparatorFromFile(inputFile); /* Set to the first line of tData when found */ firstDataRowLineNbr= -1; /* If making Index-Map, setup the empty ftIdxMap Table */ if(!makeIndexMapFlag) ftIdxMap= null; /* There is no map Table until read data rows */ else { /* setup the empty ftIdxMap Table */ ftIdxMap= new FileTable("File-Index-Map"); ftIdxMap.setHasTableHeaderFlag(true); ftIdxMap.setDuplicateFieldsFlag(false); ftIdxMap.setNbrTableHdrLines(1); ftIdxMap.setRmvTrailingBlankLinesFlag(false); ftIdxMap.setHasEmptyLineBeforeTableFlag(false); } /* setup the empty ftIdxMap Table */ /* [2] Read and parse the tab-delimited data file into the Table * and get the tPreface as all data before the Header. */ try { /* Read file and parse data into Table */ RandomAccessFile rafL= new RandomAccessFile(inputFile, "r"); /* [2.1] Get the file line separator and it's size. * It is either \r, \n or \r\n - or null if an error */ if(fileLineSep==null) { /* Handle errors */ errMsgLog += "MIF-RAPF no \r, \n or \r\n terminator in '" + inputFile + "'\n"; lastErrMsgLog= errMsgLog; return (false); } /* [2.2] Init parse variables */ String lookbackHdrs[]= new String[nbrTableHdrLines], tokens[]= null, /* list of tokens in the inputLine */ inputLine = null; /* lines read from buffered reader */ long lookbackStartBytePtr[]= new long[nbrTableHdrLines+1], lookbackEndBytePtr[]= new long[nbrTableHdrLines+1]; boolean beforeFirstDataLineFlag= true, /* set false on 1st data row */ foundDataRowFlag= false, /* set at end of the header */ foundHdrRowFlag= false; /* saw header row in file, else error */ tHdrRows= nbrTableHdrLines; tRows= 0; /* count first row */ tCols= 0; /* there is at least 1 Row if fields*/ /* [3] Read lines from the input file using the RandomAccessFile * readLine() fct that stops at and ignores line terminators "\n", * "\r", or "\r\n". */ /* Compute the extra line terminate length we will add to each startByte * after 1st so aligned correctly. * Note that the lineTermSize of the file may be different from * that of the operating system (for various reasons such as being * edited by an editor that changes "\n" or "\r", to "\r\n"). * Therefore, we need to compute it for the current file. */ lineTermSize= computeFileLineTermSize(rafL, 0L); if(lineTermSize==-1) { UtilCM.logMsg("Problem computing file line terminator size for "+ "file '"+inputFile+"- aborting.\n"); return(false); } endByte= -lineTermSize; /* set for first line computations */ long curFilePtr= rafL.getFilePointer(); /* Should be 0L */ while((inputLine= rafL.readLine()) != null) { /* process lines */ countLines++; /* first row is defined as line 1, not 0 */ /* [3.1] Compute the line byte pointers with terminator. * Note: startByte starts and old endByte + lineTermSize + 1. */ int //offset= ((countLines==1)? 0 : lineTermSize+1), /* no offset first line */ lineLth= inputLine.length(); /* does not include terminator */ long prevFilePtr= curFilePtr; /* Save it */ curFilePtr= rafL.getFilePointer(); /* After the read */ startByte= prevFilePtr; /* starts after prev line terminator */ endByte= curFilePtr-lineTermSize; /* does not include term.r */ /* [3.1.1] DEBUG TEST if Index-Map byte seek pointers are correct. * [TODO] remove this DEBUG code when working fully. */ if(false && countLines=0; lb--) { /* lb is backwards index */ lookbackHdrs[lb+1]= lookbackHdrs[lb]; lookbackStartBytePtr[lb+1]= lookbackStartBytePtr[lb]; lookbackEndBytePtr[lb+1]= lookbackEndBytePtr[lb]; } /* [3.3] Save current line at end of look back stack. */ lookbackHdrs[0]= inputLine; lookbackStartBytePtr[0]= startByte; lookbackEndBytePtr[0]= endByte; /* [3.4] Read the rest of the header lines. */ if(countMoreHdrLines>0) { /* save more header lines until ready to parse */ if(--countMoreHdrLines<=0) foundHdrRowFlag= true; else continue; /* keep saving lines */ } /* [3.5] Look for the header starting with specific * string in the tFields (last row of the tHeader. */ if(startTblColStr!=null) { /* look for string known in header for start of Table */ int idxKeyword= inputLine.indexOf(startTblColStr); if(idxKeyword!=-1) { /* Found the header tFields row */ foundHdrRowFlag= true; } } /* [3.6] Alternately, look for empty line after which the table * will start. This can be problematic if there are multiple * blank lines. */ else if(hasEmptyLineBeforeTableFlag) { /* scan through the preface */ if(inputLine.length()==0) { /* found empty line - assume before header */ countMoreHdrLines= nbrTableHdrLines; continue; } /* found empty line - assume before header */ } else if(countLines>=tHdrRows) { /* captured header line since no empty lines before Table */ foundHdrRowFlag= true; } /* [3.7] Continue scanning for the header */ if(!foundHdrRowFlag && !foundDataRowFlag) continue; /* keep scanning for header or data */ /* [4] Found the header - save it in tFields and tHeader */ else if(foundHdrRowFlag && !foundDataRowFlag) { /* Process the header rows */ if(countLines==1) tPreface= null; /* there is no preface */ /* [4.1] Save the tFields[] */ inputLine= lookbackHdrs[0]; tFields= UtilCM.cvs2Array(inputLine, "\t"); tCols= tFields.length; /* [4.2] Now save the header rows */ tHeader= new String[tHdrRows][]; lineStartHeaderBytePtr= new long[tHdrRows]; lineEndHeaderBytePtr= new long[tHdrRows]; for(int h=0;h= tDataSize) { /* grow the arrays by nbrRowsIncr */ tDataSize += nbrRowsIncr; if(saveTblRowDataFlag) { /* expand this Table data */ String tDataNew[][]= new String[tDataSize][]; for(int r= 0; r < tRows; r++) tDataNew[r]= tData[r]; tData= tDataNew; } if(ftIdxMap!=null) { /* expand ftIdxMap Table data */ String tDataNew[][]= new String[tDataSize][]; for(int r= 0; r < tRows; r++) tDataNew[r]= ftIdxMap.tData[r]; ftIdxMap.tData= tDataNew; } long lineStartDataBytePtrNew[]= new long[tDataSize]; long lineEndDataBytePtrNew[]= new long[tDataSize]; for(int r= 0; r < tRows; r++) { /* copy data */ lineStartDataBytePtrNew[r]= lineStartDataBytePtr[r]; lineEndDataBytePtrNew[r]= lineEndDataBytePtr[r]; } lineStartDataBytePtr= lineStartDataBytePtrNew; lineEndDataBytePtr= lineEndDataBytePtrNew; } /* grow the arrays by nbrRowsIncr */ } /* alloc the tData array */ /* [5.3.1] Copy the data row for this line to Table tData * only if saving the data. But copy the index map data. */ if(saveTblRowDataFlag) tData[tRows]= tokens; if(saveTblRowDataFlag || ftIdxMap!=null) { /* Save the Index-Map byte pointers */ lineStartDataBytePtr[tRows]= startByte; lineEndDataBytePtr[tRows]= endByte; } /* [5.3.1.1] DEBUG TEST if Index-Map byte seek pointers are correct. * [TODO] remove this DEBUG code when working fully. */ if(false && countLines0) UtilCM.logMsg(" errMsgLog="+this.errMsgLog+"\n"); } /* ***DEBUG*** TEST if seek pointers are correct */ /* [5.3.2] Build the ftIdxMap data Table row by row */ if(ftIdxMap!=null) { /* add ftIdxMap row */ String imDataRow[]= new String[ftIMcols]; /* copy the colNameIndexMap[] key values */ int c, k= 0; for(int m=0;m=0L) rafL.seek(setSeekPtr); /* Rewind the file to the beginning */ } catch (IOException e) { lineTermSize= -1; } return(lineTermSize); } /* computeFileLineTermSize */ /** * cvtTableToTabDelimStr() - convert Table to tab delimited string. * This converts out multi-line tHeader data if they exist and then * the tData. * @param addHdrFlag - add the header (i.e., tFields) to front * @return converted string, else null */ public String cvtTableToTabDelimStr(boolean addHdrFlag) { /* cvtTableToTabDelimStr */ /* [1] validate Table data */ if(tCols==0 || tRows==0 || addHdrFlag && tFields==null) return(null); /* bad data */ String cell, rowData[], sRowData, sHdr= "", sTbl= ""; /* [2] Add the header if requested */ if(addHdrFlag) { /* add the header */ int nHdrs= (tHeader==null) ? 1 : tHdrRows; for(int h=0;h=0;r--) { /* look backwards for 1st non-blank row */ tRowsData= tData[r]; if(tRowsData==null) { /* no row data */ continue; } for(c=0;c0) { lastNonblankRow= r; break; } } /* If find any non-blank cell, then row is not blank */ if(lastNonblankRow!=-1) break; } /* look backwards for 1st non-blank row */ if(lastNonblankRow<=0) return(false); /* no blank rows - just return */ if((lastNonblankRow+1)==tRows) return(false); /* same size - just return */ /* Shorten the table by copying tData[0:lastNonBlankRow] to new * String array and then set tData to the new array. */ String tDataNew[][]= new String[lastNonblankRow+1][]; for(r=0;r<=lastNonblankRow;r++) tDataNew[r]= tData[r]; tData= tDataNew; tRows= lastNonblankRow+1; return(true); } /* rmvTrailingBlankLinesFromTable */ /** * deleteTableColumnByName() - delete a column from the table. * Remove the column from the tFields[] and sData[][] and * decrement tCols if succeed. It will also delete the tData column * if tData exists (i.e., is not null). * @param columnName of column to be deleted if found. * @return true if succeed, else if fail the errMsgLog string * is set with the reason it failed. * @see #deleteTableColumnByColIdx */ public boolean deleteTableColumnByName(String columnName) { /* deleteTableColumnByName */ /* [1] find the column index to delete */ int c, cIdx= -1; for(c=0;c= tData.length || rowsToDelete[i] >= tRows || rowsToDelete[i] < 0) { errMsgLog += "DRYROT bad Table["+tName+"] row index ("+ rowsToDelete[i]+ ") is outside of row range [0:"+tRows+"]\n"; lastErrMsgLog= errMsgLog; return(false); } } for(int i=0;i0 && tHeader!=null && tHeader[tHdrRows-1]!=null) tHeader[tHdrRows-1][c]= new String(newName); break; } } /* map m'th map entry if match */ return(true); } /* mapHeaderColNames */ /** * setFieldsToTable() - set the Fields list to the Table and set tCols. * @param fieldNames is the list of field column names. * @return true if added fieldNames to Table */ public boolean setFieldsToTable(String fieldNames[]) { /* setFieldsToTable */ if(fieldNames==null) return(false); tCols= fieldNames.length; tFields= new String[tCols]; for(int c=0;c0 && colData.length!=tRows))) return(false); if((newHdrCols!=null && tHeader!=null) && newHdrCols.length!=tHdrRows) return(false); /* [1] See if column exists */ int colIdx= lookupFieldIdx(newFieldName); /* [2] If the first column does not exist and there is no data in Table * then make a new tData[][] and set tRows to 1. */ if(colIdx==-1 && tCols==0) { /* create new one column Table */ tCols= 1; colIdx= 0; tFields= new String[1]; tFields[0]= newFieldName; if(tHdrRows>0) { /* Make 1 column header row */ tHeader= new String[tHdrRows][]; for(int h=0;h0) { /* If header exists, extend it */ newHeaders= new String[tHdrRows][]; for(int h=0;h0 && tData==null) { tData= new String[tRows][]; /* sync it */ for(int r=0;rnNonNullRows) { /* Shrink the array by removing empty cells at the end */ String newColData[]= new String[nNonNullRows]; for(int r=0;rtRows) return(false); int siLth= sortIdx.length, rFrom; String rowData[]= null, tData2[][]= new String[siLth][]; for(int rTo=0;rTo * Multiple new columns must be different. All reorderColName[] * names must be in tFields or it is an error. * Those columns not specified are moved toward the right. * This is done after the list of dropped columns has been * processed. It will also reorder the tData column * if tData exists (i.e., is not null). *

* if reorderRemainingColumnsAlphabeticlyFlag is set, then sort the * remaining columns not specified, but that are used, alphabetically. *

* @param reordColName - list of column names to be reordered * @param reordColNbr - corresponding list of new column numbers * starting at 1 (used specified, but we * map to n-1). * @param nReorderColName - number of mappings * @param reorderRemainingColsFlag to sort the remaining * columns not specified, but that are used, * alphabetically. * @return true if succeed else if fail the errMsgLog string * is set with the reason it failed. */ public boolean reorderColumnsInTable(String reordColName[], int reordColNbr[], int nReorderColName, boolean reorderRemainingColsFlag) { /* reorderColumnsInTable */ String roColName, copyReorderColName[]= new String[nReorderColName]; int roColIdx, copyReorderColNbr[]= new int[nReorderColName], c; boolean flag; /* [1] Make sure have tFields */ if(tFields==null) { /* No tFields in the Table */ errMsgLog += "FT-RCIT there is no Table (no tFields) header" + " - ignoring reorder.\n"; lastErrMsgLog= errMsgLog; return(false); } if(tFields.length!=tCols) { /* No tFields in the Table */ errMsgLog += "FT-RCIT DRYROT |tFields|="+tFields.length+ " .NEQ tCols="+tCols + " - ignoring reorder.\n"; lastErrMsgLog= errMsgLog; return(false); } if(nReorderColName==0 || reordColName==null) { /* No tFields in the Table */ errMsgLog += "FT-RCIT there is no Reorder columns list" + " - ignoring reorder.\n"; lastErrMsgLog= errMsgLog; return(false); } /* work off of local copy */ for(int i=0;i reorderColNbr[n] to keep it legal. */ int missingColToNbr= copyReorderColNbr[n], newToNbr= -1; for(int n1=n;n1<(nReorderColName-1);n1++) { /* remove it from the reorder list */ copyReorderColName[n1]= copyReorderColName[n1+1]; newToNbr= copyReorderColNbr[n1+1]; if(newToNbr>missingColToNbr) newToNbr--; /* decrement it to keep it legal */ copyReorderColNbr[n1]= newToNbr; } /* remove it from the reorder list */ nReorderColName--; /* shorten the list */ n--; /* Backup the search by 1 */ copyReorderColName[nReorderColName]= null; copyReorderColNbr[nReorderColName]= -1; } } /* make sure roColName is in tFields */ /* [2] Make sure all reorder new col indexes are different. */ for(int n=0;n0) for(int h=0;h0) for(int h=0;h0) { hdrColJ= new String[ftJoin.tHdrRows]; for(int h=0;h0) sawPositiveValueFlag= true; else if(dValue>0) sawNegativeValueFlag= true; doubleData[r]= (useAbsValueFlag) ? Math.abs(dValue) : dValue; } else { /* found a non-number, so sort by string */ doubleData= null; break; } if(doubleData != null) sortIdx= Sort.bubbleSortIndex(doubleData, nRows, sortTblAscendingFlag); else sortIdx= Sort.bubbleSortIndex(colData, nRows, sortTblAscendingFlag); if(sortIdx == null) { errMsgLog= "Problem sorting table by column '" + sortField + "' - ignoring sort, continuing.\n"; return (true); } /* Reorder the entire table rows by the sortIdx. */ if(!this.reorderRowsBySortIndex(sortIdx)) { errMsgLog= "Problem reordering table by column '" + sortField + "'.\n"; return (false); } errMsgLog= "Finished sorting table by column '" + sortField + "'.\n"; /* Set global resort flag if need a resort */ boolean resortFlag= (useAbsValueFlag && sawPositiveValueFlag && sawNegativeValueFlag ); this.needToSortTableAgainFlag= resortFlag; } /* sort by column data */ return(true); } /* sortTableRowsByField */ /** * limitMaxRows() - keep max nbr rows in front of the Table. * @param maxRowsToKeep is the maximum number of rows to keep. * @param useAbsValueFlag to ignore the sign of the data in the search. * If the table is sorted by numeric value, and both + and - changes * were found, it will set the this.needToSortTableAgainFlag * so the calling program can resort one more time if it wants. * @return true if succeed or there is no change, false if fail with * error message in errMsgLog */ public boolean limitMaxRows(int maxRowsToKeep, boolean useAbsValueFlag) { /* limitMaxRows */ return(limitMaxRowsSortedByField(null,false, maxRowsToKeep, false, useAbsValueFlag)); } /* limitMaxRows */ /** * limitMaxRowsSortedByField() - sort Table by field, then keep max nbr rows. * Save the full table data in this.tDataFull BEFORE shortening the * length of the this.tData. The this.tDataFull is null unless * the Table was limited in number of rows. This is useful * if we need to get at the full Table later. * @param sortField is the field to sort by and to use in the thresholding * @param sortTblAscendingFlag * @param maxRowsToKeep is the maximum number of rows to keep. * @param useAbsValueFlag to ignore the sign of the data in the search. * If the table is sorted by numeric value, and both + and - changes * were found, it will set the this.needToSortTableAgainFlag * so the calling program can resort one more time if it wants. * @return true if succeed or there is no change, false if fail with * error message in errMsgLog */ public boolean limitMaxRowsSortedByField(String sortField, boolean sortTblAscendingFlag, int maxRowsToKeep, boolean doSortFirstFlag, boolean useAbsValueFlag) { /* limitMaxRowsSortedByField */ this.tDataFull= null; /* clear it just in case */ if(tData==null || maxRowsToKeep<=0) { errMsgLog= "Can't limit Table rows by field since Table has no data.\n"; return(false); } if(tData!=null && tRows<=maxRowsToKeep) return(true); /* [1] Sort Table before limit the rows we keep */ this.needToSortTableAgainFlag= false; /* clear the resort flag */ if(doSortFirstFlag) { /* Sort Table before limit the rows we keep */ int idxSortField= this.lookupFieldIdx(sortField); if(idxSortField==-1) { errMsgLog= "Can't limit Table rows by field since sortField '"+ sortField+"' not found.\n"; return(false); } /* [1.1] Get the column data and then do a bubble sort of that data * by magnitude. Use the sort index to then sort the rows of * the Table. */ useAbsValueFlag= true; if(!this.sortTableRowsByField(sortField, sortTblAscendingFlag, useAbsValueFlag)) return(false); /* Note it sets the errMsgLog */ } /* Sort Table before limit the rows we keep */ /* [2] Now remove any rows >= row maxRowsToKeep */ if(this.tRows>maxRowsToKeep) { /* realloc table with smaller tData */ /* Save the full table data BEFORE the length of the table * was limited using limitMaxRowsXXX() methods. It is null unless * the Table was limited in number of rows. This is useful * if we need to get at the full Table later. */ tDataFull= tData;; String new_tData[][]= new String[maxRowsToKeep][]; for(int r=0;r