/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers 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, see http://www.gnu.org */ package net.wotonomy.datastore; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import net.wotonomy.foundation.internal.Introspector; abstract public class FileSoup implements DataSoup { public static final String INDEX_SUFFIX = ".idx"; public static final String MAP_SUFFIX = ".map"; private static final String ID_ID = "id"; private static final String IDENTITY_PROPERTY = "__SELF__"; protected DataKey nextUniqueIdentifier; protected File homeDirectory; protected Map indices; public FileSoup(String aPath) { homeDirectory = new File(aPath); // if specified directory does not exist if (!homeDirectory.exists()) { homeDirectory.mkdirs(); } // if existing path is a file, exit with error if (homeDirectory.isDirectory()) { new RuntimeException("DataStore: Specified directory is a file."); } // read indices DataIndex index; indices = new HashMap(); String[] files = getHomeDirectory().list(); for (int i = 0; i < files.length; i++) { if (files[i].endsWith(INDEX_SUFFIX)) { index = (DataIndex) readFile(files[i]); if (index != null) { indices.put(index.getName(), index); } } } // read unique identifier nextUniqueIdentifier = (DataKey) readFile(ID_ID); if (nextUniqueIdentifier == null) nextUniqueIdentifier = new DataKey("1"); } public File getHomeDirectory() { return homeDirectory; } // index management public void addIndex(String aName, String aProperty) { DataIndex index = new DefaultDataIndex(aName, aProperty); indices.put(index.getName(), index); buildIndex(index); writeIndices(); } public void removeIndex(String aName) { indices.remove(aName); writeIndices(); } public Collection getAllIndices() { return indices.values(); } // public void addIndex( String aName, Index anIndex ) {} // public void removeIndex( String aName ) {} // public void addTaggedObject( String aKey, Serializable anObject ) // public Object getTaggedObject( String aKey ); // public void removeTaggedObject( String aKey ); // public void setMetaData( // String aMetaProperty, Serializable aValue, Serializable anObject ); protected void buildIndex(DataIndex anIndex) { //System.out.print( "FileSoup.buildIndex: " + anIndex.getName() + " : " ); long millis = System.currentTimeMillis(); anIndex.clear(); int count = 0; DataKey key; Object o; Object value; String property = anIndex.getProperty(); String[] files = getHomeDirectory().list(); for (int i = 0; i < files.length; i++) { if ((!files[i].equals(ID_ID.toString()) && (!files[i].endsWith(INDEX_SUFFIX)))) { key = new DataKey(files[i]); o = getObjectByKey(key); value = getValueFromObject(o, property); anIndex.addObject(key, value); count++; } } //System.out.print( count + " objects: " ); //System.out.println( System.currentTimeMillis() - millis + " milliseconds" ); } protected void writeIndices() { DataIndex index; Iterator it = getAllIndices().iterator(); while (it.hasNext()) { index = (DataIndex) it.next(); writeFile(index.getName() + INDEX_SUFFIX, index); } } // object management // this implementation currently uses up a valid key increment public DataKey registerTemporaryObject(Object anObject) { DataKey id = getNextKey(); if (anObject instanceof UniquelyIdentifiable) { ((UniquelyIdentifiable) anObject).setUniqueIdentifier(id); } return id; } /** * Adds the specified object to the soup and returns the key for the new object * by which it may be subsequently retrieved. Null indicates an error, probably * due to serialization. */ public DataKey addObject(Object anObject) { DataKey id = getNextKey(); if (anObject instanceof UniquelyIdentifiable) { // set id if necessary ((UniquelyIdentifiable) anObject).setUniqueIdentifier(id); } writeFile(id.toString(), anObject); // update indices DataIndex index; Iterator it = indices.values().iterator(); while (it.hasNext()) { index = (DataIndex) it.next(); index.addObject(id, getValueFromObject(anObject, index.getProperty())); } writeIndices(); return id; } /** * Removes the specified object from the soup and returns the removed object as * read from the soup (which is the original copy of the object). Null indicates * object not found. */ public Object removeObject(DataKey aKey) { Object existing = getObjectByKey(aKey); if (existing != null) { if (!deleteFile(aKey.toString())) { existing = null; // return error } else { // update indices DataIndex index; Iterator it = indices.values().iterator(); while (it.hasNext()) { index = (DataIndex) it.next(); index.removeObject(aKey, getValueFromObject(existing, index.getProperty())); } writeIndices(); } } return existing; } /** * Updates the specified object and returns the object as updated. Null * indicates an error writing the object. */ public Object updateObject(DataKey aKey, Object updatedObject) { Object existing = getObjectByKey(aKey); if (existing == null) { System.err.println("FileSoup.updateObject: " + "existing object could not be found with id: " + aKey); return null; } Object result = null; if (updatedObject instanceof UniquelyIdentifiable) { // update key if changed ((UniquelyIdentifiable) updatedObject).setUniqueIdentifier(aKey); } if (writeFile(aKey.toString(), updatedObject)) { result = updatedObject; // update indices DataIndex index; Iterator it = indices.values().iterator(); while (it.hasNext()) { index = (DataIndex) it.next(); index.updateObject(aKey, getValueFromObject(existing, index.getProperty()), getValueFromObject(updatedObject, index.getProperty())); } writeIndices(); } //System.out.println( "FileSoup.updateObject: " + updatedObject + " -> " + result ); return getObjectByKey(aKey); } protected DataKey getNextKey() { DataKey id = (DataKey) nextUniqueIdentifier.clone(); // while ( id isn't yet in use by the soup ) increment(); nextUniqueIdentifier.increment(); writeFile(ID_ID.toString(), nextUniqueIdentifier); return id; } protected DataKey getNextTempKey() { return getNextKey(); } /** * Gets object from data store whose identifier is equal to the specified * object. */ public Object getObjectByKey(DataKey aKey) { return readFile(aKey.toString()); } // queries /** * Returns an empty data view, suitable for creating new entries in the soup. * * @return A DataView containing no entries. */ public DataView createView() { return new DefaultDataView(this, new LinkedList()); } /** * Queries by the specified pre-generated index, if it exists. Otherwise, falls * through to queryByProperty. */ public DataView queryByIndex(String anIndexName, Object beginKey, Object endKey) { DataIndex index = (DataIndex) indices.get(anIndexName); if (index == null) { return queryByProperty(anIndexName, beginKey, endKey); } return queryByKeys(index.query(beginKey, endKey)); } /** * Generates an index based on the specified property and then executes the * query. */ public DataView queryByProperty(String aPropertyName, Object beginKey, Object endKey) { if (aPropertyName == null) aPropertyName = IDENTITY_PROPERTY; DataIndex index = new DefaultDataIndex("temporary", aPropertyName); buildIndex(index); return queryByKeys(index.query(beginKey, endKey)); } /** * Generates an index based on the specified property and then executes the * query. */ public DataView queryObjects(Object beginKey, Object endKey) { return queryByProperty(IDENTITY_PROPERTY, beginKey, endKey); } /** * Returns a view containing the objects for the specified keys. */ public DataView queryByKeys(Collection aKeyList) { return new DefaultDataView(this, aKeyList); } /** * As queryByIndex, but with objects returned in reverse order. * * @param anIndexName The index to be queried. * @param beginValue The beginning value, or null for all values up to an * including the end key. * @param endValue The ending value, or null for all values since and * including the begin key. * @return A DataView containing the query results, or null for invalid query * parameters. */ public DataView reverseQueryByIndex(String anIndexName, Object beginKey, Object endKey) { DataIndex index = (DataIndex) indices.get(anIndexName); if (index == null) { return reverseQueryByProperty(anIndexName, beginKey, endKey); } List items = index.query(endKey, beginKey); Collections.reverse(items); return queryByKeys(items); } /** * As queryByProperty, but with objects returned in reverse order. * * @param aPropertyName The property to be queried. If null, will query the * objects directly with queryObjects(). * @param beginValue The beginning value, or null for all values up to an * including the end key. * @param endValue The ending value, or null for all values since and * including the begin key. * @return A DataView containing the query results, or null for invalid query * parameters. */ public DataView reverseQueryByProperty(String aPropertyName, Object beginKey, Object endKey) { if (aPropertyName == null) aPropertyName = IDENTITY_PROPERTY; DataIndex index = new DefaultDataIndex("temporary", aPropertyName); buildIndex(index); List items = index.query(endKey, beginKey); Collections.reverse(items); return queryByKeys(items); } /** * As queryObjects, but with objects returned in reverse order. * * @param beginValue The beginning value, or null for all values up to an * including the end key. * @param endValue The ending value, or null for all values since and * including the begin key. * @return A DataView containing the query results, or null for invalid query * parameters. */ public DataView reverseQueryObjects(Object beginKey, Object endKey) { return queryByProperty(IDENTITY_PROPERTY, beginKey, endKey); } public Object getValueFromObject(Object anObject, String aProperty) { if (IDENTITY_PROPERTY.equals(aProperty)) return anObject; return Introspector.getValueForObject(anObject, aProperty); } // file access methods abstract protected boolean writeFile(String name, Object anObject); abstract protected Object readFile(String name); abstract protected boolean deleteFile(String name); } /* * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to * tests project Fix up code to work with proper imports Fix maven dependencies. * * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.4 2003/08/14 19:29:38 chochos minor cleanup (imports, static * method calls, etc) * * Revision 1.3 2001/03/05 22:12:11 mpowers Created the control package for a * datastore-specific implementation of EOObjectStore. * * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout * the api. This breaks compatibility. insertObject now returns the permanent * key for the newly created object. The old way returned a copy of the object * which was an additional read that was often ignored. Now you can read it only * if you need it. Furthermore, there was not other way of getting the permanent * key. * * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers Contributing wotonomy. * * Revision 1.3 2000/12/20 16:25:36 michael Added log to all files. * * */