001: // You can redistribute this software and/or modify it under the terms of
002: // the Ozone Library License version 1 published by ozone-db.org.
003: //
004: // The original code and portions created by SMB are
005: // Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
006: //
007: // $Id: DxDiskHashMap.java,v 1.3 2002/06/08 00:49:38 mediumnet Exp $
008:
009: package org.ozoneDB.DxLib;
010:
011: import java.io.*;
012:
013: /**
014: * @author <a href="http://www.softwarebuero.de/">SMB</a>
015: * @author <a href="http://www.medium.net/">Medium.net</a>
016: * @version $Revision: 1.3 $Date: 2002/06/08 00:49:38 $
017: */
018: public class DxDiskHashMap extends DxAbstractMap {
019:
020: final static long serialVersionUID = 2;
021:
022: public final static String ROOT_TABLE_NAME = ".rootTable";
023:
024: private String baseFileName;
025:
026: /** The directory where all tables reside. */
027: protected File tableDirectory;
028:
029: private long subTableNameCount = System.currentTimeMillis();
030: private DxDiskSubTable rootTable;
031: private int itemCount = 0;
032:
033: // cache related stuff
034: private int cacheBits = 10;
035: private int cacheMask;
036: private DxKeyData[] cache;
037:
038: // buffer related stuff
039: protected int maxBufferSize = 10;
040: protected DxSet buffer;
041:
042: public int bufferAccesses;
043: public int bufferHits;
044: public int cacheAccesses;
045: public int cacheHits;
046:
047: public int tableBitSize = 8;
048:
049: public DxDiskHashMap(String _baseFileName, int _maxBufferSize,
050: int _cacheBits, int _tableBitSize) {
051: baseFileName = _baseFileName;
052: maxBufferSize = _maxBufferSize;
053: tableBitSize = _tableBitSize;
054:
055: int index = baseFileName.lastIndexOf(File.separatorChar);
056:
057: if (index != -1) {
058: tableDirectory = new File(baseFileName.substring(0, index));
059: } else {
060: tableDirectory = new File(System.getProperty("user.dir",
061: "."));
062: }
063:
064: cacheBits = _cacheBits;
065: cacheMask = ((int) Math.pow(2, cacheBits) - 1);
066:
067: clear();
068: }
069:
070: public synchronized void clear() {
071: cache = new DxKeyData[(int) Math.pow(2, cacheBits)];
072:
073: rootTable = new DxDiskSubTable(this , 0, tableBitSize);
074: buffer = new DxHashSet();
075: buffer.add(rootTable);
076: }
077:
078: public DxDiskSubTable rootTable() {
079: return this .rootTable;
080: }
081:
082: public DxDiskHashNodeLeaf newNodeLeaf() {
083: return new DxDiskHashNodeLeaf(this );
084: }
085:
086: public DxDiskHashNodeBranch newNodeBranch() {
087: return new DxDiskHashNodeBranch(this );
088: }
089:
090: public DxKeyData newKeyData() {
091: return new DxKeyData();
092: }
093:
094: public boolean isDirtyTable(DxDiskSubTable table) {
095: // in general we don't know if and when the data objects stored in
096: // this table has been changed since last write, so we have to assume
097: // it is always dirty
098: return true;
099: }
100:
101: /**
102: * Reuse an existing table from disk. To do so a previously created table
103: * has to be correctly closed. Once a hash map has been re-used it has to
104: * closed before opening again.
105: */
106: public synchronized void re_use() throws Exception {
107: File f = new File(baseFileName + ROOT_TABLE_NAME);
108: ObjectInputStream in = new ObjectInputStream(
109: new BufferedInputStream(new FileInputStream(f)));
110: try {
111: itemCount = in.readInt();
112: rootTable.readExternal(in);
113: rootTable.grandParent = this ;
114: } finally {
115: in.close();
116: }
117: }
118:
119: /**
120: * Close this hash map. Write all changed tables to the disk. Store also
121: * all information that are needed to re-initialize this object from the
122: * disk data.
123: */
124: public synchronized void close() throws Exception {
125: writeAllTables();
126: setReusable(true);
127: }
128:
129: public void setReusable(boolean flag) throws IOException {
130: File f = new File(baseFileName + ROOT_TABLE_NAME);
131:
132: if (flag) {
133: ObjectOutputStream out = new ObjectOutputStream(
134: new BufferedOutputStream(new FileOutputStream(f)));
135: try {
136: out.writeInt(itemCount);
137: rootTable.writeExternal(out);
138: } finally {
139: out.close();
140: }
141: } else {
142: if (f.exists() && !f.delete()) {
143: throw new IOException("Unable to delete file.");
144: }
145: }
146: }
147:
148: public Object clone() {
149: throw new RuntimeException(getClass().getName()
150: + ".clone() is not implemented yet.");
151: }
152:
153: private final Object cachedElementForKey(Object key, int hashCode) {
154: cacheAccesses++;
155: if (cacheMask == 0) {
156: return null;
157: }
158:
159: int cacheIndex = hashCode & cacheMask;
160: DxKeyData entry = cache[cacheIndex];
161: if (entry != null
162: && (entry.key == key || entry.key.equals(key))) {
163: // System.out.print (".");
164: cacheHits++;
165: return entry.data;
166: } else {
167: return null;
168: }
169: }
170:
171: private final void addElementToCache(Object obj, Object key,
172: int hashCode) {
173: if (cacheMask == 0) {
174: return;
175: }
176:
177: synchronized (cache) {
178: int cacheIndex = hashCode & cacheMask;
179: DxKeyData entry = cache[cacheIndex];
180: if (entry != null) {
181: entry.set(key, obj);
182: } else {
183: entry = newKeyData();
184: entry.set(key, obj);
185: cache[cacheIndex] = entry;
186: }
187: }
188: }
189:
190: private final void removeElementFromCache(Object key) {
191: if (cacheMask == 0) {
192: return;
193: }
194:
195: synchronized (cache) {
196: int cacheIndex = key.hashCode() & cacheMask;
197: cache[cacheIndex] = null;
198: }
199: }
200:
201: public void printStatistics() {
202: System.out.println("DxDiskHastable statistics:");
203: System.out.println(" sub-table accesses: " + bufferAccesses
204: + " hits: " + bufferHits + " loads: "
205: + (bufferAccesses - bufferHits));
206: System.out.println(" cache accesses: " + cacheAccesses
207: + " hits: " + cacheHits);
208: }
209:
210: /**
211: * Eine sub-tabelle will nachladen. Wenn der buffer voll ist,
212: * muss eine andere verworfen werden. Die "aelteste" table ist
213: * immer ein blatt, da die zeiten mmer beim rekursiven aufstieg
214: * gesetzt werden.
215: */
216: protected synchronized void readRequest(DxDiskSubTable subTable)
217: throws Exception {
218: if (buffer.count() > maxBufferSize) {
219: //die blatt-sub-tabelle mit der aeltesten zugriffszeit
220: //suchen
221: long time = Long.MAX_VALUE;
222: DxIterator it = buffer.iterator();
223: DxDiskSubTable table;
224: DxDiskSubTable bestMatch = null;
225: while ((table = (DxDiskSubTable) it.next()) != null) {
226:
227: // check access time but do not deactivate rootTable
228: if (table.accessTime < time && table != rootTable) {
229: time = table.accessTime;
230: bestMatch = table;
231: }
232: }
233: // System.out.println (" - " + time);
234:
235: //test ob noch sub-tabels da sind
236: // for (int i=0; i<DxDiskSubTable.SIZE; i++) {
237: // DxDiskHashNode node = bestMatch.table[i];
238: // if (node != null) {
239: // if (node.element == null && node.subTable.table == null) {
240: // System.out.println ("Node is not a leaf!");
241: // break;
242: // }
243: // }
244: // }
245:
246: //schreiben, leer-machen und aus buffer loeschen
247: if (isDirtyTable(bestMatch)) {
248: bestMatch.writeTable();
249: }
250: deleteRequest(bestMatch);
251: }
252: // subTable.touch();
253: buffer.add(subTable);
254: }
255:
256: /**
257: * The specified sub-table was deleted from the tree. So we have
258: * to delete it from the table buffer too.
259: */
260: public synchronized void deleteRequest(DxDiskSubTable subTable) {
261: subTable.empty();
262: buffer.remove(subTable);
263: }
264:
265: /**
266: This method is synchronized because sub table filenames have to be unique.
267: */
268: public synchronized File newSubTableFile() {
269: return new File(baseFileName + "."
270: + String.valueOf(subTableNameCount++));
271: }
272:
273: /**
274: Computes a File object which represents the DxDiskSubTable file denoted by the given filename.
275: <div>
276: There are two formats for the given filename:
277: <ul>
278: <li>
279: The long format is a pathname relative to the current working directory of the
280: Java application which wrote the table referring the DxDiskSubTable in question.
281: It may also be an absolute pathname. This format is error prone as it does not
282: allow changing the working directory of Java applications or changing the location
283: of the table files within the filesystem. That's why the short format is used now.
284: </li>
285: <li>
286: The short format is only the last pathname component of the pathname to the referred DxDiskSubTable file.
287: The other pathname components (e.g. the directory where the DxDiskSubTable file resides) are determined
288: by the directory where the root table resides. This is possible because als DxDiskSubTable files reside
289: in the same directory as the root table file does.
290: </li>
291: </ul>
292: </div>
293: <div>
294: For compatibility with old tables, the long format is broken up into pathname components
295: and only the last pathname component is used then as a directory entry of the directory of the root table file.
296: </div>
297: */
298: public File getFileForFilename(String filename) {
299: int index = filename.lastIndexOf(File.separatorChar);
300:
301: if (index != -1) {
302: filename = filename.substring(index + 1);
303: }
304: return new File(tableDirectory, filename);
305: }
306:
307: /**
308: * Delete all the files used by this hashtable.
309: */
310: public void cleanFiles() {
311: String baseName = new File(baseFileName).getName();
312: File path = new File(new File(baseFileName).getParent());
313: String[] fileList = path.list();
314:
315: for (int i = 0; i < fileList.length; i++) {
316: if (fileList[i].startsWith(baseName)) {
317: new File(path, fileList[i]).delete();
318: }
319: }
320: }
321:
322: public synchronized void writeAllTables() throws Exception {
323: DxIterator it = buffer.iterator();
324: DxDiskSubTable table = null;
325: while ((table = (DxDiskSubTable) it.next()) != null) {
326: table.writeTable();
327: }
328: }
329:
330: public synchronized boolean addForKey(Object obj, Object key) {
331: try {
332: if (rootTable.addForKey(obj, key)) {
333: itemCount++;
334: addElementToCache(obj, key, key.hashCode());
335: return true;
336: } else {
337: return false;
338: }
339: } catch (Exception e) {
340: e.printStackTrace();
341: throw new RuntimeException(e.toString());
342: }
343: }
344:
345: /**
346: * Gives the element for the specified key.<p>
347: *
348: *
349: * Note: This method is synchronized because the cache of subtables may
350: * change.
351: */
352: public synchronized Object elementForKey(Object key) {
353: try {
354: int hashCode = key.hashCode();
355: Object cacheEntry = cachedElementForKey(key, hashCode);
356: if (cacheEntry != null) {
357: return cacheEntry;
358: } else {
359: Object answer = rootTable.elementForKey(key, hashCode);
360: addElementToCache(answer, key, hashCode);
361: return answer;
362: }
363: } catch (Exception e) {
364: e.printStackTrace();
365: throw new RuntimeException(e.toString());
366: }
367: }
368:
369: protected synchronized void elementDone(DxDiskHashCompatible obj) {
370: rootTable.elementDone(obj);
371: }
372:
373: public Object keyForElement(Object obj) {
374: throw new RuntimeException("keyForElement() not implemented.");
375: }
376:
377: public synchronized boolean remove(Object obj) {
378: throw new RuntimeException("remove() not implemented.");
379: }
380:
381: public synchronized Object removeForKey(Object key) {
382: try {
383: Object obj = rootTable.removeForKey(key);
384: if (obj != null) {
385: itemCount--;
386: removeElementFromCache(key);
387: }
388: return obj;
389: } catch (Exception e) {
390: e.printStackTrace();
391: throw new RuntimeException(e.toString());
392: }
393: }
394:
395: public DxIterator iterator() {
396: return new DxDiskHashIterator(this );
397: }
398:
399: public int count() {
400: return itemCount;
401: }
402:
403: public boolean isEmpty() {
404: return itemCount == 0;
405: }
406:
407: public boolean containsKey(Object key) {
408: return elementForKey(key) != null;
409: }
410: }
|