// kelondroFlexWidthArray.java // (C) 2006 by Michael Peter Christen; mc@anomic.de, Frankfurt a. M., Germany // first published 01.06.2006 on http://www.anomic.de // // $LastChangedDate: 2006-04-02 22:40:07 +0200 (So, 02 Apr 2006) $ // $LastChangedRevision: 1986 $ // $LastChangedBy: orbiter $ // // LICENSE // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package de.anomic.kelondro; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import de.anomic.server.serverFileUtils; import de.anomic.server.logging.serverLog; public class kelondroFlexWidthArray implements kelondroArray { protected kelondroFixedWidthArray[] col; protected kelondroRow rowdef; protected File path; protected String tablename; protected String filename; public kelondroFlexWidthArray(File path, String tablename, kelondroRow rowdef, boolean resetOnFail) { this.path = path; this.rowdef = rowdef; this.tablename = tablename; try { init(); } catch (IOException e) { if (resetOnFail) { serverLog.logSevere("kelondroFlexWidthArray", "IOException during initialization of " + new File(path, tablename).toString() + ": reset"); delete(path, tablename); try { init(); } catch (IOException e1) { e1.printStackTrace(); throw new kelondroException("IOException during initialization of " + new File(path, tablename).toString() + ": cannot reset: " + e1.getMessage()); } } else { throw new kelondroException("IOException during initialization of " + new File(path, tablename).toString() + ": not allowed to reset: " + e.getMessage()); } } catch (kelondroException e) { if (resetOnFail) { serverLog.logSevere("kelondroFlexWidthArray", "kelondroException during initialization of " + new File(path, tablename).toString() + ": reset"); delete(path, tablename); try { init(); } catch (IOException e1) { e1.printStackTrace(); throw new kelondroException("kelondroException during initialization of " + new File(path, tablename).toString() + ": cannot reset: " + e1.getMessage()); } } else { throw new kelondroException("kelondroException during initialization of " + new File(path, tablename).toString() + ": not allowed to reset: " + e.getMessage()); } } } public void init() throws IOException { // initialize columns col = new kelondroFixedWidthArray[rowdef.columns()]; String check = ""; for (int i = 0; i < rowdef.columns(); i++) { col[i] = null; check += '_'; } // check if table directory exists File tabledir = new File(path, tablename); if (tabledir.exists()) { if (!(tabledir.isDirectory())) throw new IOException("path " + tabledir.toString() + " must be a directory"); } else { tabledir.mkdirs(); tabledir.mkdir(); } this.filename = tabledir.getCanonicalPath(); // save/check property file for this array File propfile = new File(tabledir, "properties"); Map props = new HashMap(); if (propfile.exists()) { props = serverFileUtils.loadHashMap(propfile); String stored_rowdef = (String) props.get("rowdef"); if ((stored_rowdef == null) || (!(rowdef.subsumes(new kelondroRow(stored_rowdef, rowdef.objectOrder, 0))))) { System.out.println("FATAL ERROR: stored rowdef '" + stored_rowdef + "' does not match with new rowdef '" + rowdef + "' for flex table '" + path + "', table " + tablename); System.exit(-1); } } props.put("rowdef", rowdef.toString()); serverFileUtils.saveMap(propfile, props, "FlexWidthArray properties"); // open existing files String[] files = tabledir.list(); for (int i = 0; i < files.length; i++) { if ((files[i].startsWith("col.") && (files[i].endsWith(".list")))) { int colstart = Integer.parseInt(files[i].substring(4, 7)); int colend = (files[i].charAt(7) == '-') ? Integer.parseInt(files[i].substring(8, 11)) : colstart; kelondroColumn columns[] = new kelondroColumn[colend - colstart + 1]; for (int j = colstart; j <= colend; j++) columns[j-colstart] = rowdef.column(j); col[colstart] = new kelondroFixedWidthArray(new File(tabledir, files[i]), new kelondroRow(columns, (colstart == 0) ? rowdef.objectOrder : kelondroNaturalOrder.naturalOrder, 0), 16); for (int j = colstart; j <= colend; j++) check = check.substring(0, j) + "X" + check.substring(j + 1); } } // check if all columns are there int p, q; while ((p = check.indexOf('_')) >= 0) { q = p; if (p != 0) { while ((q <= check.length() - 1) && (check.charAt(q) == '_')) q++; q--; } // create new array file kelondroColumn[] columns = new kelondroColumn[q - p + 1]; for (int j = p; j <= q; j++) { columns[j - p] = rowdef.column(j); check = check.substring(0, j) + "X" + check.substring(j + 1); } col[p] = new kelondroFixedWidthArray(new File(tabledir, colfilename(p, q)), new kelondroRow(columns, (p == 0) ? rowdef.objectOrder : kelondroNaturalOrder.naturalOrder, 0), 16); } } public final String filename() { return this.filename; } public static int staticsize(File path, String tablename) { // check if table directory exists File tabledir = new File(path, tablename); if (tabledir.exists()) { if (!(tabledir.isDirectory())) return 0; } else { return 0; } // open existing files File file = new File(tabledir, "col.000.list"); return kelondroRecords.staticsize(file); } public static void delete(File path, String tablename) { File tabledir = new File(path, tablename); if ((tabledir.exists()) && (!(tabledir.isDirectory()))) { tabledir.delete(); return; } String[] files = tabledir.list(); for (int i = 0; i < files.length; i++) { new File(tabledir, files[i]).delete(); } tabledir.delete(); } public void reset() throws IOException { this.close(); delete(path, tablename); this.init(); } public synchronized void close() { if (col != null) { for (int i = 0; i < col.length; i++) { if (col[i] != null) { // a column can be null, this is normal col[i].close(); col[i] = null; } } } } protected static final String colfilename(int start, int end) { String f = Integer.toString(end); while (f.length() < 3) f = "0" + f; if (start == end) return "col." + f + ".list"; f = Integer.toString(start) + "-" + f; while (f.length() < 7) f = "0" + f; return "col." + f + ".list"; } public kelondroRow row() { return rowdef; } public int size() { return col[0].size(); } public synchronized void setMultiple(TreeMap /*of {Integer, kelondroRow.Entry}*/ entries) throws IOException { // a R/W head path-optimized option to write a set of entries Iterator i; Map.Entry entry; kelondroRow.Entry rowentry, e; int c = 0, index; // go across each file while (c < rowdef.columns()) { i = entries.entrySet().iterator(); while (i.hasNext()) { entry = (Map.Entry) i.next(); index = ((Integer) entry.getKey()).intValue(); rowentry = (kelondroRow.Entry) entry.getValue(); assert rowentry.objectsize() == this.rowdef.objectsize; e = col[c].row().newEntry(rowentry.bytes(), rowdef.colstart[c], false); col[c].set(index, e); } c = c + col[c].row().columns(); } } public synchronized void set(int index, kelondroRow.Entry rowentry) throws IOException { assert rowentry.objectsize() == this.rowdef.objectsize; int c = 0; kelondroRow.Entry e; byte[] reb = rowentry.bytes(); while (c < rowdef.columns()) { e = col[c].row().newEntry(reb, rowdef.colstart[c], false); col[c].set(index, e); c = c + col[c].row().columns(); } } public synchronized int add(kelondroRow.Entry rowentry) throws IOException { assert rowentry.objectsize() == this.rowdef.objectsize; int index = -1; byte[] reb = rowentry.bytes(); index = col[0].add(col[0].row().newEntry(reb, 0, false)); int c = col[0].row().columns(); while (c < rowdef.columns()) { col[c].set(index, col[c].row().newEntry(reb, rowdef.colstart[c], false)); c = c + col[c].row().columns(); } return index; } protected synchronized TreeMap addMultiple(List rows) throws IOException { // result is a Integer/byte[] relation // of newly added rows (index, key) TreeMap indexref = new TreeMap(); Iterator i; kelondroRow.Entry rowentry; // prepare storage for other columns TreeMap[] colm = new TreeMap[col.length]; for (int j = 0; j < col.length; j++) { if (col[j] == null) colm[j] = null; else colm[j] = new TreeMap(); } i = rows.iterator(); while (i.hasNext()) { rowentry = (kelondroRow.Entry) i.next(); assert rowentry.objectsize() == this.rowdef.objectsize; kelondroRow.Entry e; int index = -1; byte[] reb = rowentry.bytes(); e = col[0].row().newEntry(reb, 0, false); index = col[0].add(e); int c = col[0].row().columns(); while (c < rowdef.columns()) { e = col[c].row().newEntry(reb, rowdef.colstart[c], false); // remember write to column, but do not write directly colm[c].put(new Integer(index), e); // col[c].set(index,e); c = c + col[c].row().columns(); } indexref.put(new Integer(index), rowentry.getColBytes(0)); } // write the other columns for (int j = 1; j < col.length; j++) { if (col[j] != null) col[j].setMultiple(colm[j]); } // retrun references to entries with key return indexref; } public synchronized kelondroRow.Entry get(int index) throws IOException { kelondroRow.Entry e = col[0].getIfValid(index); if (e == null) return null; // probably a deleted entry kelondroRow.Entry p = rowdef.newEntry(); p.setCol(0, e.getColBytes(0)); int r = col[0].row().columns(); while (r < rowdef.columns()) { e = col[r].get(index); for (int i = 0; i < col[r].row().columns(); i++) { p.setCol(r + i, e.getColBytes(i)); } r = r + col[r].row().columns(); } return p; } public synchronized void remove(int index) throws IOException { int r = 0; // remove only from the first column col[0].remove(index); r = r + col[r].row().columns(); // the other columns will be blanked out only while (r < rowdef.columns()) { col[r].set(index, null); r = r + col[r].row().columns(); } } public void print() throws IOException { System.out.println("PRINTOUT of table, length=" + size()); kelondroRow.Entry row; for (int i = 0; i < (col[0].free() + col[0].size()); i++) { System.out.print("row " + i + ": "); row = get(i); System.out.println(row.toString()); //for (int j = 0; j < row().columns(); j++) System.out.print(((row.empty(j)) ? "NULL" : row.getColString(j, "UTF-8")) + ", "); //System.out.println(); } System.out.println("EndOfTable"); } public static void main(String[] args) { //File f = new File("d:\\\\mc\\privat\\fixtest.db"); File f = new File("/Users/admin/"); kelondroRow rowdef = new kelondroRow("byte[] a-12, byte[] b-4", kelondroNaturalOrder.naturalOrder, 0); String testname = "flextest"; try { System.out.println("erster Test"); kelondroFlexWidthArray.delete(f, testname); kelondroFlexWidthArray k = new kelondroFlexWidthArray(f, "flextest", rowdef, true); k.add(k.row().newEntry(new byte[][]{"a".getBytes(), "xxxx".getBytes()})); k.add(k.row().newEntry(new byte[][]{"b".getBytes(), "xxxx".getBytes()})); k.remove(0); k.add(k.row().newEntry(new byte[][]{"c".getBytes(), "xxxx".getBytes()})); k.add(k.row().newEntry(new byte[][]{"d".getBytes(), "xxxx".getBytes()})); k.add(k.row().newEntry(new byte[][]{"e".getBytes(), "xxxx".getBytes()})); k.add(k.row().newEntry(new byte[][]{"f".getBytes(), "xxxx".getBytes()})); k.remove(0); k.remove(1); k.print(); k.col[0].print(true); k.col[1].print(true); k.close(); System.out.println("zweiter Test"); kelondroFlexWidthArray.delete(f, testname); //k = kelondroFlexWidthArray.open(f, "flextest", rowdef); for (int i = 1; i <= 20; i = i * 2) { System.out.println("LOOP: " + i); k = new kelondroFlexWidthArray(f, "flextest", rowdef, true); for (int j = 0; j < i*2; j++) { k.add(k.row().newEntry(new byte[][]{(Integer.toString(i) + "-" + Integer.toString(j)).getBytes(), "xxxx".getBytes()})); } k.close(); k = new kelondroFlexWidthArray(f, "flextest", rowdef, true); for (int j = 0; j < i; j++) { k.remove(i*2 - j - 1); } k.close(); } k = new kelondroFlexWidthArray(f, "flextest", rowdef, true); k.print(); k.col[0].print(true); k.close(); } catch (IOException e) { e.printStackTrace(); } } }