diff options
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net')
77 files changed, 20655 insertions, 0 deletions
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessArrayFaultHandler.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessArrayFaultHandler.java new file mode 100644 index 0000000..1aafa13 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessArrayFaultHandler.java @@ -0,0 +1,71 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOKeyGlobalID; + +/** +* A fault handler for to-many relationships. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/public class EOAccessArrayFaultHandler extends EOAccessGenericFaultHandler { + + protected EOKeyGlobalID _sourceID; + protected String _relation; + + public EOAccessArrayFaultHandler(EOKeyGlobalID sourceID, String relationName, EODatabaseContext dbc, EOEditingContext ec) { + super(); + _sourceID = sourceID; + _relation = relationName; + setContext(dbc, ec); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOFaultHandler#completeInitializationOfObject(java.lang.Object) + */ + public void completeInitializationOfObject(Object obj) { + // TODO Auto-generated method stub + + } + + public String relationshipName() { + return _relation; + } + + public EOKeyGlobalID sourceGlobalID() { + return _sourceID; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/19 19:53:20 chochos + * EOAccess fault handlers (still incomplete) + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessFaultHandler.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessFaultHandler.java new file mode 100644 index 0000000..d4cabe9 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessFaultHandler.java @@ -0,0 +1,66 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOKeyGlobalID; + +/** +* A fault handler for single objects. Usually the destinations of a +* to-one relationship. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/public class EOAccessFaultHandler extends EOAccessGenericFaultHandler { + + protected EOKeyGlobalID _gid; + + public EOAccessFaultHandler(EOKeyGlobalID gid, EODatabaseContext dbc, EOEditingContext ec) { + super(); + _gid = gid; + setContext(dbc, ec); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOFaultHandler#completeInitializationOfObject(java.lang.Object) + */ + public void completeInitializationOfObject(Object obj) { + // TODO Auto-generated method stub + + } + + public EOKeyGlobalID globalID() { + return _gid; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/19 19:53:20 chochos + * EOAccess fault handlers (still incomplete) + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessGenericFaultHandler.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessGenericFaultHandler.java new file mode 100644 index 0000000..6876151 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessGenericFaultHandler.java @@ -0,0 +1,75 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOFaultHandler; + +/** +* A generic fault handler for EOAccess. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EOAccessGenericFaultHandler extends EOFaultHandler { + + protected EODatabaseContext _dbContext; + protected EOEditingContext _ec; + + public EOAccessGenericFaultHandler() { + super(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOFaultHandler#faultWillFire(java.lang.Object) + */ + public void faultWillFire(Object obj) { + // TODO Auto-generated method stub + + } + + protected void setContext(EODatabaseContext dbc, EOEditingContext ec) { + _dbContext = dbc; + _ec = ec; + } + + public EODatabaseContext databaseContext() { + return _dbContext; + } + + public EOEditingContext editingContext() { + return _ec; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/19 19:53:20 chochos + * EOAccess fault handlers (still incomplete) + * + */ +
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessLock.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessLock.java new file mode 100644 index 0000000..28199c5 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAccessLock.java @@ -0,0 +1,60 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSRecursiveLock; + +/** + * This class offers a very simple interface to a global locking + * mechanism to be used by the whole access layer. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ +public class EOAccessLock { + + private static NSRecursiveLock _lock = new NSRecursiveLock(); + + private EOAccessLock() { + super(); + } + + public static void lock() { + _lock.lock(); + } + + public static void unlock() { + _lock.unlock(); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/29 20:43:25 chochos + * a global access layer lock + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptor.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptor.java new file mode 100644 index 0000000..28295d1 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptor.java @@ -0,0 +1,269 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSData; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSKeyValueCoding; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSTimestamp; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public abstract class EOAdaptor { + + protected String _name; + protected NSDictionary _connectionDictionary = NSDictionary.EmptyDictionary; + protected NSMutableArray _contexts = new NSMutableArray(); + protected Class _expressionClass; + private static NSMutableDictionary _expressionClassesByName = new NSMutableDictionary(); + + public EOAdaptor(String name) { + super(); + _name = name; + } + + /** + * Creates an adaptor with model's adaptorName and sets its connection + * dictionary to the model's connection dictionary. + * @param model The model to take adaptorName and connectionDictionary from. + * @return The adaptor specified in model. + */ + public static EOAdaptor adaptorWithModel(EOModel model) { + if (model == null) + throw new IllegalArgumentException("Model must not be null."); + if (model.adaptorName() == null || model.adaptorName().length() == 0) + throw new IllegalArgumentException("Cannot create an adaptor with an empty name."); + EOAdaptor adaptor = adaptorWithName(model.adaptorName()); + if (adaptor == null) + throw new IllegalArgumentException("Cannot create adaptor with name " + model.adaptorName()); + adaptor.setConnectionDictionary(model.connectionDictionary()); + return adaptor; + } + + /** + * Instantiates an adaptor of a concrete EOAdaptor subclass, based on name. + * If name is a fully qualified class name, then it returns an instance of that class + * by invoking the constructor with a string argument. Otherwise, it + * tries to find a class called (name)Adaptor in a package called + * net.wotonomy.(lowercase name)adaptor; if it can't find one, an exception is raised. + * @param name The name of the adaptor, or a fully qualified class name. + * @return + */ + public static EOAdaptor adaptorWithName(String name) { + Class adaptorClass = null; + String cname = null; + if (name.endsWith("Adaptor") && name.indexOf('.') > 0) { + cname = name; + int lastdot = name.lastIndexOf('.'); + //take off the package and the 'Adaptor' suffix + name = cname.substring(lastdot, cname.length() - 7); + } else { + //construct the fully qualified class name + cname = "net.wotonomy." + name.toLowerCase() + "adaptor." + name + "Adaptor"; + } + try { + adaptorClass = Class.forName(cname); + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException("Cannot find class named " + name); + } + EOAdaptor adaptor = null; + java.lang.reflect.Constructor callme = null; + try { + callme = adaptorClass.getConstructor(new Class[]{ String.class }); + adaptor = (EOAdaptor)callme.newInstance((Object[])new String[]{ name }); + } catch (ClassCastException ex) { + throw new IllegalArgumentException("Class " + adaptorClass.getName() + " must inherit from net.wotonomy.access.EOAdaptor"); + }catch (Exception ex) { + throw new IllegalArgumentException("Cannot find or invoke constructor with name argument in class " + adaptorClass.getName()); + } + return adaptor; + } + + public static void setExpressionClassName(String expClassName, String adaptorClassName) { + _expressionClassesByName.setObjectForKey(expClassName, adaptorClassName); + } + public static String expressionClassName(String adaptorClassName) { + return (String)_expressionClassesByName.objectForKey(adaptorClassName); + } + + public void assignExternalInfoForAttribute(EOAttribute attribute) { + if (!attribute.isDerived()) { + attribute.setColumnName(attribute.name().toUpperCase()); + } + assignExternalTypeForAttribute(attribute); + } + + public void assignExternalTypeForAttribute(EOAttribute attribute) { + } + + public void assignExternalInfoForEntity(EOEntity entity) { + entity.setExternalName(entity.name().toUpperCase()); + } + + public void assignExternalInfoForEntireModel(EOModel model) { + NSArray ents = model.entities(); + for (int i = 0; i < ents.count(); i++) { + EOEntity e = (EOEntity)ents.objectAtIndex(i); + //TODO: check that entity is not a prototypes entity + NSArray atts = e.attributes(); + for (int j = 0; j < atts.count(); j++) { + EOAttribute a = (EOAttribute)atts.objectAtIndex(i); + assignExternalInfoForAttribute(a); + } + assignExternalInfoForEntity(e); + } + } + + public boolean canServiceModel(EOModel model) { + NSDictionary mcd = model.connectionDictionary(); + if (mcd == null && _connectionDictionary == null) + return true; + if (mcd == null || _connectionDictionary == null) + return false; + return mcd.equals(_connectionDictionary); + } + + public void setConnectionDictionary(NSDictionary connection) { + _connectionDictionary = connection; + } + public NSDictionary connectionDictionary() { + return _connectionDictionary; + } + + public NSArray contexts() { + return new NSArray(_contexts); + } + + public abstract void assertConnectionDictionaryIsValid(); + + public abstract EOAdaptorContext createAdaptorContext(); + + public abstract Class defaultExpressionClass(); + + public abstract EOSQLExpressionFactory expressionFactory(); + + public abstract boolean isValidQualifierType(String typeName, EOModel model); + + public Class expressionClass() { + if (_expressionClass != null) + return _expressionClass; + String cname = expressionClassName(name()); + if (cname != null) { + try { + _expressionClass = Class.forName(cname); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException("Cannot find expression class named " + cname); + } + } + return defaultExpressionClass(); + } + + public NSArray externalTypesWithModel(EOModel model) { + return NSArray.EmptyArray; + } + + public NSData fetchedValueForDataValue(NSData value, EOAttribute attr) { + return value; + } + + public NSTimestamp fetchedValueForDateValue(NSTimestamp value, EOAttribute attr) { + return value; + } + + public Number fetchedValueForNumberValue(Number value, EOAttribute attr) { + return value; + } + + public String fetchedValueForStringValue(String value, EOAttribute attr) { + return value; + } + + public Object fetchedValueForValue(Object value, EOAttribute attr) { + if (value == NSKeyValueCoding.NullValue) + return value; + if (value instanceof String) + return fetchedValueForStringValue((String)value, attr); + if (value instanceof NSData) + return fetchedValueForDataValue((NSData)value, attr); + if (value instanceof Number) + return fetchedValueForNumberValue((Number)value, attr); + if (value instanceof NSTimestamp) + return fetchedValueForDateValue((NSTimestamp)value, attr); + return value; + } + + public void handleDroppedConnection() { + for (int i = 0; i < _contexts.count(); i++) { + EOAdaptorContext c = (EOAdaptorContext)_contexts.objectAtIndex(i); + c.transactionDidRollback(); + c.handleDroppedConnection(); + } + _contexts.removeAllObjects(); + } + + public boolean hasOpenChannels() { + for (int i = 0; i < _contexts.count(); i++) { + EOAdaptorContext c = (EOAdaptorContext)_contexts.objectAtIndex(i); + if (c.hasOpenChannels()) + return true; + } + return false; + } + + public String internalTypeForExternalType(String extType, EOModel model) { + return null; + } + + public boolean isDroppedConnectionException(Exception ex) { + return false; + } + + public String name() { + return _name; + } + + public NSArray prototypeAttributes() { + return NSArray.EmptyArray; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2005/12/08 06:52:32 cgruber + * Move tests, improve build.xml, and make certain casts explicit so that Java 1.5 doesn't complain about varargs. + * + * Revision 1.1 2003/08/13 00:37:45 chochos + * an almost complete implementation of the abstract adaptor-layer classes + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorChannel.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorChannel.java new file mode 100644 index 0000000..09b8a6d --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorChannel.java @@ -0,0 +1,244 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EOAdaptorChannel { + + protected EOAdaptorContext _context; + + public EOAdaptorChannel(EOAdaptorContext context) { + super(); + _context = context; + } + + public EOAdaptorContext adaptorContext() { + return _context; + } + + public void addStoredProceduresNamed(NSArray names, EOModel model) { + } + + public abstract NSArray attributesToFetch(); + + public abstract void cancelFetch(); + + public abstract void closeChannel(); + + public abstract NSArray describeResults(); + + public abstract int deleteRowsDescribedByQualifier(EOQualifier q, EOEntity entity); + + public abstract void evaluateExpression(EOSQLExpression sql); + + public abstract void executeStoredProcedure(EOStoredProcedure proc, NSDictionary values); + + public abstract NSMutableDictionary fetchRow(); + + public abstract void insertRow(NSDictionary row, EOEntity entity); + + public abstract boolean isFetchInProgress(); + + public abstract boolean isOpen(); + + public abstract void openChannel(); + + public abstract NSDictionary returnValuesForLastStoredProcedureInvocation(); + + public abstract void selectAttributes(NSArray atts, EOFetchSpecification fspec, boolean lock, EOEntity entity); + + public abstract void setAttributesToFetch(NSArray atts); + + public abstract int updateValuesInRowsDescribedByQualifier(NSDictionary row, EOQualifier q, EOEntity entity); + + public void deleteRowDescribedByQualifier(EOQualifier q, EOEntity entity) { + adaptorContext().beginTransaction(); + int count = deleteRowsDescribedByQualifier(q, entity); + if (count != 1) { + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("Qualifier deleted " + count + " rows instead of exactly one."); + } + adaptorContext().commitTransaction(); + } + + public EOModel describeModelWithTableNames(NSArray names) { + return null; + } + + public NSArray describeStoredProcedureNames() { + return NSArray.EmptyArray; + } + + public NSArray describeTableNames() { + return NSArray.EmptyArray; + } + + public NSMutableDictionary dictionaryWithObjectsForAttributes(Object[] values, NSArray attributes) { + Object[] keys = new Object[attributes.count()]; + for (int i = 0; i < attributes.count(); i++) + keys[i] = ((EOAttribute)attributes.objectAtIndex(i)).name(); + return new NSMutableDictionary(values, keys); + } + + public void lockRowComparingAttributes(NSArray atts, EOEntity entity, EOQualifier q, NSDictionary snapshot) { + EOFetchSpecification fspec = new EOFetchSpecification(entity.name(), q, null); + adaptorContext().beginTransaction(); + selectAttributes(atts, fspec, true, entity); + if (isFetchInProgress()) { + NSDictionary row = fetchRow(); + if (row == null) { + cancelFetch(); + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("Cannot obtain row to lock. Probably modified from the outside."); + } + if (isFetchInProgress()) { + if (fetchRow() != null) { + cancelFetch(); + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("Qualifier returns more than one row."); + } + } + java.util.Enumeration enumeration = snapshot.keyEnumerator(); + while (enumeration.hasMoreElements()) { + Object key = enumeration.nextElement(); + Object svalue = snapshot.objectForKey(key); + Object rvalue = row.objectForKey(key); + if (rvalue == null) { + cancelFetch(); + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("Value for key " + key + " not found in locked row."); + } + if (!rvalue.equals(svalue)) { + cancelFetch(); + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("Value for key " + key + " differes from snapshot."); + } + } + adaptorContext().commitTransaction(); + return; + } + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("A fetch was never generated."); + } + + public void performAdaptorOperation(EOAdaptorOperation operation) { + int opcode = operation.adaptorOperator(); + switch (opcode) { + case EODatabaseOperation.AdaptorLockOperator: + if (operation.entity() == null) + throw new EOGeneralAdaptorException("A lock operation must have an entity assigned to it.", + new NSDictionary(operation, "operation")); + if (operation.qualifier() == null) + throw new EOGeneralAdaptorException("A lock operation must have a qualifier assigned to it.", + new NSDictionary(operation, "operation")); + if (operation.qualifier() == null) + throw new EOGeneralAdaptorException("A lock operation must have changedValues assigned to it.", + new NSDictionary(operation, "operation")); + lockRowComparingAttributes(operation.attributes(), operation.entity(), operation.qualifier(), operation.changedValues()); + break; + case EODatabaseOperation.AdaptorInsertOperator: + if (operation.entity() == null) + throw new EOGeneralAdaptorException("An insert operation must have an entity assigned to it.", + new NSDictionary(operation, "operation")); + if (operation.changedValues() == null) + throw new EOGeneralAdaptorException("An insert operation must have changedValues assigned to it.", + new NSDictionary(operation, "operation")); + insertRow(operation.changedValues(), operation.entity()); + break; + case EODatabaseOperation.AdaptorUpdateOperator: + if (operation.entity() == null) + throw new EOGeneralAdaptorException("An update operation must have an entity assigned to it.", + new NSDictionary(operation, "operation")); + if (operation.changedValues() == null) + throw new EOGeneralAdaptorException("An update operation must have changedValues assigned to it.", + new NSDictionary(operation, "operation")); + updateValuesInRowsDescribedByQualifier(operation.changedValues(), operation.qualifier(), operation.entity()); + break; + case EODatabaseOperation.AdaptorDeleteOperator: + if (operation.entity() == null) + throw new EOGeneralAdaptorException("A delete operation must have an entity assigned to it.", + new NSDictionary(operation, "operation")); + deleteRowsDescribedByQualifier(operation.qualifier(), operation.entity()); + break; + case EODatabaseOperation.AdaptorStoredProcedureOperator: + if (operation.storedProcedure() == null) + throw new EOGeneralAdaptorException("A stored procedure operation must have a stored procedure assigned to it.", + new NSDictionary(operation, "operation")); + executeStoredProcedure(operation.storedProcedure(), operation.changedValues()); + break; + default: + throw new EOGeneralAdaptorException("I don't know how to perform an operation with code " + opcode, + new NSDictionary(operation, "operation")); + } + } + + public void performAdaptorOperations(NSArray ops) { + for (int i = 0; i < ops.count(); i++) { + EOAdaptorOperation adop = (EOAdaptorOperation)ops.objectAtIndex(i); + performAdaptorOperation(adop); + } + } + + public NSArray primaryKeysForNewRowsWithEntity(int count, EOEntity entity) { + NSDictionary[] keys = new NSDictionary[count]; + for (int i = 0; i < count; i++) + keys[i] = NSDictionary.EmptyDictionary; + return new NSArray(keys); + } + + public void updateValuesInRowDescribedByQualifier(NSDictionary row, EOQualifier q, EOEntity entity) { + adaptorContext().beginTransaction(); + int count = updateValuesInRowsDescribedByQualifier(row, q, entity); + if (count != 1) { + adaptorContext().rollbackTransaction(); + throw new EOGeneralAdaptorException("The qualifier should describe exactly one row (updated " + count + " rows)"); + } + adaptorContext().commitTransaction(); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.1 2003/08/13 00:37:45 chochos + * an almost complete implementation of the abstract adaptor-layer classes + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorContext.java new file mode 100644 index 0000000..aff0ddd --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorContext.java @@ -0,0 +1,118 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSNotificationCenter; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public abstract class EOAdaptorContext { + + public static final String AdaptorContextBeginTransactionNotification = "AdaptorContextBeginTransaction"; + public static final String AdaptorContextCommitTransactionNotification = "AdaptorContextCommitTransaction"; + public static final String AdaptorContextRollbackTransactionNotification = "AdaptorContextRollbackTransaction"; + + protected EOAdaptor _adaptor; + protected NSMutableArray _channels = new NSMutableArray(); + protected boolean _hasOpenTransaction; + + public EOAdaptorContext(EOAdaptor adaptor) { + super(); + _adaptor = adaptor; + } + + public EOAdaptor adaptor() { + return _adaptor; + } + + public abstract void beginTransaction(); + + public abstract void commitTransaction(); + + public abstract void rollbackTransaction(); + + public abstract EOAdaptorChannel createAdaptorChannel(); + + public abstract void handleDroppedConnection(); + + public NSArray channels() { + return new NSArray(_channels); + } + + public boolean hasBusyChannels() { + for (int i = 0; i < _channels.count(); i++) { + EOAdaptorChannel chan = (EOAdaptorChannel)_channels.objectAtIndex(i); + if (chan.isFetchInProgress()) + return true; + } + return false; + } + + public boolean hasOpenChannels() { + for (int i = 0; i < _channels.count(); i++) { + EOAdaptorChannel chan = (EOAdaptorChannel)_channels.objectAtIndex(i); + if (chan.isOpen()) + return true; + } + return false; + } + + public boolean hasOpenTransaction() { + return _hasOpenTransaction; + } + + public void transactionDidBegin() { + _hasOpenTransaction = true; + NSNotificationCenter.defaultCenter().postNotification( + AdaptorContextBeginTransactionNotification, this); + } + + public void transactionDidCommit() { + _hasOpenTransaction = false; + NSNotificationCenter.defaultCenter().postNotification( + AdaptorContextCommitTransactionNotification, this); + } + + public void transactionDidRollback() { + _hasOpenTransaction = false; + NSNotificationCenter.defaultCenter().postNotification( + AdaptorContextRollbackTransactionNotification, this); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/13 00:37:45 chochos + * an almost complete implementation of the abstract adaptor-layer classes + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorOperation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorOperation.java new file mode 100644 index 0000000..818985d --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAdaptorOperation.java @@ -0,0 +1,120 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOQualifier; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; + +/** +* Represents a single primitive operation in a database server. +* Can be insert, update, delete, lock a row, or execute a +* stored procedure. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOAdaptorOperation { + + protected EOEntity _entity; + protected NSArray _attributes; + protected NSDictionary _changedValues; + protected Throwable _exception; + protected EOQualifier _qualifier; + protected EOStoredProcedure _proc; + protected int _adaptorOp; + + public EOAdaptorOperation(EOEntity entity) { + super(); + _entity = entity; + } + + public void setAdaptorOperator(int adOp) { + _adaptorOp = adOp; + } + public int adaptorOperator() { + return _adaptorOp; + } + + public void setAttributes(NSArray atts) { + _attributes = atts; + } + public NSArray attributes() { + return _attributes; + } + + public void setChangedValues(NSDictionary values) { + _changedValues = values; + } + public NSDictionary changedValues() { + return _changedValues; + } + + public int compareAdaptorOperation(EOAdaptorOperation op) { + if (op.entity() != null && entity() != null) { + if (!op.entity().name().equals(entity().name())) + op.entity().name().compareTo(entity().name()); + } + if (adaptorOperator() < op.adaptorOperator()) + return -1; + if (adaptorOperator() > op.adaptorOperator()) + return 1; + return 0; + } + + public EOEntity entity() { + return _entity; + } + + public void setException(Throwable t) { + _exception = t; + } + public Throwable exception() { + return _exception; + } + + public void setQualifier(EOQualifier q) { + _qualifier = q; + } + public EOQualifier qualifier() { + return _qualifier; + } + + public void setStoredProcedure(EOStoredProcedure sp) { + _proc = sp; + } + public EOStoredProcedure storedProcedure() { + return _proc; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/13 00:37:45 chochos + * an almost complete implementation of the abstract adaptor-layer classes + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAttribute.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAttribute.java new file mode 100644 index 0000000..8b651ec --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOAttribute.java @@ -0,0 +1,381 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; + +/** +* Represents an attribute inside an entity. Contains mapping data for +* the attribute's external name, external and internal datatypes, etc. +* It can also represent a flattened or derived attribute, or a prototype; +* and they are also used to represent parameters in a stored procedure. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOAttribute extends EOProperty implements EOPropertyListEncoding { + + //These are used for stored procedure parameters. + public static final int Void = 0; + public static final int InParameter = 1; + public static final int OutParameter = 2; + public static final int InOutParameter = 3; + + protected EOEntity _entity; + protected String _name; + protected String _columnName; + protected String _definition; + protected String _className; + protected String _externalType; + protected Class _valueClass; + protected String _valueClassName; + protected String _valueType; + protected String _valueFactoryMethodName; + protected String _readFormat; + protected String _writeFormat; + protected String _prototypeName; + protected EOAttribute _prototype; + protected NSSelector _valueFactoryMethod; + protected boolean _allowsNull; + protected boolean _readOnly; + protected boolean _isFlattened; + protected boolean _knowsIfFlattened; + protected int _precision; + protected int _scale; + protected int _width; + protected int _parameterDirection; + protected NSDictionary _internalInfo; + protected NSDictionary _userInfo; + + protected boolean _has_allowsNull; + + public EOAttribute() { + super(); + _allowsNull = true; + } + + public EOAttribute(NSDictionary dict, Object obj) { + super(); + if (obj instanceof EOEntity) + _entity = (EOEntity)obj; + setName((String)dict.objectForKey("name")); + if (dict.objectForKey("columnName") != null) + setColumnName((String)dict.objectForKey("columnName")); + if (dict.objectForKey("definition") != null) + setDefinition((String)dict.objectForKey("definition")); + _prototypeName = (String)dict.objectForKey("prototypeName"); + setExternalType((String)dict.objectForKey("externalType")); + setClassName((String)dict.objectForKey("valueClassName")); + setValueType((String)dict.objectForKey("valueType")); + _writeFormat = (String)dict.objectForKey("writeFormat"); + _readFormat = (String)dict.objectForKey("readFormat"); + if (dict.objectForKey("precision") != null) + setPrecision(Integer.parseInt((String)dict.objectForKey("precision"))); + if (dict.objectForKey("scale") != null) + setScale(Integer.parseInt((String)dict.objectForKey("scale"))); + if (dict.objectForKey("width") != null) + setWidth(Integer.parseInt((String)dict.objectForKey("width"))); + if (dict.objectForKey("parameterDirection") != null) + setParameterDirection(Integer.parseInt((String)dict.objectForKey("parameterDirection"))); + setAllowsNull("Y".equals(dict.objectForKey("allowsNull"))); + } + + void setEntity(EOEntity value) { + _entity = value; + } + + public void setName(String name) { + _name = name; + } + public String name() { + return _name; + } + + public void setColumnName(String name) { + _columnName = name; + } + public String columnName() { + if (_columnName != null) + return _columnName; + if (prototype() != null) + if (_prototype.columnName() != null) + return _prototype.columnName(); + return null; + } + + public void setClassName(String name) { + _className = name; + } + public String className() { + if (_className != null) + return _className; + if (prototype() != null) + if (_prototype.className() != null) + return _prototype.className(); + return null; + } + + public void setDefinition(String def) { + _definition = def; + _columnName = null; + } + public String definition() { + if (_definition != null) + return _definition; + if (prototype() != null) + if (_prototype.definition() != null) + return _prototype.definition(); + return null; + } + + public void setExternalType(String type) { + _externalType = type; + } + public String externalType() { + if (_externalType != null) + return _externalType; + if (prototype() != null) + if (_prototype.externalType() != null) + return _prototype.externalType(); + return null; + } + + public void setAllowsNull(boolean flag) { + _allowsNull = flag; + _has_allowsNull = true; + } + public boolean allowsNull() { + if (_has_allowsNull) + return _allowsNull; + if (prototype() != null) + return _prototype.allowsNull(); + return _allowsNull; + } + + public void setReadOnly(boolean flag) { + _readOnly = flag; + } + public boolean readOnly() { + return _readOnly; + } + + public void setPrototype(EOAttribute proto) { + _prototype = proto; + if (proto != null) + _prototypeName = proto.name(); + else + _prototypeName = null; + } + public EOAttribute prototype() { + if (_prototypeName != null && _prototype == null) { + try { + EOModel m = _entity.model(); + EOModelGroup g = m.modelGroup(); + EOEntity protos = g.entityNamed("EO" + m.adaptorName() + "Prototypes"); + if (protos == null) + protos = g.entityNamed("EOPrototypes"); + _prototype = protos.attributeNamed(_prototypeName); + } catch (NullPointerException e) { + } + } + return _prototype; + } + + public void setPrecision(int value) { + _precision = value; + } + public int precision() { + if (_precision > 0) + return _precision; + if (prototype() != null) + return _prototype.precision(); + return _precision; + } + + public void setScale(int value) { + _scale = value; + } + public int scale() { + if (_scale > 0) + return _scale; + if (prototype() != null) + return _prototype.scale(); + return _scale; + } + + public void setWidth(int value) { + _width = value; + } + public int width() { + if (_width > 0) + return _width; + if (prototype() != null) + return _prototype.width(); + return _width; + } + + /** @deprecated Use setClassName instead. */ + public void setValueClassName(String name) { + setClassName(name); + } + /** @deprecated Use className() instead. */ + public String valueClassName() { + return className(); + } + + public void setValueType(String type) { + _valueType = type; + } + public String valueType() { + if (_valueType != null) + return _valueType; + if (prototype() != null) + return _prototype.valueType(); + return null; + } + + public void setReadFormat(String value) { + _readFormat = value; + } + public String readFormat() { + return _readFormat; + } + + public void setWriteFormat(String value) { + _writeFormat = value; + } + public String writeFormat() { + return _writeFormat; + } + + public boolean isDerived() { + return (definition() != null); + } + + /** Determines whether the receiver is a flattened attribute. + * A flattened attribute has as its definition a relationship + * path that can be resolved to an attribute. + * @return true if the receiver is flattened. + */ + public boolean isFlattened() { + if (_knowsIfFlattened) + return _isFlattened; + _knowsIfFlattened = true; + if (definition() == null) + return false; + _isFlattened = (entity()._attributeForPath(definition()) != null); + return _isFlattened; + } + + public EOEntity entity() { + return _entity; + } + + public void setParameterDirection(int dir) { + _parameterDirection = dir; + } + public int parameterDirection() { + return _parameterDirection; + } + + public String relationshipPath() { + if (isFlattened()) + return definition(); + return null; + } + + public void setUserInfo(NSDictionary value) { + _userInfo = value; + } + public NSDictionary userInfo() { + return _userInfo; + } + + public void awakeWithPropertyList(NSDictionary plist) { + } + + public void encodeIntoPropertyList(NSMutableDictionary dict) { + dict.setObjectForKey(name(), "name"); + if (_prototypeName != null) + dict.setObjectForKey(_prototypeName, "prototypeName"); + if (_columnName != null) + dict.setObjectForKey(_columnName, "columnName"); + if (_definition != null) + dict.setObjectForKey(_definition, "definition"); + if (_className != null) + dict.setObjectForKey(_className, "valueClassName"); + if (_valueType != null) + dict.setObjectForKey(_valueType, "valueType"); + if (_precision > 0) + dict.setObjectForKey(new Integer(_precision), "precision"); + if (_scale > 0) + dict.setObjectForKey(new Integer(_scale), "scale"); + if (_width > 0) + dict.setObjectForKey(new Integer(_width), "width"); + if (_externalType != null) + dict.setObjectForKey(_externalType, "externalType"); + if (_readFormat != null) + dict.setObjectForKey(_readFormat, "readFormat"); + if (_writeFormat != null) + dict.setObjectForKey(_writeFormat, "writeFormat"); + if (_allowsNull) + dict.setObjectForKey("Y", "allowsNull"); + if (_entity == null) + dict.setObjectForKey(new Integer(parameterDirection()), "parameterDirection"); + if (userInfo() != null && userInfo().count() > 0) + dict.setObjectForKey(userInfo(), "userInfo"); + if (_internalInfo != null && _internalInfo.count() > 0) + dict.setObjectForKey(_internalInfo, "internalInfo"); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.7 2003/08/14 02:13:56 chochos + * implement and use _attributeForPath() + * + * Revision 1.6 2003/08/12 01:45:04 chochos + * added some code to handle prototypes + * + * Revision 1.5 2003/08/11 19:38:27 chochos + * Can now read from a file and re-write to another file. + * + * Revision 1.4 2003/08/09 01:35:35 chochos + * implement EOPropertyListEncoding + * + * Revision 1.3 2003/08/08 06:52:09 chochos + * isFlattened() works + * + * Revision 1.2 2003/08/08 02:15:03 chochos + * added parameterDirection (for use with stored procedures) + * + * Revision 1.1 2003/08/07 02:39:45 chochos + * EOAttribute. Can be initialized from a property list. + * +*/
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabase.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabase.java new file mode 100644 index 0000000..a9177c5 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabase.java @@ -0,0 +1,267 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import java.util.Enumeration; + +import net.wotonomy.control.EOEnterpriseObject; +import net.wotonomy.control.EOGlobalID; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSTimestamp; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EODatabase { + + protected EOAdaptor _adaptor; + protected NSMutableArray _models = new NSMutableArray(); + protected NSMutableArray _contexts = new NSMutableArray(); + protected NSMutableDictionary _resultCache = new NSMutableDictionary(); + protected NSMutableDictionary _snapshots = new NSMutableDictionary(); + protected NSTimestamp _timestamp; + protected static boolean _releaseUnrefSnapshots = true; + + public EODatabase(EOAdaptor adaptor) { + super(); + if (adaptor == null) + throw new IllegalArgumentException("Adaptor cannot be null."); + _adaptor = adaptor; + } + + public EODatabase(EOModel model) { + super(); + _adaptor = EOAdaptor.adaptorWithModel(model); + addModel(model); + } + + public EOAdaptor adaptor() { + return _adaptor; + } + + public void addModel(EOModel model) { + if (!addModelIfCompatible(model)) + throw new IllegalArgumentException("Model is not compatible with this database."); + } + + public void removeMode(EOModel model) { + _models.removeObject(model); + } + + public boolean addModelIfCompatible(EOModel model) { + if (_models.containsObject(model)) + return false; + if (model.adaptorName().equals(adaptor().name())) { + if (adaptor().canServiceModel(model)) { + _models.addObject(model); + return true; + } + } + return false; + } + + public void decrementSnapshotCountForGlobalID(EOGlobalID gid) { + if (_releaseUnrefSnapshots) { + } + } + + public void incrementSnapshotCountForGlobalID(EOGlobalID gid) { + if (_releaseUnrefSnapshots) { + } + } + + public static void disableSnapshotRefCounting() { + _releaseUnrefSnapshots = false; + } + + public EOEntity entityForObject(EOEnterpriseObject eo) { + String cname = eo.getClass().getName(); + for (int i = 0; i < _models.count(); i++) { + EOModel m = (EOModel)_models.objectAtIndex(i); + NSArray ents = m.entities(); + for (int j = 0; j < ents.count(); j++) { + EOEntity e = (EOEntity)ents.objectAtIndex(i); + if (e.className().equals(cname)) + return e; + } + } + return null; + } + + public EOEntity entityNamed(String name) { + for (int i = 0; i < _models.count(); i++) { + EOModel m = (EOModel)_models.objectAtIndex(i); + NSArray ents = m.entities(); + for (int j = 0; j < ents.count(); j++) { + EOEntity e = (EOEntity)ents.objectAtIndex(i); + if (e.name().equals(name)) + return e; + } + } + return null; + } + + public void forgetAllSnapshots() { + _snapshots.removeAllObjects(); + } + + public void forgetSnapshotForGlobalID(EOGlobalID gid) { + _snapshots.removeObjectForKey(gid); + } + + public void forgetSnapshotsForGlobalIDs(NSArray gids) { + for (int i = 0; i < gids.count(); i++) + forgetSnapshotForGlobalID((EOGlobalID)gids.objectAtIndex(i)); + } + + public void handleDroppedConnection() { + adaptor().handleDroppedConnection(); + for (int i = 0; i < _contexts.count(); i++) { + EODatabaseContext c = (EODatabaseContext)_contexts.objectAtIndex(i); + c.handleDroppedConnection(); + } + } + + public void invalidateResultCache() { + _resultCache.removeAllObjects(); + } + + public void invalidateResultCacheForEntityNamed(String name) { + _resultCache.removeObjectForKey(name); + } + + public NSArray models() { + return new NSArray(_models); + } + + public void recordSnapshotForGlobalID(NSDictionary snap, EOGlobalID gid) { + _snapshots.setObjectForKey(snap, gid); + } + + public void recordSnapshotForSourceGlobalID(NSArray gids, EOGlobalID gid, String name) { + NSMutableDictionary d = (NSMutableDictionary)_snapshots.objectForKey(gid); + if (d == null) { + d = new NSMutableDictionary(); + _snapshots.setObjectForKey(d, gid); + } + d.setObjectForKey(gids, name); + } + + public void recordSnapshots(NSDictionary snaps) { + _snapshots.addEntriesFromDictionary(snaps); + } + + public void recordToManySnapshots(NSDictionary snaps) { + Enumeration enumeration = snaps.keyEnumerator(); + while (enumeration.hasMoreElements()) { + EOGlobalID gid = (EOGlobalID)enumeration.nextElement(); + NSDictionary rels = (NSDictionary)snaps.objectForKey(gid); + Enumeration relEnum = rels.keyEnumerator(); + while (relEnum.hasMoreElements()) { + String relName = (String)relEnum.nextElement(); + NSArray gids = (NSArray)rels.objectForKey(relName); + recordSnapshotForSourceGlobalID(gids, gid, relName); + } + } + } + + public void registerContext(EODatabaseContext context) { + if (!_contexts.contains(context)) { + if (context.database() != this) + throw new IllegalStateException("Cannot register context assigned to a different database."); + _contexts.addObject(context); + } + } + + public void unregisterContext(EODatabaseContext context) { + _contexts.removeObject(context); + } + + public NSArray registeredContexts() { + return new NSArray(_contexts); + } + + public NSArray resultCacheForEntityNamed(String name) { + return (NSArray)_resultCache.objectForKey(name); + } + + public void setResultCache(NSArray cache, String entityName) { + _resultCache.setObjectForKey(cache, entityName); + } + + public void setTimestampToNow() { + _timestamp = new NSTimestamp(); + } + + public NSDictionary snapshotForGlobalID(EOGlobalID gid) { + return (NSDictionary)_snapshots.objectForKey(gid); + } + + public NSDictionary snapshotForGlobalID(EOGlobalID gid, long l) { + return null; + } + + public NSArray snapshotForSourceGlobalID(EOGlobalID gid, String name) { + NSDictionary d = (NSDictionary)_snapshots.objectForKey(gid); + if (d == null) + return null; + return (NSArray)d.objectForKey(name); + } + + public NSDictionary snapshotForSourceGlobalID(EOGlobalID gid, String s, long l) { + return null; + } + + public NSDictionary snapshots() { + return _snapshots; + } + + public long timestampForGlobalID(EOGlobalID gid) { + return NSTimestamp.DistantPast.timeIntervalSinceReferenceDate(); + } + + public long timestampForSourceGlobalID(EOGlobalID gid, String s) { + return 0; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.1 2003/08/19 01:54:43 chochos + * The EODatabase layer still needs a lot of work, but it's on its way... + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseChannel.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseChannel.java new file mode 100644 index 0000000..10424e9 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseChannel.java @@ -0,0 +1,138 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOClassDescription; +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOGlobalID; +import net.wotonomy.control.EOKeyValueCodingAdditions; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EODatabaseChannel { + + protected EODatabaseContext _context; + protected EOAdaptorChannel _channel; + protected EOEntity _currEntity; + protected EOEditingContext _currEC; + protected NSArray _attributes; + protected boolean _locking; + protected boolean _refreshing; + + public EODatabaseChannel(EODatabaseContext context) { + super(); + _context = context; + _channel = _context.adaptorContext().createAdaptorChannel(); + } + + public EOAdaptorChannel adaptorChannel() { + return _channel; + } + + public EODatabaseContext databaseContext() { + return _context; + } + + public void cancelFetch() { + _channel.cancelFetch(); + } + + public Object fetchObject() { + NSDictionary r = _channel.fetchRow(); + EOGlobalID gid = _currEntity.globalIDForRow(r); + Object eo = _currEC.objectForGlobalID(gid); + if (eo == null) { + eo = EOClassDescription.classDescriptionForEntityName(_currEntity.name()).createInstanceWithEditingContext(_currEC, gid); + if (eo instanceof EOKeyValueCodingAdditions) + ((EOKeyValueCodingAdditions)eo).takeValuesFromDictionary(r); + else + EOKeyValueCodingAdditions.DefaultImplementation.takeValuesFromDictionary(eo, r); + } else { + if (isRefreshingObjects()) { + //TODO: refresh object (how?) + } + } + return eo; + } + + public boolean isFetchInProgress() { + return _channel.isFetchInProgress(); + } + + public void setIsLocking(boolean flag) { + _locking = flag; + } + + public boolean isLocking() { + return _locking; + } + + public void setIsRefreshingObjects(boolean flag) { + _refreshing = flag; + } + + public boolean isRefreshingObjects() { + return _refreshing; + } + + public void selectObjectsWithFetchSpecification(EOFetchSpecification fspec, EOEditingContext ec) { + setIsLocking(fspec.locksObjects()); + setIsRefreshingObjects(fspec.refreshesRefetchedObjects()); + setCurrentEditingContext(ec); + setCurrentEntity(databaseContext().database().entityNamed(fspec.entityName())); + NSMutableArray atts = new NSMutableArray(); + atts.addObjectsFromArray(_currEntity.attributes()); + adaptorChannel().selectAttributes(atts, fspec, isLocking(), _currEntity); + adaptorChannel().setAttributesToFetch(atts); + _attributes = atts; + } + + public void setCurrentEditingContext(EOEditingContext ec) { + _currEC = ec; + } + + public void setCurrentEntity(EOEntity entity) { + _currEntity = entity; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/29 21:15:46 chochos + * use EOEntity's globalIDForRow method. + * + * Revision 1.1 2003/08/19 01:54:43 chochos + * The EODatabase layer still needs a lot of work, but it's on its way... + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseContext.java new file mode 100644 index 0000000..af696fe --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseContext.java @@ -0,0 +1,568 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +import net.wotonomy.control.EOAndQualifier; +import net.wotonomy.control.EOCooperatingObjectStore; +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOEnterpriseObject; +import net.wotonomy.control.EOFaultHandler; +import net.wotonomy.control.EOFaulting; +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOGlobalID; +import net.wotonomy.control.EOKeyGlobalID; +import net.wotonomy.control.EOKeyValueCoding; +import net.wotonomy.control.EOKeyValueCodingSupport; +import net.wotonomy.control.EOKeyValueQualifier; +import net.wotonomy.control.EOObjectStoreCoordinator; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSLocking; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EODatabaseContext + extends EOCooperatingObjectStore implements NSLocking { + + private static Class _contextClass; + protected EODatabase _database; + protected EOAdaptorContext _context; + protected NSMutableArray _channels = new NSMutableArray(); + protected NSMutableArray _lockedObjects = new NSMutableArray(); + + protected NSMutableDictionary _simpleSnaps; + protected NSMutableDictionary _manySnaps; + + protected EOObjectStoreCoordinator _coordinator; + protected EOEditingContext _currEC; + protected int _updateStrategy; + + public EODatabaseContext(EODatabase database) { + super(); + _database = database; + _context = _database.adaptor().createAdaptorContext(); + } + + public EOAdaptorContext adaptorContext() { + return _context; + } + + public EODatabase database() { + return _database; + } + + public EODatabaseChannel availableChannel() { + for (int i = 0; i < _channels.count(); i++) { + EODatabaseChannel c = (EODatabaseChannel)_channels.objectAtIndex(i); + if (!c.isFetchInProgress()) + return c; + } + EODatabaseChannel c = new EODatabaseChannel(this); + registerChannel(c); + return c; + } + + public void batchFetchRelationship(EORelationship rel, NSArray arr, EOEditingContext ec) { + } + + public static void setContextClassToRegister(Class contextClass) { + _contextClass = contextClass; + } + public static Class contextClassToRegister() { + if (_contextClass == null) + _contextClass = EODatabaseContext.class; + return _contextClass; + } + + public EOObjectStoreCoordinator coordinator() { + return _coordinator; + } + + public void editingContextDidForgetObjectWithGlobalID(EOEditingContext ec, EOGlobalID gid) { + database().decrementSnapshotCountForGlobalID(gid); + } + + public void handleDroppedConnection() { + //TODO: unregister channels + adaptorContext().handleDroppedConnection(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#ownsGlobalID(net.wotonomy.control.EOGlobalID) + */ + public boolean ownsGlobalID(EOGlobalID gid) { + if (!(gid instanceof EOKeyGlobalID)) + return false; + return (database().entityNamed(((EOKeyGlobalID)gid).entityName()) != null); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#ownsObject(net.wotonomy.control.EOEnterpriseObject) + */ + public boolean ownsObject(EOEnterpriseObject eo) { + if (eo.entityName() == null) + return false; + return (database().entityNamed(eo.entityName()) != null); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#handlesFetchSpecification(net.wotonomy.control.EOFetchSpecification) + */ + public boolean handlesFetchSpecification(EOFetchSpecification fspec) { + String ename = fspec.entityName(); + return (database().entityNamed(ename) != null); + } + + public boolean hasBusyChannels() { + return adaptorContext().hasBusyChannels(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#prepareForSaveWithCoordinator(net.wotonomy.control.EOObjectStoreCoordinator, net.wotonomy.control.EOEditingContext) + */ + public void prepareForSaveWithCoordinator(EOObjectStoreCoordinator coord, EOEditingContext ec) { + // TODO Auto-generated method stub + _coordinator = coord; + _currEC = ec; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#recordChangesInEditingContext() + */ + public void recordChangesInEditingContext() { + // TODO insert, delete, update + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#recordUpdateForObject(net.wotonomy.control.EOEnterpriseObject, net.wotonomy.foundation.NSDictionary) + */ + public void recordUpdateForObject(EOEnterpriseObject eo, NSDictionary changes) { + // TODO Auto-generated method stub + } + + public void recordSnapshotForGlobalID(NSDictionary snap, EOGlobalID gid) { + if (_simpleSnaps == null) + throw new IllegalArgumentException("Attempt to record a snapshot without a transaction in progress"); + _simpleSnaps.setObjectForKey(snap, gid); + } + + public void recordSnapshotForSourceGlobalID(NSArray gids, EOGlobalID gid, String relationName) { + if (_manySnaps == null) + throw new IllegalArgumentException("Attempt to record a snapshot without a transaction in progress"); + NSMutableDictionary d = (NSMutableDictionary)_manySnaps.objectForKey(gid); + if (d == null) { + d = new NSMutableDictionary(); + _manySnaps.setObjectForKey(d, gid); + } + d.setObjectForKey(gids, relationName); + } + + public void recordSnapshots(NSDictionary snaps) { + if (_simpleSnaps == null) + throw new IllegalArgumentException("Attempt to record snapshots without a transaction in progress."); + _simpleSnaps.addEntriesFromDictionary(snaps); + /* Make sure we don't need to do this instead + Enumeration enumeration = snaps.keyEnumerator(); + while (enumeration.hasMoreElements()) { + EOGlobalID g = (EOGlobalID)enumeration.nextElement(); + NSDictionary d = (NSDictionary)snaps.objectForKey(g); + recordSnapshotForGlobalID(d, g); + }*/ + } + + public void recordToManySnapshots(NSDictionary snaps) { + if (_manySnaps == null) + throw new IllegalArgumentException("Attempt to record snapshots without a transaction in progress."); + Enumeration enumeration = snaps.keyEnumerator(); + while (enumeration.hasMoreElements()) { + Object key = enumeration.nextElement(); + NSDictionary d = (NSDictionary)snaps.objectForKey(key); + NSMutableDictionary d2 = (NSMutableDictionary)_manySnaps.objectForKey(key); + if (d2 == null) { + d2 = new NSMutableDictionary(); + _manySnaps.setObjectForKey(d2, key); + } + //this could also be done with many calls to recordSnapshotForSourceGID + d2.addEntriesFromDictionary(d); + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#performChanges() + */ + public void performChanges() { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#commitChanges() + */ + public void commitChanges() { + adaptorContext().commitTransaction(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#rollbackChanges() + */ + public void rollbackChanges() { + adaptorContext().rollbackTransaction(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOCooperatingObjectStore#valuesForKeys(net.wotonomy.foundation.NSArray, net.wotonomy.control.EOEnterpriseObject) + */ + public NSDictionary valuesForKeys(NSArray keys, EOEnterpriseObject eo) { + // TODO check snapshots; eo could be a fault + return eo.valuesForKeys(keys); + } + + /* (non-Javadoc) + * @see net.wotonomy.foundation.NSLocking#lock() + */ + public void lock() { + EOAccessLock.lock(); + } + + /* (non-Javadoc) + * @see net.wotonomy.foundation.NSLocking#unlock() + */ + public void unlock() { + EOAccessLock.unlock(); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#arrayFaultWithSourceGlobalID(net.wotonomy.control.EOGlobalID, java.lang.String, net.wotonomy.control.EOEditingContext) + */ + public NSArray arrayFaultWithSourceGlobalID( + EOGlobalID gid, String relName, EOEditingContext ec) { + if (!(gid instanceof EOKeyGlobalID)) + throw new IllegalArgumentException("an EOKeyGlobalID is needed."); + EOAccessArrayFaultHandler handler = new EOAccessArrayFaultHandler((EOKeyGlobalID)gid, relName, this, ec); + return new NSArray(handler); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#faultForGlobalID(net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + */ + public /*EOEnterpriseObject*/Object faultForGlobalID(EOGlobalID gid, EOEditingContext ec) { + if (!(gid instanceof EOKeyGlobalID)) + throw new IllegalArgumentException("Cannot fault an object that doesn't have a key global ID."); + EOAccessFaultHandler handler = new EOAccessFaultHandler((EOKeyGlobalID)gid, this, ec); + EOEntity e = database().entityNamed(((EOKeyGlobalID)gid).entityName()); + Object o = e.classDescriptionForInstances().createInstanceWithEditingContext(ec, gid); + EOFaultHandler.makeObjectIntoFault(o, handler); + return o; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#faultForRawRow(java.util.Map, java.lang.String, net.wotonomy.control.EOEditingContext) + */ + public /*EOEnterpriseObject*/ Object faultForRawRow(Map row, String entityName, EOEditingContext ec) { + EOEntity e = database().entityNamed(entityName); + EOGlobalID gid = e.globalIDForRow(row); + return faultForGlobalID(gid, ec); + } + + public void forgetSnapshotForGlobalID(EOGlobalID gid) { + if (_simpleSnaps == null) + throw new IllegalArgumentException("Attempt to forget snapshot with no transaction in progress."); + _simpleSnaps.removeObjectForKey(gid); + _manySnaps.removeObjectForKey(gid); + } + + public void forgetSnapshotsForGlobalIDs(List gids) { + for (int i = 0; i < gids.size(); i++) { + EOGlobalID g = (EOGlobalID)gids.get(i); + forgetSnapshotForGlobalID(g); + database().forgetSnapshotForGlobalID(g); + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#initializeObject(java.lang.Object, net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + */ + public void initializeObject(/*EOEnterpriseObject*/Object eo, EOGlobalID gid, EOEditingContext ec) { + if (gid.isTemporary()) + return; + NSDictionary snap = snapshotForGlobalID(gid); + Object obj = ec.objectForGlobalID(gid); + EOEntity e = database().entityNamed(((EOKeyGlobalID)gid).entityName()); + NSArray props = e.classProperties(); + for (int i = 0; i < props.count(); i++) { + EOProperty p = (EOProperty)props.objectAtIndex(i); + Object val = snap.objectForKey(p.name()); + if (p instanceof EOAttribute) { + if ( eo instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)eo).takeValueForKey(val, p.name()); + } + else + { + EOKeyValueCodingSupport.takeValueForKey( eo, val, p.name() ); + } + } else if (p instanceof EORelationship) { + if (((EORelationship)p).isToMany()) { + val = arrayFaultWithSourceGlobalID(gid, p.name(), ec); + } else { + EOEntity dest = ((EORelationship)p).destinationEntity(); + EOKeyGlobalID kgid = (EOKeyGlobalID)dest.globalIDForRow(snap); + val = ec.objectForGlobalID(kgid); + if (val == null) + val = new EOAccessFaultHandler(kgid, this, ec); + } + if (val == EOKeyValueCoding.NullValue) + val = null; + if ( eo instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)eo).takeValueForKey(val, p.name()); + } + else + { + EOKeyValueCodingSupport.takeValueForKey( eo, val, p.name() ); + } + } + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#invalidateAllObjects() + */ + public void invalidateAllObjects() { + invalidateObjectsWithGlobalIDs(database().snapshots().allKeys()); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#invalidateObjectsWithGlobalIDs(java.util.List) + */ + public void invalidateObjectsWithGlobalIDs(List aList) { + forgetSnapshotsForGlobalIDs(aList); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#isObjectLockedWithGlobalID(net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + */ + public boolean isObjectLockedWithGlobalID(EOGlobalID gid, EOEditingContext ec) { + return isObjectLockedWithGlobalID(gid); + } + + public boolean isObjectLockedWithGlobalID(EOGlobalID gid) { + return _lockedObjects.containsObject(gid); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#lockObjectWithGlobalID(net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + */ + public void lockObjectWithGlobalID(EOGlobalID gid, EOEditingContext ec) { + NSDictionary snap = snapshotForGlobalID(gid); + if (snap == null) + return; + if (!(gid instanceof EOKeyGlobalID)) + return; + EOEntity e = database().entityNamed(((EOKeyGlobalID)gid).entityName()); + EOQualifier q = e.qualifierForPrimaryKey(snap); + EOFetchSpecification fspec = new EOFetchSpecification(e.name(), q, null); + fspec.setLocksObjects(true); + NSArray arr = ec.objectsWithFetchSpecification(fspec); + if (arr.count() != 1) + throw new IllegalStateException("Cannot lock object with Global ID " + gid); + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#objectsForSourceGlobalID(net.wotonomy.control.EOGlobalID, java.lang.String, net.wotonomy.control.EOEditingContext) + */ + public NSArray objectsForSourceGlobalID( + EOGlobalID gid, String relationName, EOEditingContext ec) { + EOEnterpriseObject eo = (EOEnterpriseObject)ec.objectForGlobalID(gid); + if (eo == null) + throw new IllegalStateException("Cannot find object for global ID " + gid + " in specified editing context."); + //Get the source object + EOEnterpriseObject source = (EOEnterpriseObject) faultForGlobalID(gid, ec); + if (source == null) + throw new IllegalStateException("There is no snapshot for source global ID " + gid); + + //Check if there is already a value here + NSArray value = (NSArray)source.valueForKey(relationName); + EOAccessArrayFaultHandler handler = null; + if (value != null) { + if (EOFaultHandler.isFault(value)) { + handler = new EOAccessArrayFaultHandler((EOKeyGlobalID)gid, relationName, this, ec); + //TODO: fire the fault an return the value + } else + return value; + } + + //Get the relationship + EOEntity entity = database().entityNamed(eo.entityName()); + EORelationship rel = entity.relationshipNamed(relationName); + if (rel == null) + throw new IllegalStateException("Cannot find relationship named " + relationName + " in entity " + entity.name()); + + //create a fetch specification for this + EOQualifier q = null; + NSArray joins = rel.joins(); + NSMutableArray subq = new NSMutableArray(joins.count()); + for (int i = 0; i < joins.count(); i++) { + EOJoin j = (EOJoin)joins.objectAtIndex(i); + String key = j.destinationAttribute().name(); + Object val = eo.valueForKey(j.sourceAttribute().name()); + subq.addObject(new EOKeyValueQualifier(key, EOQualifier.QualifierOperatorEqual, val)); + } + if (subq.count() == 1) { + q = (EOQualifier)subq.objectAtIndex(0); + } else { + q = new EOAndQualifier(subq); + } + EOFetchSpecification fspec = new EOFetchSpecification(rel.destinationEntity().name(), q, null); + NSArray res = ec.objectsWithFetchSpecification(fspec, ec); + NSMutableArray gids = new NSMutableArray(res.count()); + for (int i = 0; i < res.count(); i++) + gids.addObject(ec.globalIDForObject(res.objectAtIndex(i))); + recordSnapshotForSourceGlobalID(gids, gid, relationName); + return res; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#objectsWithFetchSpecification(net.wotonomy.control.EOFetchSpecification, net.wotonomy.control.EOEditingContext) + */ + public NSArray objectsWithFetchSpecification(EOFetchSpecification fspec, EOEditingContext ec) { + EODatabaseChannel channel = availableChannel(); + channel.selectObjectsWithFetchSpecification(fspec, ec); + NSMutableArray arr = new NSMutableArray(); + while (channel.isFetchInProgress()) { + Object o = channel.fetchObject(); + if (o != null) { + arr.addObject(o); + } + } + return arr; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#refaultObject(java.lang.Object, net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + */ + public void refaultObject(Object obj, EOGlobalID gid, EOEditingContext ec) { + if (!(gid instanceof EOKeyGlobalID)) + throw new IllegalArgumentException("GlobalID must be an EOKeyGlobalID"); + if (obj instanceof EOFaulting) + //check if it's already a fault + if (!EOFaultHandler.isFault(obj)) { + ((EOFaulting)obj).turnIntoFault(EOFaultHandler.handlerForFault(obj)); + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOObjectStore#saveChangesInEditingContext(net.wotonomy.control.EOEditingContext) + */ + public void saveChangesInEditingContext(EOEditingContext ec) { + prepareForSaveWithCoordinator(null, ec); + recordChangesInEditingContext(); + performChanges(); + commitChanges(); + } + + public void registerChannel(EODatabaseChannel channel) { + if (channel.databaseContext() != this) + throw new IllegalArgumentException("Cannot register a channel on a context other than its own."); + if (_channels.containsObject(channel)) + throw new IllegalArgumentException("Attempt to register a channel more than once."); + _channels.addObject(channel); + } + + public void unregisterChannel(EODatabaseChannel channel) { + if (channel.databaseContext() != this) + throw new IllegalArgumentException("Attempt to unregister a channel from a context other than its own."); + _channels.removeObject(channel); + } + + public NSArray registeredChannels() { + return new NSArray(_channels); + } + + public NSDictionary snapshotForGlobalID(EOGlobalID gid) { + NSDictionary d = null; + if (_simpleSnaps != null) { + d = (NSDictionary)_simpleSnaps.objectForKey(gid); + } + if (d == null) + d = database().snapshotForGlobalID(gid); + return d; + } + + public NSArray snapshotForSourceGlobalID(EOGlobalID gid, String name) { + NSArray a = null; + if (_manySnaps != null) { + NSDictionary d = (NSDictionary)_manySnaps.objectForKey(gid); + a = (NSArray)d.objectForKey(name); + } + if (a == null) + a = database().snapshotForSourceGlobalID(gid, name); + return a; + } + + public void setUpdateStrategy(int strategy) { + _updateStrategy = strategy; + } + + public int updateStrategy() { + return _updateStrategy; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.4 2003/12/18 15:37:38 mpowers + * Changes to retain ability to work with objects that don't necessarily + * implement EOEnterpriseObject. I would still like to preserve this case + * for general usage, however the access package is free to assume that + * those objects will be EOs and cast appropriately. + * + * Revision 1.3 2003/08/29 21:14:44 chochos + * implement a couple more methods. + * + * Revision 1.2 2003/08/20 01:16:22 chochos + * more methods have code now, but there's no way to test this yet. The core is still pending. + * + * Revision 1.1 2003/08/19 01:54:43 chochos + * The EODatabase layer still needs a lot of work, but it's on its way... + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseOperation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseOperation.java new file mode 100644 index 0000000..596180e --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EODatabaseOperation.java @@ -0,0 +1,48 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EODatabaseOperation { + + public static final int AdaptorLockOperator = 0; + public static final int AdaptorInsertOperator = 1; + public static final int AdaptorUpdateOperator = 2; + public static final int AdaptorDeleteOperator = 3; + public static final int AdaptorStoredProcedureOperator = 4; + + public static final int DatabaseNothingOperator = 0; + public static final int DatabaseInsertOperator = 1; + public static final int DatabaseUpdateOperator = 2; + public static final int DatabaseDeleteOperator = 3; + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/13 00:38:47 chochos + * for the moment, this only contains some constants needed by EOAdaptorChannel and EOAdaptorOperation. + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntity.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntity.java new file mode 100644 index 0000000..4adc4a1 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntity.java @@ -0,0 +1,637 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Map; + +import net.wotonomy.control.EOAndQualifier; +import net.wotonomy.control.EOClassDescription; +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOGenericRecord; +import net.wotonomy.control.EOGlobalID; +import net.wotonomy.control.EOIntegralKeyGlobalID; +import net.wotonomy.control.EOKeyGlobalID; +import net.wotonomy.control.EOKeyValueArchiver; +import net.wotonomy.control.EOKeyValueQualifier; +import net.wotonomy.control.EOKeyValueUnarchiver; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.control.EOVectorKeyGlobalID; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSKeyValueCoding; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSPropertyListSerialization; + +/** +* An EOEntity is a mapping between a Java class and a database table or view. +* It indicates which attributes should be fetched from the table/view, what +* attributes are part of the primary key, what class the entity should map to. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOEntity implements EOPropertyListEncoding { + + protected NSMutableDictionary _attributes = new NSMutableDictionary(); + protected NSMutableDictionary _relations = new NSMutableDictionary(); + private NSMutableArray _classPropertyNames = new NSMutableArray(); + private NSMutableArray _classProperties = new NSMutableArray(); + private NSMutableArray _classPropertyAttributes = new NSMutableArray(); + private NSMutableArray _classPropertyManyRelationships = new NSMutableArray(); + private NSMutableArray _classPropertyOneRelationships = new NSMutableArray(); + protected NSArray _pkAttributes = NSArray.EmptyArray; + protected NSArray _pkAttributeNames = NSArray.EmptyArray; + protected NSMutableDictionary _fetchSpecs = new NSMutableDictionary(); + protected NSMutableArray _lockingAttributes = new NSMutableArray(); + + protected String _className; + protected String _name; + protected String _externalName; + protected boolean _isAbstract; + protected boolean _isReadOnly; + protected EOModel _model; + protected NSDictionary _userInfo; + protected NSDictionary _internalInfo; + private boolean _loadedFetchSpecs; + + public EOEntity() { + super(); + } + + public EOEntity(NSDictionary dict, Object obj) { + super(); + _model = (EOModel)obj; + setName((String)dict.objectForKey("name")); + setExternalName((String)dict.objectForKey("externalName")); + setClassName((String)dict.objectForKey("className")); + if (dict.objectForKey("internalInfo") != null) + _internalInfo = (NSDictionary)dict.objectForKey("internalInfo"); + if (dict.objectForKey("userInfo") != null) + _userInfo = (NSDictionary)dict.objectForKey("userInfo"); + + //Read the attributes + NSArray atr = (NSArray)dict.objectForKey("attributes"); + for (int i = 0; i < atr.count(); i++) { + NSDictionary d = (NSDictionary)atr.objectAtIndex(i); + EOAttribute atrib = new EOAttribute(d, this); + addAttribute(atrib); + } + + //Set the primary key + atr = (NSArray)dict.objectForKey("primaryKeyAttributes"); + NSMutableArray pka = new NSMutableArray(); + for (int i = 0; i < atr.count(); i++) { + EOAttribute a = attributeNamed((String)atr.objectAtIndex(i)); + pka.addObject(a); + } + _pkAttributes = new NSArray(pka); + _pkAttributeNames = atr; + + //attributes used for locking + _lockingAttributes.removeAllObjects(); + atr = (NSArray)dict.objectForKey("attributesUsedForLocking"); + for (int i = 0; i < atr.count(); i++) { + String x = (String)atr.objectAtIndex(i); + EOAttribute a = attributeNamed(x); + _lockingAttributes.addObject(a); + } + + //class properties + atr = (NSArray)dict.objectForKey("classProperties"); + if (atr != null) { + for (int i = 0; i < atr.count(); i++) + if (!_classPropertyNames.containsObject((atr.objectAtIndex(i)))) + _classPropertyNames.addObject(atr.objectAtIndex(i)); + } + + //Read the relationships + atr = (NSArray)dict.objectForKey("relationships"); + if (atr != null) { + for (int i = 0; i < atr.count(); i++) { + NSDictionary d = (NSDictionary)atr.objectAtIndex(i); + EORelationship rel = new EORelationship(d, this); + addRelationship(rel); + } + } + } + + public void addAttribute(EOAttribute atr) { + if (atr.name() == null) + throw new IllegalArgumentException("Cannot add an unnamed attribute to an entity."); + if (_attributes.objectForKey(atr.name()) != null) + throw new IllegalArgumentException("Entity " + name() + " already has an attribute named " + atr.name()); + _attributes.setObjectForKey(atr, atr.name()); + atr.setEntity(this); + _lockingAttributes.addObject(atr); + _classProperties.addObject(atr); + _classPropertyNames.addObject(atr.name()); + _classPropertyAttributes.addObject(atr); + } + + public void removeAttribute(EOAttribute atr) { + _attributes.removeObjectForKey(atr.name()); + atr.setEntity(null); + _classProperties.removeObject(atr); + _classPropertyNames.removeObject(atr.name()); + _classPropertyAttributes.removeObject(atr); + } + + public void addFetchSpecification(EOFetchSpecification fspec, String name) { + loadFetchSpecifications(); + if (_fetchSpecs.objectForKey(name) != null) + throw new IllegalArgumentException("Entity " + name() + " already has a fetch specification named " + name); + _fetchSpecs.setObjectForKey(fspec, name); + } + + public void removeFetchSpecificationNamed(String name) { + _fetchSpecs.removeObjectForKey(name); + } + + public EOFetchSpecification fetchSpecificationNamed(String name) { + loadFetchSpecifications(); + return (EOFetchSpecification)_fetchSpecs.objectForKey(name); + } + + public NSArray fetchSpecificationNames() { + loadFetchSpecifications(); + return _fetchSpecs.allKeys(); + } + + /** Loads fetch specifications from the .fspec file, + * if one exists. + */ + private void loadFetchSpecifications() { + if (_loadedFetchSpecs) + return; + _loadedFetchSpecs = true; + if (model().path() == null) + return; + File f = new File(model().path()); + //Read the fetch specification file, if it exists + f = new File(f, name() + ".fspec"); + if (!f.exists()) + return; + NSDictionary fdict = null; + String x = null; + try { + FileInputStream fin = new FileInputStream(f); + byte[] b = new byte[fin.available()]; + fin.read(b); + fin.close(); + x = new String(b); + } catch (IOException ex) { + throw new IllegalArgumentException("Cannot read file " + f); + } + fdict = NSPropertyListSerialization.dictionaryForString(x); + if (fdict == null) + throw new IllegalArgumentException("Cannot read dictionary from " + f); + NSArray keys = fdict.allKeys(); + + //Unarchive the fetch specification + EOKeyValueUnarchiver unarch = new EOKeyValueUnarchiver(fdict); + for (int i = 0; i < keys.count(); i++) { + String k = (String)keys.objectAtIndex(i); + EOFetchSpecification fs = (EOFetchSpecification)unarch.decodeObjectForKey(k); + if (fs != null) + _fetchSpecs.setObjectForKey(fs, k); + } + } + + public NSArray attributes() { + return _attributes.allValues(); + } + + public EOAttribute attributeNamed(String name) { + return (EOAttribute)_attributes.objectForKey(name); + } + + public NSArray flattenedAttributes() { + return null; + } + + public void setClassName(String name) { + _className = name; + } + public String className() { + return _className; + } + + public void setName(String name) { + _name = name; + } + public String name() { + return _name; + } + + public void setExternalName(String name) { + _externalName = name; + } + public String externalName() { + return _externalName; + } + + public void addRelationship(EORelationship rel) { + if (rel.name() == null) + throw new IllegalArgumentException("Cannot add an unnamed relationship to an entity."); + if (_relations.objectForKey(rel.name()) != null) + throw new IllegalArgumentException("Entity " + name() + " already has a relationship named " + rel.name()); + if (_attributes.objectForKey(rel.name()) != null) + throw new IllegalArgumentException("Entity " + name() + " has an attribute named " + rel.name()); + _relations.setObjectForKey(rel, rel.name()); + _classProperties.addObject(rel); + _classPropertyNames.addObject(rel.name()); + if (rel.isToMany()) + _classPropertyManyRelationships.addObject(rel); + else + _classPropertyOneRelationships.addObject(rel); + } + + public void removeRelationship(EORelationship rel) { + _relations.removeObjectForKey(rel.name()); + _classProperties.removeObject(rel); + _classPropertyNames.removeObject(rel.name()); + _classPropertyManyRelationships.removeObject(rel); + _classPropertyOneRelationships.removeObject(rel); + } + + /** Returns the relationships from this entity to other entities. + * @return An array of the relationships of this entity. + */ + public NSArray relationships() { + return _relations.allValues(); + } + + public EORelationship relationshipNamed(String name) { + return (EORelationship)_relations.objectForKey(name); + } + public EOModel model() { + return _model; + } + + public void setPrimaryKeyAtributes(NSArray pk) { + _pkAttributes = pk; + } + public NSArray primaryKeyAttributes() { + return _pkAttributes; + } + + public NSArray primaryKeyAttributeNames() { + if (_pkAttributeNames.count() != _pkAttributes.count()) { + NSMutableArray arr = new NSMutableArray(); + for (int i = 0; i < _pkAttributes.count(); i++) { + EOAttribute a = (EOAttribute)_pkAttributes.objectAtIndex(i); + arr.addObject(a.name()); + } + _pkAttributeNames = new NSArray(arr); + } + return _pkAttributeNames; + } + + public boolean hasSimplePrimaryKey() { + return _pkAttributes.count() == 1; + } + + public boolean isValidPrimaryKeyAttribute(EOAttribute attr) { + return !attr.allowsNull(); + } + + public void setAttributesUsedForLocking(NSArray value) { + _lockingAttributes.removeAllObjects(); + _lockingAttributes.addObjectsFromArray(value); + } + public NSArray attributesUsedForLocking() { + return new NSArray(_lockingAttributes); + } + + public void setClassProperties(NSArray value) { + _classProperties.removeAllObjects(); + _classProperties.addObjectsFromArray(value); + _classPropertyNames.removeAllObjects(); + _classPropertyAttributes.removeAllObjects(); + _classPropertyOneRelationships.removeAllObjects(); + _classPropertyManyRelationships.removeAllObjects(); + for (int i = 0; i < value.count(); i++) { + EOProperty o = (EOProperty)value.objectAtIndex(i); + _classPropertyNames.addObject(o.name()); + if (o instanceof EOAttribute) { + _classPropertyAttributes.addObject(o); + } else if (o instanceof EORelationship) { + if (((EORelationship)o).isToMany()) + _classPropertyManyRelationships.addObject(o); + else + _classPropertyOneRelationships.addObject(o); + } + } + } + public NSArray classProperties() { + if (_classProperties == null) { + if (_classPropertyNames == null) + return NSArray.EmptyArray; + else { + NSMutableArray props = new NSMutableArray(); + NSMutableArray atribs = new NSMutableArray(); + NSMutableArray ones = new NSMutableArray(); + NSMutableArray manies = new NSMutableArray(); + for (int i = 0; i < _classPropertyNames.count(); i++) { + String name = (String)_classPropertyNames.objectAtIndex(i); + EOAttribute a = attributeNamed(name); + EORelationship r = relationshipNamed(name); + if (a != null) { + props.addObject(a); + atribs.addObject(a); + } else if (r != null) { + props.addObject(r); + if (r.isToMany()) + manies.addObject(r); + else + ones.addObject(r); + } else + throw new IllegalArgumentException("Cannot find attribute or relationship named " + name); + } + _classProperties = props; + _classPropertyAttributes = atribs; + _classPropertyOneRelationships = ones; + _classPropertyManyRelationships = manies; + } + } + return _classProperties; + } + + public NSArray classPropertyNames() { + return _classPropertyNames; + } + + public NSArray classPropertyAttributeNames() { + if (_classPropertyAttributes == null) + return NSArray.EmptyArray; + NSMutableArray arr = new NSMutableArray(_classPropertyAttributes.count()); + for (int i = 0 ; i < _classPropertyAttributes.count(); i++) { + EOAttribute a = (EOAttribute)_classPropertyAttributes.objectAtIndex(i); + arr.addObject(a.name()); + } + return arr; + } + + public NSArray classPropertyToManyRelationshipNames() { + if (_classPropertyManyRelationships == null) + return NSArray.EmptyArray; + NSMutableArray arr = new NSMutableArray(_classPropertyManyRelationships.count()); + for (int i = 0 ; i < _classPropertyManyRelationships.count(); i++) { + EOAttribute a = (EOAttribute)_classPropertyManyRelationships.objectAtIndex(i); + arr.addObject(a.name()); + } + return arr; + } + + public NSArray classPropertyToOneRelationshipNames() { + if (_classPropertyOneRelationships == null) + return NSArray.EmptyArray; + NSMutableArray arr = new NSMutableArray(_classPropertyOneRelationships.count()); + for (int i = 0 ; i < _classPropertyOneRelationships.count(); i++) { + EOAttribute a = (EOAttribute)_classPropertyOneRelationships.objectAtIndex(i); + arr.addObject(a.name()); + } + return arr; + } + + public void setIsAbstractEntity(boolean flag) { + _isAbstract = flag; + } + public boolean isAbstractEntity() { + return _isAbstract; + } + + public void setReadOnly(boolean flag) { + _isReadOnly = flag; + } + public boolean isReadOnly() { + return _isReadOnly; + } + + public void setStoredProcedure(EOStoredProcedure proc, String operation) { + } + public EOStoredProcedure storedProcedureForOperation(String operation) { + return null; + } + + public NSArray subEntities() { + return null; + } + + public NSArray attributesToFetch() { + return attributes(); + } + + public NSArray externalModelsReferenced() { + return null; + } + + public EOClassDescription classDescriptionForInstances() { + EOClassDescription cd = EOClassDescription.classDescriptionForEntityName(name()); + if (cd == null) { + cd = new EOEntityClassDescription(this); + Class cl = null; + try { + cl = Class.forName(className()); + } catch (ClassNotFoundException ex) { + cl = EOGenericRecord.class; + } + EOClassDescription.registerClassDescription(cd, cl); + } + return cd; + } + + /** + * Creates a global ID for a row. The row must have values + * for all the primary key attributes. + * @param row A raw row for this entity. + * @return A key global ID to identify this row. + */ + public EOGlobalID globalIDForRow(Map row) { + NSArray pknames = primaryKeyAttributeNames(); + EOKeyGlobalID gid = null; + if (pknames.count() == 1 && row.get(pknames.objectAtIndex(0)) instanceof Number) { + Number n = (Number)row.get(pknames.objectAtIndex(0)); + gid = new EOIntegralKeyGlobalID(name(), n); + } else { + Object[] vals = new Object[pknames.count()]; + for (int i = 0; i < pknames.count(); i++) { + Object v = row.get(pknames.objectAtIndex(i)); + vals[i] = v; + } + gid = new EOVectorKeyGlobalID(name(), vals); + } + return gid; + } + + /** + * Returns a dictionary with the primary key values contained in + * the global id. + * @param gid A Key global ID. + * @return A dictionary with the primary key values for gid. + */ + public NSDictionary primaryKeyForGlobalID(EOGlobalID gid) { + if (!(gid instanceof EOKeyGlobalID)) + return null; + Object[] vals = ((EOKeyGlobalID)gid).keyValues(); + NSArray pknames = primaryKeyAttributeNames(); + return new NSDictionary(vals, pknames.toArray()); + } + + public EOQualifier qualifierForPrimaryKey(Map pkey) { + NSArray pknames = primaryKeyAttributeNames(); + EOQualifier q = null; + NSMutableArray subq = new NSMutableArray(pknames.count()); + for (int i = 0; i < pknames.count(); i++) { + String key = (String)pknames.objectAtIndex(i); + Object v = pkey.get(key); + if (v == null || v == NSKeyValueCoding.NullValue) + throw new IllegalArgumentException("Primary key with null values."); + subq.addObject(new EOKeyValueQualifier(key, EOQualifier.QualifierOperatorEqual, v)); + } + if (subq.count() == 1) + q = (EOQualifier)subq.objectAtIndex(0); + else + q = new EOAndQualifier(subq); + return q; + } + + public void setUserInfo(NSDictionary value) { + _userInfo = value; + } + public NSDictionary userInfo() { + return _userInfo; + } + + public void awakeWithPropertyList(NSDictionary plist) { + } + + public void encodeIntoPropertyList(NSMutableDictionary dict) { + dict.setObjectForKey(name(), "name"); + dict.setObjectForKey(externalName(), "externalName"); + dict.setObjectForKey(className(), "className"); + + //Encode attributes + NSMutableArray arr = new NSMutableArray(_attributes.allValues()); + for (int i = 0; i < _attributes.count(); i++) { + EOAttribute a = (EOAttribute)arr.objectAtIndex(i); + NSMutableDictionary d = new NSMutableDictionary(); + a.encodeIntoPropertyList(d); + arr.replaceObjectAtIndex(i, d); + } + dict.setObjectForKey(arr, "attributes"); + //Encode relationships + if (_relations.count() > 0) { + arr = new NSMutableArray(_relations.allValues()); + for (int i = 0; i < _relations.count(); i++) { + EORelationship r = (EORelationship)arr.objectAtIndex(i); + NSMutableDictionary d = new NSMutableDictionary(); + r.encodeIntoPropertyList(d); + arr.replaceObjectAtIndex(i, d); + } + dict.setObjectForKey(arr, "relationships"); + } + + //Fetch specifications + NSMutableDictionary d = new NSMutableDictionary(); + loadFetchSpecifications(); + java.util.Enumeration enumeration = _fetchSpecs.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String k = (String)enumeration.nextElement(); + EOFetchSpecification f = (EOFetchSpecification)_fetchSpecs.objectForKey(k); + EOKeyValueArchiver arch = new EOKeyValueArchiver(); + f.encodeWithKeyValueArchiver(arch); + d.setObjectForKey(arch.dictionary(), k); + } + dict.setObjectForKey(d, "fetchSpecificationDictionary"); + + //Other information + dict.setObjectForKey(_classPropertyNames, "classProperties"); + dict.setObjectForKey(_pkAttributeNames, "primaryKeyAttributes"); + arr = new NSMutableArray(_lockingAttributes); + for (int i = 0; i < arr.count(); i++) + arr.replaceObjectAtIndex(i, ((EOAttribute)arr.objectAtIndex(i)).name()); + dict.setObjectForKey(arr, "attributesUsedForLocking"); + if (_userInfo != null && _userInfo.count() > 0 ) + dict.setObjectForKey(_userInfo, "userInfo"); + if (_internalInfo != null && _internalInfo.count() > 0 ) + dict.setObjectForKey(_internalInfo, "internalInfo"); + } + + public EOAttribute _attributeForPath(String path) { + NSArray comps = NSArray.componentsSeparatedByString(path, "."); + if (comps.count() < 2) + return null; + EORelationship r = null; + EOEntity e = this; + for (int i = 0; i < comps.count()-1; i++) { + String name = (String)comps.objectAtIndex(i); + r = e.relationshipNamed(name); + if (r == null) + return null; + e = r.destinationEntity(); + } + return e.attributeNamed((String)comps.lastObject()); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.8 2003/08/19 19:47:58 chochos + * added some methods to handle EOGlobalIDs and primary keys. + * + * Revision 1.7 2003/08/14 02:13:56 chochos + * implement and use _attributeForPath() + * + * Revision 1.6 2003/08/11 19:38:27 chochos + * Can now read from a file and re-write to another file. + * + * Revision 1.5 2003/08/11 18:19:33 chochos + * encoding into property list seems to work fine now. + * + * Revision 1.4 2003/08/09 01:39:04 chochos + * better handling of class properties; use EOKeyValueArchiving to encode/decode fetch specifications; implement EOPropertyListEncoding + * + * Revision 1.3 2003/08/08 05:52:21 chochos + * gets the class description for the entity. + * + * Revision 1.2 2003/08/08 02:14:20 chochos + * now it can read stored procedures. + * + * Revision 1.1 2003/08/07 02:38:33 chochos + * implementation of EOEntity. What works for now is reading an entity from file. + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntityClassDescription.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntityClassDescription.java new file mode 100644 index 0000000..a235c99 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOEntityClassDescription.java @@ -0,0 +1,185 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.access; + +import net.wotonomy.control.EOClassDescription; +import net.wotonomy.control.EOEditingContext; +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOGenericRecord; +import net.wotonomy.control.EOGlobalID; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; + +/** +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOEntityClassDescription extends EOClassDescription { + + protected EOEntity _entity; + + public EOEntityClassDescription() { + super(); + } + + public EOEntityClassDescription(EOEntity entity) { + this(); + _entity = entity; + } + + public NSArray allAttributeKeys() { + NSArray arr = entity().attributes(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EOAttribute atrib = (EOAttribute)arr.objectAtIndex(i); + a.addObject(atrib); + } + return a; + } + + public NSArray allPropertyKeys() { + return entity().classPropertyNames(); + } + + public NSArray allToManyRelationshipKeys() { + NSArray arr = entity().relationships(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EORelationship r = (EORelationship)arr.objectAtIndex(i); + if (r.isToMany()) + a.addObject(r); + } + return a; + } + + public NSArray allToOneRelationshipKeys() { + NSArray arr = entity().relationships(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EORelationship r = (EORelationship)arr.objectAtIndex(i); + if (!r.isToMany()) + a.addObject(r); + } + return a; + } + + /** Returns all attributes that correspond to columns + * in a database table. + */ + public NSArray attributeKeys() { + NSArray arr = entity().attributes(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EOAttribute atrib = (EOAttribute)arr.objectAtIndex(i); + if (!atrib.isDerived()) + a.addObject(atrib); + } + return a; + } + + public EOClassDescription classDescriptionForDestinationKey(String key) { + EORelationship r = entity().relationshipNamed(key); + if (r == null) + return null; + return r.destinationEntity().classDescriptionForInstances(); + } + + public NSArray clientAttributeKeys() { + return null; + } + + public NSArray clientToManyRelationshipKeys() { + return null; + } + + public NSArray clientToOneRelationshipKeys() { + return null; + } + + public EOEntity entity() { + return _entity; + } + + public String entityName() { + return _entity.name(); + } + + public EOFetchSpecification fetchSpecificationNamed(String name) { + return entity().fetchSpecificationNamed(name); + } + + public NSArray toManyRelationshipKeys() { + NSArray arr = entity().relationships(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EORelationship r = (EORelationship)arr.objectAtIndex(i); + if (r.isToMany() && !r.isFlattened()) + a.addObject(r); + } + return a; + } + + public NSArray toOneRelationshipKeys() { + NSArray arr = entity().relationships(); + NSMutableArray a = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + EORelationship r = (EORelationship)arr.objectAtIndex(i); + if (!r.isToMany() && !r.isFlattened()) + a.addObject(r); + } + return a; + } + + public Object createInstanceWithEditingContext(EOEditingContext ec, EOGlobalID gid) { + if (theClass == null) { + try { + theClass = Class.forName(entity().className()); + } catch (ClassNotFoundException ex) { + if (entity().className().equals("net.wotonomy.control.EOGenericRecord")) + throw new IllegalArgumentException("Cannot find class " + entity().className()); + theClass = EOGenericRecord.class; + } + } + return super.createInstanceWithEditingContext(ec, gid); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2003/08/19 01:55:54 chochos + * the behavior is now more consistent with its Apple counterpart. + * + * Revision 1.3 2003/08/09 01:40:31 chochos + * use EOClassDescription's methods to get a destination entity's class description. + * + * Revision 1.2 2003/08/08 05:51:59 chochos + * createInstanceWithEditingContext now works. It sets theClass with the class from the entity className() before calling super. + * + * Revision 1.1 2003/08/08 00:36:19 chochos + * concrete implementation of EOClassDescription that uses an EOEntity + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOGeneralAdaptorException.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOGeneralAdaptorException.java new file mode 100644 index 0000000..6c48a3f --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOGeneralAdaptorException.java @@ -0,0 +1,62 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSDictionary; + +/** +* Generic exception thrown by wotonomy.access. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOGeneralAdaptorException extends RuntimeException implements java.io.Serializable { + + private NSDictionary _userInfo; + + public EOGeneralAdaptorException(String msg) { + super(msg); + } + + public EOGeneralAdaptorException(String name, NSDictionary userInfo) { + super(name); + _userInfo = userInfo; + } + + public EOGeneralAdaptorException(String selectorName, String className, String msg) { + super(msg); + _userInfo = new NSDictionary( + new Object[]{ selectorName, className }, + new Object[]{ "selectorName", "className" }); + } + + public NSDictionary userInfo() { + return _userInfo; + } + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/13 00:41:50 chochos + * general adaptor exception + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOJoin.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOJoin.java new file mode 100644 index 0000000..693a7d0 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOJoin.java @@ -0,0 +1,56 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +/** +* An EOJoin represents a connection between two attributes in +* different entities. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOJoin { + + protected EOAttribute _source; + protected EOAttribute _destination; + + public EOJoin(EOAttribute src, EOAttribute dst) { + super(); + _source = src; + _destination = dst; + } + + public EOAttribute sourceAttribute() { + return _source; + } + + public EOAttribute destinationAttribute() { + return _destination; + } + + /* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/07 02:41:04 chochos + * these don't do much for now. + * + */ +} diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModel.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModel.java new file mode 100644 index 0000000..46fc8d0 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModel.java @@ -0,0 +1,396 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Enumeration; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSPropertyListSerialization; + +/** +* An EOModel is a set of entities and stored procedures, along with a connection +* dictionary to connect to a database. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOModel { + + protected static final String IDX_NAME = "index.eomodeld"; + + //This array contains dictionaries with "className" and "name" + protected NSMutableArray _entities = new NSMutableArray(); + protected NSMutableDictionary _entitiesByName = new NSMutableDictionary(); + protected NSMutableDictionary _entitiesByClass = new NSMutableDictionary(); + protected NSDictionary _connectionDictionary = NSDictionary.EmptyDictionary; + protected String _adaptorName = "JDBC"; + protected NSMutableDictionary _prototypesByName = new NSMutableDictionary(); + protected NSMutableDictionary _storedProcedures = new NSMutableDictionary(); + protected NSMutableArray _storedProcedureNames = new NSMutableArray(); + protected NSDictionary _userInfo = NSDictionary.EmptyDictionary; + protected String _name; + protected String _path; + protected NSDictionary _internalInfo = NSDictionary.EmptyDictionary; + protected EOModelGroup _group; + + public EOModel() { + super(); + } + + public EOModel(NSDictionary dict, Object o) { + this(); + } + + public EOModel(String path) { + this(); + File f = new File(path); + _path = f.getAbsolutePath(); + if (!(f.exists() && f.isDirectory())) + throw new IllegalArgumentException("The path is invalid (" + f + ")"); + _name = f.getName(); + f = new File(f, "index.eomodeld"); + if (!(f.exists() && f.isFile())) + throw new IllegalArgumentException("Cannot find " + f); + String x = null; + try { + FileInputStream in = new FileInputStream(f); + byte[] b = new byte[in.available()]; + in.read(b); + in.close(); + x = new String(b); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot read index.eomodeld"); + } + NSDictionary d = NSPropertyListSerialization.dictionaryForString(x); + String version = (String)d.objectForKey("EOModelVersion"); + if (version == null || !version.startsWith("2.")) + throw new IllegalArgumentException("Invalid eomodel version: " + version); + setAdaptorName((String)d.objectForKey("adaptorName")); + setConnectionDictionary((NSDictionary)d.objectForKey("connectionDictionary")); + _entities = ((NSArray)d.objectForKey("entities")).mutableClone(); + if (d.objectForKey("storedProcedures") != null) + _storedProcedureNames.addObjectsFromArray((NSArray)d.objectForKey("storedProcedures")); + if (d.objectForKey("internalInfo") != null) + _internalInfo = (NSDictionary)d.objectForKey("internalInfo"); + if (d.objectForKey("userInfo") != null) + _userInfo = (NSDictionary)d.objectForKey("userInfo"); + entityNamed("EOPrototypes"); + } + + public void setConnectionDictionary(NSDictionary dict) { + _connectionDictionary = dict; + } + public NSDictionary connectionDictionary() { + return _connectionDictionary; + } + + public void addEntity(EOEntity ent) { + _entitiesByName.setObjectForKey(ent, ent.name()); + _entitiesByClass.setObjectForKey(ent, ent.className()); + } + + public void removeEntity(EOEntity ent) { + _entitiesByName.removeObjectForKey(ent.name()); + _entitiesByClass.removeObjectForKey(ent.className()); + } + + public void addStoredProcedure(EOStoredProcedure proc) { + if (!_storedProcedureNames.containsObject(proc)) + _storedProcedureNames.addObject(proc); + _storedProcedures.setObjectForKey(proc, proc.name()); + } + + public void removeStoredProcedure(EOStoredProcedure proc) { + _storedProcedureNames.removeObject(proc.name()); + _storedProcedures.removeObjectForKey(proc.name()); + } + + public EOStoredProcedure storedProcedureNamed(String name) { + EOStoredProcedure proc = (EOStoredProcedure)_storedProcedures.objectForKey(name); + //if we can't find it, check if we should load it + if (proc == null && _path != null && _storedProcedureNames.containsObject(name)) { + //try to read it from file + File f = new File(new File(_path), name + ".storedProcedure"); + if (!f.exists()) + return null; + String sdict = null; + try { + FileInputStream fin = new FileInputStream(f); + byte[] b = new byte[fin.available()]; + fin.read(b); + fin.close(); + sdict = new String(b); + } catch (IOException ex) { + return null; + } + NSDictionary plist = NSPropertyListSerialization.dictionaryForString(sdict); + if (plist == null) + throw new IllegalArgumentException("File " + f + " does not contain a valid stored procedure property list."); + proc = new EOStoredProcedure(plist, this); + //add it to our collection + _storedProcedures.setObjectForKey(proc, proc.name()); + } + return proc; + } + + public NSArray storedProcedures() { + if (_storedProcedures.count() < _storedProcedureNames.count()) { + for (int i=0; i<_storedProcedureNames.count(); i++) + storedProcedureNamed((String)_storedProcedureNames.objectAtIndex(i)); + } + return _storedProcedures.allValues(); + } + + public NSArray storedProcedureNames() { + return _storedProcedures.allKeys(); + } + + public void setAdaptorName(String value) { + _adaptorName = value; + } + public String adaptorName() { + return _adaptorName; + } + + public NSArray entities() { + if (_entitiesByName.count() >= _entities.count()) + return _entitiesByName.allValues(); + NSMutableArray es = new NSMutableArray(); + for (int i = 0; i < _entities.count(); i++) { + NSDictionary d = (NSDictionary)_entities.objectAtIndex(i); + es.addObject(entityNamed((String)d.objectForKey("name"))); + } + return es; + } + + public NSArray entityNames() { + if (_entitiesByName.count() >= _entities.count()) + return _entitiesByName.allKeys(); + NSMutableArray names = new NSMutableArray(); + for (int i = 0; i < _entities.count(); i++) { + NSDictionary d = (NSDictionary)_entities.objectAtIndex(i); + names.addObject(d.objectForKey("name")); + } + return names; + } + + public EOEntity entityNamed(String name) { + EOEntity e = (EOEntity)_entitiesByName.objectForKey(name); + if (e == null && path() != null) { + boolean exists = false; + for (int i = 0; i < _entities.count(); i++) { + NSDictionary d = (NSDictionary)_entities.objectAtIndex(i); + if (d.objectForKey("name").equals(name)) + exists = true; + } + if (!exists) + return null; + File f = new File(new File(path()), name + ".plist"); + if (!(f.exists() && f.isFile())) + throw new IllegalArgumentException("Cannot find " + name + ".plist"); + String s = null; + try { + FileInputStream fin = new FileInputStream(f); + byte[] b = new byte[fin.available()]; + fin.read(b); + fin.close(); + s = new String(b); + } catch (IOException ex) { + throw new IllegalArgumentException("Cannot read " + f); + } + NSDictionary d = NSPropertyListSerialization.dictionaryForString(s); + if (d == null) + throw new IllegalArgumentException("Cannot parse dictionary for " + f); + e = new EOEntity(d, this); + _entitiesByName.setObjectForKey(e, e.name()); + } + return e; + } + + public String name() { + return _name; + } + + public String path() { + return _path; + } + + public void setModelGroup(EOModelGroup group) { + _group = group; + } + public EOModelGroup modelGroup() { + return _group; + } + + public void setUserInfo(NSDictionary dict) { + _userInfo = dict; + } + + public NSDictionary userInfo() { + return _userInfo; + } + + public void write() { + } + + public void writeToFile(String path) { + if (!path.endsWith(".eomodeld")) + path += ".eomodeld"; + File f = new File(path); + if (f.exists()) { + if (f.isDirectory()) { + File[] kids = f.listFiles(); + for (int i = 0; i < kids.length; i++) { + String fname = kids[i].getName(); + if (kids[i].isFile() && (fname.endsWith(".plist") || fname.endsWith(".eomodeld") || fname.endsWith(".fspec") || fname.endsWith(".storedProcedure"))) + kids[i].delete(); + } + } else + f.delete(); + } else + f.mkdirs(); + File kid = new File(f, "index.eomodeld"); + + //encode the index file + NSMutableDictionary d = new NSMutableDictionary(); + d.setObjectForKey(_adaptorName, "adaptorName"); + d.setObjectForKey("2.1", "EOModelVersion"); + d.setObjectForKey(_connectionDictionary, "connectionDictionary"); + if (_internalInfo != null && _internalInfo.count() > 0) + d.setObjectForKey(_internalInfo, "internalInfo"); + if (_userInfo != null && _userInfo.count() > 0) + d.setObjectForKey(_userInfo, "userInfo"); + if (_storedProcedureNames.count() > 0) + d.setObjectForKey(_storedProcedureNames, "storedProcedures"); + + //encode the entity list + entities(); + storedProcedures(); + NSMutableArray arr = new NSMutableArray(_entitiesByName.count()); + Enumeration enumeration = _entitiesByName.keyEnumerator(); + NSMutableDictionary md = new NSMutableDictionary(); + while (enumeration.hasMoreElements()) { + String key = (String)enumeration.nextElement(); + EOEntity ent = (EOEntity)_entitiesByName.objectForKey(key); + md.removeAllObjects(); + ent.encodeIntoPropertyList(md); + File plist; + NSDictionary fetchSpecs = (NSDictionary)md.objectForKey("fetchSpecificationDictionary"); + if (fetchSpecs != null) { + if (fetchSpecs.count() > 0) { + plist = new File(f, key + ".fspec"); + String ps = NSPropertyListSerialization.stringForPropertyList(md.objectForKey("fetchSpecificationDictionary")); + try { + PrintStream stream = new PrintStream(new FileOutputStream(plist)); + stream.println(ps); + stream.close(); + } catch (IOException ex) { + } + } + md.removeObjectForKey("fetchSpecificationDictionary"); + } + plist = new File(f, key + ".plist"); + String ps = NSPropertyListSerialization.stringForPropertyList(md); + try { + PrintStream stream = new PrintStream(new FileOutputStream(plist)); + stream.println(ps); + stream.close(); + } catch (IOException ex) { + } + //add to the entity list for the index + arr.addObject(new NSDictionary( + new Object[]{ ent.name(), ent.className() }, + new Object[]{ "name", "className" })); + } + d.setObjectForKey(arr, "entities"); + + //write the index file + String s = NSPropertyListSerialization.stringForPropertyList(d); + try { + PrintStream stream = new PrintStream(new FileOutputStream(kid)); + stream.println(s); + stream.close(); + } catch (IOException ex) { + throw new RuntimeException("Cannot write index.eomodeld"); + } + + //write the stored procedures + for (int i = 0; i < _storedProcedureNames.count(); i++) { + EOStoredProcedure proc = (EOStoredProcedure)_storedProcedures.objectForKey(_storedProcedureNames.objectAtIndex(i)); + kid = new File(f, proc.name() + ".storedProcedure"); + d.removeAllObjects(); + proc.encodeIntoPropertyList(d); + s = NSPropertyListSerialization.stringForPropertyList(d); + try { + PrintStream stream = new PrintStream(new FileOutputStream(kid)); + stream.println(s); + stream.close(); + } catch (IOException ex) { + throw new RuntimeException("Cannot write " + f); + } + } + _path = f.getAbsolutePath(); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.8 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.7 2003/08/13 20:46:12 chochos + * entityNamed() only throws if the searched entity is in the entities array and not in a file. + * + * Revision 1.6 2003/08/12 01:45:04 chochos + * added some code to handle prototypes + * + * Revision 1.5 2003/08/11 19:38:27 chochos + * Can now read from a file and re-write to another file. + * + * Revision 1.4 2003/08/11 18:20:08 chochos + * saving to a file seems to work now. + * + * Revision 1.3 2003/08/08 02:16:55 chochos + * can now read stored procedures from file. + * + * Revision 1.2 2003/08/08 00:37:00 chochos + * add stored procedure functionality + * + * Revision 1.1 2003/08/07 02:42:28 chochos + * EOModel can read an .eomodeld file. EOModelGroup doesn't do much for now. + * +*/
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModelGroup.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModelGroup.java new file mode 100644 index 0000000..dbab09d --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOModelGroup.java @@ -0,0 +1,198 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOObjectStoreCoordinator; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; + +/** +* A group of models that connect to the same database. Entities in +* these models can have relationships that point to other entities +* in any model of the same group. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOModelGroup { + + private static EOModelGroup _defaultGroup; + private static EOModelGroup _globalGroup; + protected NSMutableDictionary _models; + + public EOModelGroup() { + super(); + } + + public void addModel(EOModel model) { + if (model.name() == null) + throw new IllegalArgumentException("Cannot add an unnamed model to a group."); + if (_models.objectForKey(model.name()) != null) + throw new IllegalArgumentException("Cannot add model " + model.name() + + " to group because it already contains a model with the same name."); + NSArray ents = model.entityNames(); + for (int i = 0; i < ents.count(); i++) { + String ename = (String)ents.objectAtIndex(i); + if (entityNamed(ename) != null) + throw new IllegalArgumentException("Cannot add model " + model.name() + + " to group because it contains entity named " + ename); + } + _models.setObjectForKey(model, model.name()); + } + + public void removeModel(EOModel model) { + _models.removeObjectForKey(model.name()); + } + + public void addModelWithPath(String path) { + EOModel model = new EOModel(path); + addModel(model); + } + + public void addModelsFromDirectory(String dir) { + } + + public static void setDefaultGroup(EOModelGroup group) { + _defaultGroup = group; + } + public static EOModelGroup defaultGroup() { + if (_defaultGroup == null) { + _defaultGroup = globalModelGroup(); + } + return _defaultGroup; + } + + public static EOModelGroup globalModelGroup() { + if (_globalGroup == null) { + _globalGroup = new EOModelGroup(); + //TODO: read all frameworks and get models from them + } + return _globalGroup; + } + + public EOEntity entityForObject(net.wotonomy.control.EOEnterpriseObject eo) { + return null; + } + + public EOEntity entityNamed(String name) { + java.util.Enumeration enumeration = _models.objectEnumerator(); + while (enumeration.hasMoreElements()) { + EOModel m = (EOModel)enumeration.nextElement(); + if (m.entityNamed(name) != null) + return m.entityNamed(name); + } + return null; + } + + public EOModel modelNamed(String name) { + return (EOModel)_models.objectForKey(name); + } + + public NSArray modelNames() { + return _models.allKeys(); + } + + public NSArray models() { + return _models.allValues(); + } + + public EOModel modelWithPath(String path) { + java.util.Enumeration enumeration = _models.objectEnumerator(); + while (enumeration.hasMoreElements()) { + EOModel m = (EOModel)enumeration.nextElement(); + if (m.path() != null && m.path().equals(path)) + return m; + } + return null; + } + + public EOStoredProcedure storedProcedureNamed(String name) { + java.util.Enumeration enumeration = _models.objectEnumerator(); + while (enumeration.hasMoreElements()) { + EOModel m = (EOModel)enumeration.nextElement(); + if (m.storedProcedureNamed(name) != null) + return m.storedProcedureNamed(name); + } + return null; + } + + public EOFetchSpecification fetchSpecificationNamed(String fetchSpecName, String entityName) { + EOEntity e = entityNamed(entityName); + if (e == null) + return null; + return e.fetchSpecificationNamed(fetchSpecName); + } + + public void loadAllModelObjects() { + java.util.Enumeration enumeration = _models.objectEnumerator(); + while (enumeration.hasMoreElements()) { + EOModel m = (EOModel)enumeration.nextElement(); + //this causes all entities to be loaded + NSArray ents = m.entities(); + for (int i=0; i<ents.count(); i++) { + EOEntity e = (EOEntity)ents.objectAtIndex(i); + //this loads all the fetch specifications + e.fetchSpecificationNamed("whatever"); + } + } + } + + public static void setModelGroupForObjectStoreCoordinator(EOObjectStoreCoordinator coord, EOModelGroup group) { + NSMutableDictionary d = new NSMutableDictionary(coord.userInfo()); + d.setObjectForKey(group, "ModelGroup"); + coord.setUserInfo(d); + } + public static EOModelGroup modelGroupForObjectStoreCoordinator(EOObjectStoreCoordinator coord) { + NSDictionary d = coord.userInfo(); + if (d == null) + return defaultGroup(); + Object g = d.objectForKey("ModelGroup"); + if (g != null && g instanceof EOModelGroup) + return (EOModelGroup)g; + return defaultGroup(); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.3 2003/08/08 00:44:04 chochos + * manage model groups for object store coordinators. + * + * Revision 1.2 2003/08/08 00:36:41 chochos + * add a little more functionality + * + * Revision 1.1 2003/08/07 02:42:28 chochos + * EOModel can read an .eomodeld file. EOModelGroup doesn't do much for now. + * +*/
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOProperty.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOProperty.java new file mode 100644 index 0000000..89f1fea --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOProperty.java @@ -0,0 +1,46 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.access; + +/** +* Abstract superclass of EOAttribute and EORelationship. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public abstract class EOProperty { + + public EOProperty() { + super(); + } + + public abstract String name(); + + public abstract String relationshipPath(); + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/08 06:51:37 chochos + * abstract superclass for relationships and attributes + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOPropertyListEncoding.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOPropertyListEncoding.java new file mode 100644 index 0000000..af456d2 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOPropertyListEncoding.java @@ -0,0 +1,53 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; + +/** +* Implemented by classes that are read from property lists and that +* can be written back to property lists. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public interface EOPropertyListEncoding { + + public abstract void awakeWithPropertyList(NSDictionary plist); + + public abstract void encodeIntoPropertyList(NSMutableDictionary plist); + +} + +/* $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * +/* Revision 1.1 2006/02/16 13:19:57 cgruber +/* Check in all sources in eclipse-friendly maven-enabled packages. +/* +/* Revision 1.1 2003/08/09 01:34:43 chochos +/* an interface to provide property list encoding capabilities +/* + * + */ +
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOQualifierSQLGeneration.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOQualifierSQLGeneration.java new file mode 100644 index 0000000..4627f76 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOQualifierSQLGeneration.java @@ -0,0 +1,241 @@ +/* +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.access; + +import net.wotonomy.control.EOAndQualifier; +import net.wotonomy.control.EOKeyComparisonQualifier; +import net.wotonomy.control.EOKeyValueQualifier; +import net.wotonomy.control.EONotQualifier; +import net.wotonomy.control.EOOrQualifier; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableDictionary; + +/** +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public interface EOQualifierSQLGeneration { + + public EOQualifier qualifierMigratedFromEntityRelationshipPath( + EOEntity entity, String path); + + public EOQualifier schemaBasedQualifierWithRootEntity(EOEntity entity); + + public String sqlStringForSQLExpression(EOSQLExpression expression); + + public abstract class Support { + + private static NSMutableDictionary _classes = new NSMutableDictionary(); + + static { + setSupportForClass(new KeyValueQualifierSupport(), EOKeyValueQualifier.class); + setSupportForClass(new KeyComparisonQualifierSupport(), EOKeyComparisonQualifier.class); + setSupportForClass(new AndQualifierSupport(), EOAndQualifier.class); + setSupportForClass(new OrQualifierSupport(), EOOrQualifier.class); + setSupportForClass(new NotQualifierSupport(), EONotQualifier.class); + } + + public Support() { + super(); + } + + public static void setSupportForClass(Support sup, Class aClass) { + _classes.setObjectForKey(sup, aClass.getName()); + } + public static Support supportForClass(Class aClass) { + return (Support)_classes.objectForKey(aClass.getName()); + } + + public abstract String sqlStringForSQLExpression(EOQualifier q, EOSQLExpression exp); + + public abstract EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier q, EOEntity e); + + public abstract EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier q, EOEntity e, String path); + + } + + public class KeyValueQualifierSupport extends Support { + + public KeyValueQualifierSupport() { + super(); + } + + public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { + EOKeyValueQualifier q = (EOKeyValueQualifier)qualifier; + String sql1 = exp.sqlStringForAttributeNamed(q.key()); + String sql2 = exp.sqlStringForSelector(q.selector(), q.value()); + String sql3 = exp.sqlStringForValue(q.value(), q.key()); + if (q.selector() == EOQualifier.QualifierOperatorCaseInsensitiveLike) + return exp.sqlStringForCaseInsensitiveLike(sql1, sql3); + else if (q.selector() == EOQualifier.QualifierOperatorLike) + sql3 = exp.sqlPatternFromShellPattern(sql3); + return sql1 + sql2 + sql3; + } + + public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier q, EOEntity e) { + return q; + } + + public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier q, EOEntity e, String path) { + return q; + } + + } + + public class KeyComparisonQualifierSupport extends Support { + + public KeyComparisonQualifierSupport() { + super(); + } + + public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { + EOKeyComparisonQualifier q = (EOKeyComparisonQualifier)qualifier; + return exp.sqlStringForAttributeNamed(q.leftKey()) + + exp.sqlStringForSelector(q.selector(), null) + + exp.sqlStringForAttributeNamed(q.rightKey()); + } + + public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier q, EOEntity e) { + return q; + } + + public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier q, EOEntity e, String path) { + return q; + } + + } + + public class NotQualifierSupport extends Support { + + public NotQualifierSupport() { + super(); + } + + public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { + EONotQualifier q = (EONotQualifier)qualifier; + return "NOT (" + EOQualifierSQLGeneration.Support.supportForClass(q.qualifier().getClass()).sqlStringForSQLExpression(q.qualifier(), exp) + ")"; + } + + public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier qualifier, EOEntity e) { + EONotQualifier q = (EONotQualifier)qualifier; + return new EONotQualifier(EOQualifierSQLGeneration.Support.supportForClass(q.qualifier().getClass()).schemaBasedQualifierWithRootEntity(q.qualifier(), e)); + } + + public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier qualifier, EOEntity e, String path) { + EONotQualifier q = (EONotQualifier)qualifier; + return new EONotQualifier(EOQualifierSQLGeneration.Support.supportForClass(q.qualifier().getClass()).qualifierMigratedFromEntityRelationshipPath(q.qualifier(), e, path)); + } + + } + + public class AndQualifierSupport extends Support { + + public AndQualifierSupport() { + super(); + } + + public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { + EOAndQualifier q = (EOAndQualifier)qualifier; + NSArray qus = q.qualifiers(); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < qus.count(); i++) { + EOQualifier sub = (EOQualifier)qus.objectAtIndex(i); + EOQualifierSQLGeneration.Support sup = EOQualifierSQLGeneration.Support.supportForClass(sub.getClass()); + if (sup == null) + throw new IllegalStateException("Cannot find support class for " + sub.getClass().getName()); + buf.append(sup.sqlStringForSQLExpression(sub, exp)); + if (i < qus.count()-1) + buf.append(" AND "); + } + if (qus.count() > 1) { + buf.insert(0, '('); + buf.append(')'); + } + return buf.toString(); + } + + public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier q, EOEntity e) { + return q; + } + + public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier q, EOEntity e, String path) { + return q; + } + + } + + public class OrQualifierSupport extends Support { + + public OrQualifierSupport() { + super(); + } + + public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { + EOOrQualifier q = (EOOrQualifier)qualifier; + NSArray qus = q.qualifiers(); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < qus.count(); i++) { + EOQualifier sub = (EOQualifier)qus.objectAtIndex(i); + EOQualifierSQLGeneration.Support sup = EOQualifierSQLGeneration.Support.supportForClass(sub.getClass()); + if (sup == null) + throw new IllegalStateException("Cannot find support class for " + sub.getClass().getName()); + buf.append(sup.sqlStringForSQLExpression(sub, exp)); + if (i < qus.count()-1) + buf.append(" OR "); + } + if (qus.count() > 1) { + buf.insert(0, '('); + buf.append(')'); + } + return buf.toString(); + } + + public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier q, EOEntity e) { + return q; + } + + public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier q, EOEntity e, String path) { + return q; + } + + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/08/14 02:13:10 chochos + * KeyValueQualifierSupport generates proper SQL + * + * Revision 1.2 2003/08/14 01:05:51 chochos + * added abstract Support inner class, with incomplete implementations for the main qualifiers (not, and, or, key-value, key-comparison) + * + * Revision 1.1 2003/08/12 01:45:49 chochos + * interface to be implemented by qualifiers (or support classes) to indicate they can generate SQL + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EORelationship.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EORelationship.java new file mode 100644 index 0000000..a5a207f --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EORelationship.java @@ -0,0 +1,319 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +/** +* Represents a relationship from one entity to another. Relationships are unidirectional. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EORelationship extends EOProperty implements EOPropertyListEncoding { + + public static final int InnerJoin = 0; + public static final int FullOuterJoin = 1; + public static final int LeftOuterJoin = 2; + public static final int RightOuterJoin = 3; + + protected String _name; + protected int _batchCount; + protected int _deleteRule; + protected int _joinSemantic; + protected EOEntity _destination; + protected EOEntity _entity; + protected NSMutableArray _joins = new NSMutableArray(); + protected boolean _isMandatory; + protected boolean _isToMany; + protected boolean _isFlattened; + protected boolean _knowsIfFlattened; + protected boolean _ownsDestination; + protected boolean _propagatesPrimaryKey; + protected boolean _useBatchFaulting; + protected NSDictionary _userInfo = NSDictionary.EmptyDictionary; + protected NSDictionary _internalInfo = NSDictionary.EmptyDictionary; + protected NSDictionary plist; + protected String _definition; + + public EORelationship() { + super(); + } + + public EORelationship(NSDictionary dict, Object obj) { + super(); + _entity = (EOEntity)obj; + setName((String)dict.objectForKey("name")); + setToMany("Y".equals(dict.objectForKey("isToMany"))); + setPropagatesPrimaryKey("Y".equals(dict.objectForKey("propagatesPrimaryKey"))); + setIsMandatory("Y".equals(dict.objectForKey("isMandatory"))); + setOwnsDestination("Y".equals(dict.objectForKey("ownsDestination"))); + setDefinition((String)dict.objectForKey("definition")); + String delrule = (String)dict.objectForKey("deleteRule"); + if (delrule != null) { + if (delrule.equals("EODeleteRuleCascade")) + setDeleteRule(0); + else if (delrule.equals("EODeleteRuleDeny")) + setDeleteRule(0); + else if (delrule.equals("EODeleteRuleNoAction")) + setDeleteRule(0); + else if (delrule.equals("EODeleteRuleNullify")) + setDeleteRule(0); + } + delrule = (String)dict.objectForKey("joinSemantic"); + if (delrule != null) { + if (delrule.equals("EOInnerJoin")) + setJoinSemantic(InnerJoin); + else if (delrule.equals("EOFullOuterJoin")) + setJoinSemantic(FullOuterJoin); + else if (delrule.equals("EOLeftOuterJoin")) + setJoinSemantic(LeftOuterJoin); + else if (delrule.equals("EORightOuterJoin")) + setJoinSemantic(RightOuterJoin); + } + delrule = (String)dict.objectForKey("batchCount"); + if (delrule != null) + setNumberOfToManyFaultsToBatchFetch(Integer.parseInt(delrule)); + NSDictionary d = (NSDictionary)dict.objectForKey("userInfo"); + if (d != null) + _userInfo = d; + d = (NSDictionary)dict.objectForKey("internalInfo"); + if (d != null) + _internalInfo = d; + plist = dict; + } + + public void setName(String name) { + _name = name; + } + public String name() { + return _name; + } + + public void addJoin(EOJoin join) { + _joins.addObject(join); + } + + public void removeJoin(EOJoin join) { + _joins.removeObject(join); + } + + public EOEntity entity() { + return _entity; + } + + public EOEntity destinationEntity() { + isFlattened(); + if (_destination == null && plist != null) { + EOModel model = _entity.model(); + EOModelGroup group = model.modelGroup(); + String destEntity = (String)plist.objectForKey("destination"); + if (group != null) + _destination = group.entityNamed(destEntity); + else + _destination = model.entityNamed(destEntity); + } + return _destination; + } + + public void setOwnsDestination(boolean flag) { + _ownsDestination = flag; + } + public boolean ownsDestination() { + return _ownsDestination; + } + + public void setToMany(boolean flag) { + _isToMany = flag; + } + public boolean isToMany() { + return _isToMany; + } + + public void setIsMandatory(boolean flag) { + _isMandatory = flag; + } + public boolean isMandatory() { + return _isMandatory; + } + + public void setPropagatesPrimaryKey(boolean flag) { + _propagatesPrimaryKey = flag; + } + public boolean propagatesPrimaryKey() { + return _propagatesPrimaryKey; + } + + public void setDeleteRule(int value) { + _deleteRule = value; + } + public int deleteRule() { + return _deleteRule; + } + + public void setJoinSemantic(int value) { + _joinSemantic = value; + } + public int joinSemantic() { + return _joinSemantic; + } + + public void setNumberOfToManyFaultsToBatchFetch(int count) { + _batchCount = count; + } + public int numberOfToManyFaultsToBatchFetch() { + return _batchCount; + } + + public NSArray joins() { + if (_joins.count() == 0 && plist != null) { + NSArray joins = (NSArray)plist.objectForKey("joins"); + for (int i = 0; i < joins.count(); i++) { + NSDictionary d = (NSDictionary)joins.objectAtIndex(i); + String srcName = (String)d.objectForKey("sourceAttribute"); + String dstName = (String)d.objectForKey("destinationAttribute"); + EOAttribute a1 = _entity.attributeNamed(srcName); + EOAttribute a2 = destinationEntity().attributeNamed(dstName); + EOJoin j = new EOJoin(a1, a2); + addJoin(j); + } + } + return new NSArray(_joins); + } + + public void setDefinition(String def) { + _definition = def; + } + public String definition() { + return _definition; + } + + public boolean isFlattened() { + if (_knowsIfFlattened) + return _isFlattened; + _knowsIfFlattened = true; + if (definition() == null) + return false; + NSArray comps = NSArray.componentsSeparatedByString(definition(), "."); + if (comps.count() < 2) + return false; + EORelationship r = null; + EOEntity e = entity(); + for (int i = 0; i < comps.count(); i++) { + String name = (String)comps.objectAtIndex(i); + r = e.relationshipNamed(name); + if (r == null) + return false; + e = r.destinationEntity(); + } + _destination = e; + _isFlattened = true; + return _isFlattened; + } + + public boolean isMultiHop() { + return false; + } + + public String relationshipPath() { + if (isFlattened()) + return _definition; + return null; + } + + public void setUserInfo(NSDictionary value) { + _userInfo = value; + } + public NSDictionary userInfo() { + return _userInfo; + } + + public void awakeWithPropertyList(NSDictionary plist) { + } + + public void encodeIntoPropertyList(NSMutableDictionary dict) { + dict.setObjectForKey(name(), "name"); + if (destinationEntity() != null && definition() == null) + dict.setObjectForKey(_destination.name(), "destination"); + if (_internalInfo != null && _internalInfo.count() > 0) + dict.setObjectForKey(_internalInfo, "internalInfo"); + if (_userInfo != null && _userInfo.count() > 0) + dict.setObjectForKey(_userInfo, "userInfo"); + if (isToMany()) + dict.setObjectForKey("Y", "isToMany"); + switch (_joinSemantic) { + case InnerJoin: + dict.setObjectForKey("EOInnerJoin", "joinSemantic"); + break; + case FullOuterJoin: + dict.setObjectForKey("EOFullOuterJoin", "joinSemantic"); + break; + case LeftOuterJoin: + dict.setObjectForKey("EOLefOuterJoin", "joinSemantic"); + break; + case RightOuterJoin: + dict.setObjectForKey("EORightOuterJoin", "joinSemantic"); + break; + } + if (_batchCount > 0) + dict.setObjectForKey(new Integer(_batchCount), "batchCount"); + if (definition() != null) + dict.setObjectForKey(definition(), "definition"); + else { + NSMutableArray jarr = new NSMutableArray(joins().count()); + for (int i = 0; i < _joins.count(); i++) { + EOJoin j = (EOJoin)_joins.objectAtIndex(i); + NSDictionary d = new NSDictionary( + new Object[]{ j.sourceAttribute().name(), j.destinationAttribute().name() }, + new Object[]{ "sourceAttribute", "destinationAttribute" }); + jarr.addObject(d); + } + dict.setObjectForKey(jarr, "joins"); + } + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:13 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/08/11 19:38:27 chochos + * Can now read from a file and re-write to another file. + * + * Revision 1.4 2003/08/09 01:35:35 chochos + * implement EOPropertyListEncoding + * + * Revision 1.3 2003/08/08 06:51:53 chochos + * isFlattened() works + * + * Revision 1.2 2003/08/08 02:17:43 chochos + * main accessors are in place. + * + * Revision 1.1 2003/08/07 02:41:30 chochos + * a relationship that for the moment can be created from a property list. + * +*/
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpression.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpression.java new file mode 100644 index 0000000..4cb15e5 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpression.java @@ -0,0 +1,697 @@ +/* +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.access; + +import java.util.Enumeration; + +import net.wotonomy.control.EOAndQualifier; +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOKeyComparisonQualifier; +import net.wotonomy.control.EOKeyValueQualifier; +import net.wotonomy.control.EOOrQualifier; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.control.EOSortOrdering; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSData; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSKeyValueCoding; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.NSTimestamp; +import net.wotonomy.foundation.NSTimestampFormatter; + +/** +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EOSQLExpression { + + public static final String BindVariableAttributeKey = "BindVariableAttribute"; + public static final String BindVariableColumnKey = "BindVariableColumn"; + public static final String BindVariableNameKey = "BindVariableName"; + public static final String BindVariablePlaceHolderKey = "BindVariablePlaceholder"; + public static final String BindVariableValueKey = "BindVariableValue"; + private static int UseBindings; + private static final int _DefaultFormatSQLStringLength = 64; + private static final int _DefaultListStringLength = 256; + private static final int _DefaultOrderByStringLength = 128; + private static final int _DefaultPathLength = 128; + private static final int _DefaultTableListLength = 128; + protected static final char[] _hexChars = new char[]{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + private static final int _ValueLengthLimit = 40; + protected NSMutableDictionary _aliasesByRelationshipPath; + protected NSMutableDictionary _aliasesByEntityName; + protected NSMutableArray _bindings; + protected NSMutableArray _contextStack; + protected static NSTimestampFormatter _defaultDateFormatter; + protected EOEntity _entity; + protected StringBuffer _joinClauseString; + protected StringBuffer _listString; + protected StringBuffer _orderByString; + protected String _statement; + protected String _upperFunctionName; + protected boolean _useAliases = true; + protected static boolean _quoteExternalNames; + protected StringBuffer _valueListString; + protected String _whereClauseString; + + private EOSQLExpression() { + super(); + } + + public EOSQLExpression(EOEntity entity) { + super(); + _entity = entity; + } + + public String _aliasForRelatedAttributeRelationshipPath(EOAttribute a, String path) { + return null; + } + + public String _aliasForRelationshipPath(String path) { + return (String)_aliasesByRelationshipPath.objectForKey(path); + } + + protected NSTimestampFormatter _defaultDateFormatter() { + return _defaultDateFormatter; + } + + protected StringBuffer _listString() { + if (_listString == null) + _listString = new StringBuffer(); + return _listString; + } + + protected StringBuffer _orderByString() { + if (_orderByString == null) + _orderByString = new StringBuffer(); + return _orderByString; + } + + public EOEntity _rootEntityForExpression() { + return _entity; + } + + public void _setEntity(EOEntity value) { + _entity = value; + } + + public String _sqlStringForJoinSemanticMatchSemantic(int semantic,int match) { + return null; + } + + protected String _stringForDate(NSTimestamp timestamp) { + return null; + } + + protected StringBuffer _valueList() { + if (_valueListString == null) + _valueListString = new StringBuffer(); + return _valueListString; + } + + public void addBindVariableDictionary( NSDictionary dict ) { + } + + /** Adds the SQL to create the attribute to the attribute list. + * The appended text is of the form attr_name attr_type allow_null + * @param attr The attribute to create the SQL for. + */ + public void addCreateClauseForAttribute(EOAttribute attr) { + StringBuffer buf = new StringBuffer(attr.columnName()); + buf.append(' '); + buf.append(columnTypeStringForAttribute(attr)); + buf.append(allowsNullClauseForConstraint(attr.allowsNull())); + appendItemToListString(buf.toString(), _listString()); + } + + public void addInsertListAttribute(EOAttribute attr, Object o) { + } + + public void addJoinClause(String left, String right, int semantic) { + String s = assembleJoinClause(left, right, semantic); + if (_joinClauseString == null) + _joinClauseString = new StringBuffer(); + if (_joinClauseString.length() > 0) + _joinClauseString.append(" AND "); + _joinClauseString.append(s); + } + + public void addOrderByAttributeOrdering(EOSortOrdering order) { + String sql = sqlStringForAttributeNamed(order.key()); + if (order.selector().equals(EOSortOrdering.CompareCaseInsensitiveAscending) || order.selector().equals(EOSortOrdering.CompareCaseInsensitiveDescending)) + sql = "UPPER(" + sql + ")"; + if (order.selector().equals(EOSortOrdering.CompareCaseInsensitiveAscending) || order.selector().equals(EOSortOrdering.CompareAscending)) + sql += " ASC"; + else + sql += " DESC"; + appendItemToListString(sql, _orderByString()); + } + + public void addSelectListAttribute(EOAttribute attr) { + appendItemToListString(formatSQLString(sqlStringForAttribute(attr), attr.readFormat()), _listString()); + } + + public void addUpdateListAttribute(EOAttribute attr, Object o) { + StringBuffer buf = new StringBuffer(attr.columnName()); + buf.append('='); + buf.append(formatSQLString(formatValueForAttribute(o, attr), attr.writeFormat())); + appendItemToListString(buf.toString(), _listString()); + } + + public NSMutableDictionary aliasesByRelationshipPath() { + if (_aliasesByRelationshipPath == null) { + _aliasesByRelationshipPath = new NSMutableDictionary(); + if (!_useAliases) + return _aliasesByRelationshipPath; + _aliasesByRelationshipPath.setObjectForKey("t0", ""); + } + return _aliasesByRelationshipPath; + } + + public String allowsNullClauseForConstraint(boolean flag) { + return flag ? "" : " NOT NULL"; + } + + public void appendItemToListString(String item, StringBuffer list) { + if (list.length() > 0) + list.append(", "); + list.append(item); + } + + public String assembleDeleteStatementWithQualifier(EOQualifier q, String tableList, String whereClause) { + String s = "DELETE FROM " + tableList; + if (whereClause != null && whereClause.length() > 0) + s += " WHERE " + whereClause; + return s; + } + + public String assembleInsertStatementWithRow(NSDictionary row, + String tableList, String columnList, String valueList) { + String sql = "INSERT INTO " + tableList; + if (columnList != null) + sql += " (" + columnList + ")"; + sql += " VALUES " + valueList; + return sql; + } + + public String assembleJoinClause(String leftName, String rightName, int semantic) { + String op = "="; + if (semantic == EORelationship.LeftOuterJoin) + op = "*="; + else if (semantic == EORelationship.RightOuterJoin) + op = "=*"; + return leftName + op + rightName; + } + + public String assembleSelectStatementWithAttributes(NSArray attributes, + boolean lock, EOQualifier q, NSArray fetchOrder, String selectString, String columnList, + String tableList, String whereClause, String joinClause, String orderByClause, String lockClause) { + String sql = selectString + " " + columnList + " FROM " + tableList; + if (lockClause != null) + sql += " " + lockClause; + if (whereClause != null || joinClause != null) + sql += " WHERE "; + if (whereClause != null) + sql += whereClause; + if (whereClause != null && joinClause != null) + sql += " AND "; + if (joinClause != null) + sql += joinClause; + if (orderByClause != null) + sql += " ORDER BY " + orderByClause; + return sql; + } + + public String assembleUpdateStatementWithRow(NSDictionary row, EOQualifier q, String tableList, String updateList, String whereClause) { + String s = "UPDATE " + tableList + " SET " + updateList; + if (whereClause != null && whereClause.length() > 0) + s += " WHERE " + whereClause; + return s; + } + + public NSArray bindVariableDictionaries() { + return null; + } + + public abstract NSMutableDictionary bindVariableDictionaryForAttribute(EOAttribute attr, Object o); + + public String columnTypeStringForAttribute(EOAttribute attr) { + String x = attr.externalType(); + if (attr.precision() > 0) { + x += " (" + attr.precision() + "," + attr.scale() + ")"; + } else if (attr.width() > 0) { + x += " (" + attr.width() + ")"; + } + return x; + } + + public EOEntity entity() { + return _entity; + } + + public String externalNameQuoteCharacter() { + return "\""; + } + + public String formatSQLString(String value, String format) { + if (format == null) + return value; + return value; + } + + /** + * Returns the received string wrapped in single quotes, + * with any quotes or escape chars found inside it + * properly escaped. + * @param s The string to format. + */ + public String formatStringValue(String s) { + StringBuffer buf = new StringBuffer(s); + for (int i = buf.length()-1; i >= 0; i--) { + if (buf.charAt(i) == sqlEscapeChar()) { + buf.insert(i, sqlEscapeChar()); + i++; + } + if (buf.charAt(i) == '\'') { + buf.insert(i, sqlEscapeChar()); + i++; + } + } + buf.append('\''); + buf.insert(0, '\''); + return buf.toString(); + } + + public String formatValueForAttribute(Object value, EOAttribute attr) { + if (value == null || value == NSKeyValueCoding.NullValue) + return "NULL"; + if (value instanceof String) + return formatStringValue((String)value); + if (value instanceof Number) + return sqlStringForNumber((Number)value); + //TODO: format timestamps + return value.toString(); + } + + public String joinClauseString() { + if (_joinClauseString == null) + return null; + return _joinClauseString.toString(); + } + + public void joinExpression() { + _joinClauseString = null; + if (_aliasesByEntityName.count() > 1) { + } + } + + public String listString() { + return _listString().toString(); + } + + public String lockClause() { + return ""; + } + + public boolean mustUseBindVariableForAttribute(EOAttribute attr) { + return false; + } + + public String orderByString() { + if (_orderByString == null) + return null; + return _orderByString.toString(); + } + + public void prepareConstraintStatementForRelationship(EORelationship rel, NSArray arr1, NSArray arr2) { + } + + public void prepareDeleteExpressionForQualifier(EOQualifier q) { + String where = null; + setStatement(assembleDeleteStatementWithQualifier(q, _entity.externalName(), where)); + } + + public void prepareInsertExpressionWithRow(NSDictionary row) { + StringBuffer cols = new StringBuffer("("); + StringBuffer values = new StringBuffer("("); + Enumeration enumeration = row.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String key = (String)enumeration.nextElement(); + EOAttribute a = _entity.attributeNamed(key); + cols.append(a.columnName()); + values.append(formatValueForAttribute(row.objectForKey(key), a)); + if (enumeration.hasMoreElements()) { + cols.append(", "); + values.append(", "); + } + } + cols.append(")"); + cols.append(")"); + setStatement(assembleInsertStatementWithRow(row, _entity.externalName(), cols.toString(), values.toString())); + } + + public void prepareSelectExpressionWithAttributes(NSArray atts, boolean lock, EOFetchSpecification fspec) { + _aliasesByRelationshipPath = new NSMutableDictionary(); + _aliasesByEntityName = new NSMutableDictionary(); + EOQualifier q = null; + NSArray order = null; + if (fspec != null) { + q = fspec.qualifier(); + order = fspec.sortOrderings(); + } + //Assemble the column list (this yields the alias list) + for (int i = 0; i < atts.count(); i++) + addSelectListAttribute((EOAttribute)atts.objectAtIndex(i)); + //assemble the where string + if (q != null) { + if (q instanceof EOQualifierSQLGeneration) + setWhereClauseString(sqlStringForQualifier((EOQualifierSQLGeneration)q)); + else { + EOQualifierSQLGeneration.Support sup = EOQualifierSQLGeneration.Support.supportForClass(q.getClass()); + setWhereClauseString(sup.sqlStringForSQLExpression(q, this)); + } + } + //assemble the join string + joinExpression(); + //assemble the order by string + if (order != null && order.count() > 0) { + for (int i = 0; i < order.count(); i++) { + EOSortOrdering so = (EOSortOrdering)order.objectAtIndex(i); + addOrderByAttributeOrdering(so); + } + } + //create the statement + setStatement(assembleSelectStatementWithAttributes(atts, lock, q, order, "SELECT", listString(), + tableListWithRootEntity(_entity), whereClauseString(), joinClauseString(), orderByString(), lockClause())); + } + + /** Build an UPDATE statement with the given information. */ + public void prepareUpdateExpressionWithRow(NSDictionary row, EOQualifier q) { + StringBuffer buf = new StringBuffer(); + Enumeration enumeration = row.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String key = (String)enumeration.nextElement(); + EOAttribute a = _entity.attributeNamed(key); + if (a == null) + throw new EOGeneralAdaptorException("Cannot find attribute named " + key + " in entity " + _entity.name()); + buf.append(a.columnName()); + buf.append('='); + buf.append(formatValueForAttribute(row.objectForKey(key), a)); + if (enumeration.hasMoreElements()) + buf.append(", "); + } + if (q != null) { + setWhereClauseString(sqlStringForQualifier(null)); + } + setStatement(assembleUpdateStatementWithRow(row, q, _entity.externalName(), buf.toString(), whereClauseString())); + } + + public void setStatement(String statement) { + _statement = statement; + } + + public String statement() { + return _statement; + } + + public void setUseAliases(boolean flag) { + _useAliases = flag; + } + + public boolean useAliases() { + return _useAliases; + } + + public void setUseBindVariables(boolean flag) { + } + + public boolean useBindVariables() { + return System.getProperty("EOAdaptorUseBindVariables", "false").equals("true"); + } + + /** @deprecated Check externalNameQuoteCharacter instead. */ + public static void setUseQuotedExternalNames(boolean flag) { + _quoteExternalNames = flag; + } + /** @deprecated Use the instance method externalNameQuoteCharacter instead. */ + public static boolean useQuotedExternalNames() { + return _quoteExternalNames; + } + + public void setWhereClauseString(String clause) { + _whereClauseString = clause; + } + + public String whereClauseString() { + return _whereClauseString; + } + + public boolean shouldUseBindVariableForAttribute(EOAttribute attr) { + return false; + } + + public char sqlEscapeChar() { + return '\\'; + } + + public String sqlPatternFromShellPattern(String pattern) { + return sqlPatternFromShellPatternWithEscapeCharacter(pattern, sqlEscapeChar()); + } + + public String sqlPatternFromShellPatternWithEscapeCharacter(String pattern, char escape) { + StringBuffer buf = new StringBuffer(pattern); + int idx = 0; + //escape all '%' + do { + idx = buf.indexOf("%"); + if (idx == 0) + buf.insert(escape, 0); + else if (idx > 0 && buf.charAt(idx-1) != escape) + buf.insert(escape, idx); + } while (idx >= 0); + //escape all '_' + do { + idx = buf.indexOf("_"); + if (idx == 0) + buf.insert(escape, 0); + else if (idx > 0 && buf.charAt(idx-1) != escape) + buf.insert(escape, idx); + } while (idx >= 0); + //substitute all '*' + do { + idx = buf.indexOf("*"); + if (idx >= 0) + buf.replace(idx, idx+1, "%"); + } while (idx >= 0); + //substitute all '?' + do { + idx = buf.indexOf("?"); + if (idx >= 0) + buf.replace(idx, idx+1, "_"); + } while (idx >= 0); + return buf.toString(); + } + + public String sqlStringForAttribute(EOAttribute attr) { + if (_aliasesByEntityName == null) + _aliasesByEntityName = new NSMutableDictionary(); + String alias = (String)_aliasesByEntityName.objectForKey(attr.entity().name()); + if (alias == null) { + alias = "t" + (_aliasesByEntityName.count() + 1); + _aliasesByEntityName.setObjectForKey(alias, attr.entity().name()); + } + if (useAliases()) + return alias + "." + attr.columnName(); + else + return attr.entity().externalName() + "." + attr.columnName(); + } + + public String sqlStringForAttributeNamed(String name) { + if (name.indexOf('.') > 0) { + return sqlStringForAttribute(_entity._attributeForPath(name)); + } + return sqlStringForAttribute(_entity.attributeNamed(name)); + + } + + /** + * Returns a string representing the path from the first + * relationship in the array to the last one. + * @param path An array of EORelationship objects. + * @return A string consisting of the names of the relationships + * separated by dots. + */ + public String sqlStringForAttributePath(NSArray path) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < path.count(); i++) { + EORelationship rel = (EORelationship)path.objectAtIndex(i); + if (i > 0) + buf.append('.'); + buf.append(rel.name()); + } + return buf.toString(); + } + + public String sqlStringForCaseInsensitiveLike(String key, String value) { + return "LOWER(" + key + ") LIKE LOWER(" + sqlPatternFromShellPattern(value) + ")"; + } + + public String sqlStringForConjoinedQualifiers(NSArray qualifiers) { + EOAndQualifier q = new EOAndQualifier(qualifiers); + return EOQualifierSQLGeneration.Support.supportForClass(EOAndQualifier.class).sqlStringForSQLExpression(q, this); + } + + public String sqlStringForData(NSData data) { + byte[] b = data.bytes(); + char[] c = new char[b.length * 2]; + int pos = 0; + for (int i = 0; i < b.length; i++) { + int x = (b[i] & 0xf0) >> 4; + c[pos++] = _hexChars[x]; + x = (b[i] & 0x0f); + c[pos++] = _hexChars[x]; + } + return new String(c); + } + + public String sqlStringForDisjoinedQualifiers(NSArray qualifiers) { + EOOrQualifier q = new EOOrQualifier(qualifiers); + return EOQualifierSQLGeneration.Support.supportForClass(EOOrQualifier.class).sqlStringForSQLExpression(q, this); + } + + public String sqlStringForKeyComparisonQualifier(EOKeyComparisonQualifier q) { + return EOQualifierSQLGeneration.Support.supportForClass(EOKeyComparisonQualifier.class).sqlStringForSQLExpression(q, this); + } + + public String sqlStringForKeyValueQualifier(EOKeyValueQualifier q) { + return EOQualifierSQLGeneration.Support.supportForClass(EOKeyValueQualifier.class).sqlStringForSQLExpression(q, this); + } + + public String sqlStringForNegatedQualifier(EOQualifier q) { + EOQualifierSQLGeneration.Support sup = EOQualifierSQLGeneration.Support.supportForClass(q.getClass()); + String sql = sup.sqlStringForSQLExpression(q, this); + return "NOT (" + sql + ")"; + } + + public static String sqlStringForNumber(Number number) { + return number.toString(); + } + + public String sqlStringForQualifier(EOQualifierSQLGeneration sql) { + return sql.sqlStringForSQLExpression(this); + } + + public String sqlStringForSchemaObjectName(String name) { + return name; + } + + public String sqlStringForSelector(NSSelector sel, Object value) { + if (sel == EOQualifier.QualifierOperatorEqual) { + if (value == NSKeyValueCoding.NullValue) + return " is "; + return "="; + } else if (sel == EOQualifier.QualifierOperatorNotEqual) { + if (value == NSKeyValueCoding.NullValue) + return " is not "; + return "<>"; + } else if (sel == EOQualifier.QualifierOperatorLessThan) { + return "<"; + } else if (sel == EOQualifier.QualifierOperatorGreaterThan) { + return ">"; + } else if (sel == EOQualifier.QualifierOperatorLessThanOrEqualTo) { + return "<="; + } else if (sel == EOQualifier.QualifierOperatorGreaterThanOrEqualTo) { + return ">="; + } else if (sel == EOQualifier.QualifierOperatorLike || sel == EOQualifier.QualifierOperatorCaseInsensitiveLike) { + return " like "; + } + return sel.name(); + } + + public static String sqlStringForString(String s) { + return s; + } + + public String sqlStringForValue(Object value, String keyPath) { + EOAttribute a = _entity._attributeForPath(keyPath); + return formatValueForAttribute(value, a); + } + + public String tableListWithRootEntity(EOEntity root) { + StringBuffer buf = new StringBuffer(root.externalName()); + if (useAliases()) { + buf.append(" "); + buf.append(_aliasesByEntityName.objectForKey(root.name())); + } + if (_aliasesByEntityName.count() > 0) { + Enumeration enumeration = _aliasesByEntityName.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String key = (String)enumeration.nextElement(); + if (!key.equals(root.name())) { + buf.append(", "); + buf.append(key); + if (useAliases()) + buf.append(_aliasesByEntityName.objectForKey(key)); + } + } + } + return buf.toString(); + } + + public String toString() { + return "<" + getClass().getName() + "> " + statement(); + } + + public String valueList() { + return _valueList().toString(); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.4 2003/08/29 21:14:18 chochos + * fix the algorithm in formatStringValue. + * + * Revision 1.3 2003/08/14 02:12:32 chochos + * implemented use of (simple) qualifiers + * + * Revision 1.2 2003/08/13 22:59:39 chochos + * Can now generate simple one-entity select statements. + * + * Revision 1.1 2003/08/13 01:04:32 chochos + * the SQL generation classes. EOSQLExpression still needs a lot of work, but the factory is pretty much done. + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpressionFactory.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpressionFactory.java new file mode 100644 index 0000000..6bdbffe --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOSQLExpressionFactory.java @@ -0,0 +1,131 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import java.lang.reflect.Constructor; + +import net.wotonomy.control.EOFetchSpecification; +import net.wotonomy.control.EOQualifier; +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOSQLExpressionFactory { + + protected EOAdaptor _adaptor; + protected Class _expressionClass; + private Constructor _instantiator; + + public EOSQLExpressionFactory(EOAdaptor adaptor) { + super(); + _adaptor = adaptor; + _expressionClass = _adaptor.expressionClass(); + if (_expressionClass == null) + throw new IllegalStateException("EOAdaptor " + _adaptor.name() + " returned null for expressionClass()"); + } + + public EOAdaptor adaptor() { + return _adaptor; + } + + /** + * Creates an instance of the adaptor's expression class, + * with entity assigned to it. + * @param entity The entity with which to initialize the expression. + * @return An EOSQLExpression. + */ + public EOSQLExpression createExpression(EOEntity entity) { + EOSQLExpression expr = null; + if (_instantiator == null) { + try { + _instantiator = _expressionClass.getConstructor(new Class[]{ EOEntity.class }); + } catch (Exception ex) { + throw new IllegalArgumentException("The expression class " + _expressionClass.getName() + " has no constructor with an entity parameter."); + } + } + try { + expr = (EOSQLExpression)_instantiator.newInstance(new Object[]{ entity }); + } catch (Exception ex) { + throw new IllegalArgumentException("Cannot create new expression of class " + _expressionClass.getName()); + } + return expr; + } + + public EOSQLExpression expressionForEntity(EOEntity entity) { + return createExpression(entity); + } + + public EOSQLExpression deleteStatementWithQualifier(EOQualifier qualifier, EOEntity entity) { + EOSQLExpression expr = createExpression(entity); + expr.prepareDeleteExpressionForQualifier(qualifier); + return expr; + } + + public EOSQLExpression insertStatementForRow(NSDictionary row, EOEntity entity) { + EOSQLExpression expr = createExpression(entity); + expr.prepareInsertExpressionWithRow(row); + return expr; + } + + public EOSQLExpression selectStatementForAttributes(NSArray atts, boolean lock, EOFetchSpecification fspec, EOEntity entity) { + EOSQLExpression expr = createExpression(entity); + expr.prepareSelectExpressionWithAttributes(atts, lock, fspec); + return expr; + } + + public EOSQLExpression updateStatementForRow(NSDictionary row, EOQualifier qualifier, EOEntity entity) { + EOSQLExpression expr = createExpression(entity); + expr.prepareUpdateExpressionWithRow(row, qualifier); + return expr; + } + + public EOSQLExpression expressionForString(String sql) { + EOSQLExpression expr = null; + try { + expr = (EOSQLExpression)_expressionClass.newInstance(); + } catch (Exception e) { + return null; + } + expr.setStatement(sql); + return expr; + } + + public Class expressionClass() { + return _expressionClass; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/13 01:04:32 chochos + * the SQL generation classes. EOSQLExpression still needs a lot of work, but the factory is pretty much done. + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOStoredProcedure.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOStoredProcedure.java new file mode 100644 index 0000000..f38ec2a --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/access/EOStoredProcedure.java @@ -0,0 +1,141 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.access; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +/** +* Represents a stored procedure in a database. +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOStoredProcedure implements EOPropertyListEncoding { + + protected String _name; + protected String _externalName; + protected EOModel _model; + protected NSArray _arguments = NSArray.EmptyArray; + protected NSDictionary _userInfo = NSDictionary.EmptyDictionary; + protected NSDictionary _internalInfo = NSDictionary.EmptyDictionary; + + /** Creates a stored procedure from a property list. */ + public EOStoredProcedure(NSDictionary dict, Object obj) { + super(); + if (obj instanceof EOModel) + _model = (EOModel)obj; + NSArray a = (NSArray)dict.objectForKey("arguments"); + if (a != null) { + NSMutableArray args = new NSMutableArray(a.count()); + for (int i = 0; i < a.count(); i++) { + NSDictionary ad = (NSDictionary)a.objectAtIndex(i); + EOAttribute arg = new EOAttribute(ad, this); + args.addObject(arg); + } + _arguments = args; + } + setName((String)dict.objectForKey("name")); + setExternalName((String)dict.objectForKey("externalName")); + if (dict.objectForKey("userInfo") != null) + setUserInfo((NSDictionary)dict.objectForKey("userInfo")); + if (dict.objectForKey("internalInfo") != null) + _internalInfo = (NSDictionary)dict.objectForKey("internalInfo"); + } + + public EOStoredProcedure(String withName) { + super(); + setName(withName); + } + + public void setName(String value) { + _name = value; + } + public String name() { + return _name; + } + + public void setExternalName(String value) { + _externalName = value; + } + public String externalName() { + return _externalName; + } + + public void setArguments(NSArray value) { + _arguments = value; + } + public NSArray arguments() { + return _arguments; + } + + public EOModel model() { + return _model; + } + + public void setUserInfo(NSDictionary info) { + _userInfo = info; + } + public NSDictionary userInfo() { + return _userInfo; + } + + public void awakeWithPropertyList(NSDictionary plist) { + } + + public void encodeIntoPropertyList(NSMutableDictionary plist) { + plist.setObjectForKey(name(), "name"); + plist.setObjectForKey(externalName(), "externalName"); + NSMutableArray arr = new NSMutableArray(_arguments.count()); + NSMutableDictionary d = null; + for (int i = 0; i < _arguments.count(); i++) { + EOAttribute a = (EOAttribute)_arguments.objectAtIndex(i); + d = new NSMutableDictionary(); + a.encodeIntoPropertyList(d); + arr.addObject(d); + } + plist.setObjectForKey(arr, "arguments"); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2003/08/11 19:38:27 chochos + * Can now read from a file and re-write to another file. + * + * Revision 1.3 2003/08/09 01:36:32 chochos + * implement EOPropertyListEncoding + * + * Revision 1.2 2003/08/08 02:14:43 chochos + * can create a stored procedure from a property list. main accessors are in place. + * + * Revision 1.1 2003/08/07 02:41:04 chochos + * these don't do much for now. + * +*/
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java new file mode 100644 index 0000000..ddaacf5 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/AbstractObjectStore.java @@ -0,0 +1,762 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSNotification; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.NSNotificationQueue; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* An abstract implementation of object store that +* implements common functionality. Subclasses must +* implement data object creation, initialization, and +* refault logic, as well as logic to commit an editing +* context. +*/ +public abstract class AbstractObjectStore extends EOObjectStore +{ + private NSMutableArray insertedIDsBuffer; + private NSMutableArray updatedIDsBuffer; + private NSMutableArray deletedIDsBuffer; + private NSMutableArray invalidatedIDsBuffer; + + private Map snapshots; + private List exceptionList; + + /** + * Constructs a new instance of this object store. + */ + public AbstractObjectStore() + { + snapshots = new HashMap(); + exceptionList = null; + + insertedIDsBuffer = new NSMutableArray(); + updatedIDsBuffer = new NSMutableArray(); + deletedIDsBuffer = new NSMutableArray(); + invalidatedIDsBuffer = new NSMutableArray(); + + // register for notifications + NSSelector handleNotification = + new NSSelector( "handleNotification", + new Class[] { NSNotification.class } ); + NSNotificationCenter.defaultCenter().addObserver( + this, + handleNotification, + EOClassDescription.ClassDescriptionNeededForEntityNameNotification, + null ); + } + + /** + * This implementation returns an appropriately configured array fault. + */ + public NSArray arrayFaultWithSourceGlobalID( EOGlobalID aGlobalID, + String aRelationship, EOEditingContext aContext ) + { // System.out.println( "arrayFaultWithSourceGlobalID: " + aGlobalID + " : " + aRelationship ); + return new ArrayFault( + aGlobalID, aRelationship, aContext ); + } + + /** + * This implementation returns the actual object for the specified id. + */ + public /*EOEnterpriseObject*/Object faultForGlobalID( EOGlobalID aGlobalID, + EOEditingContext aContext ) + { // System.out.println( "faultForGlobalID: " + aGlobalID ); + return /*(EOEnterpriseObject)*/createInstanceWithEditingContext( aGlobalID, aContext ); + } + + /** + * Returns a fault representing an object of the specified entity type with + * values from the specified dictionary. The fault should belong to the + * specified editing context. + * NOTE: Faults are not supported yet. + */ + public /*EOEnterpriseObject*/Object faultForRawRow( Map aDictionary, String anEntityName, + EOEditingContext aContext ) + { + //TODO: raw rows are not yet supported + throw new WotonomyException( "Faults are not yet supported." ); + } + + /** + * Given a newly instantiated object, this method initializes its + * properties to values appropriate for the specified id. The object + * should belong to the specified editing context. This method is called + * to populate faults. + */ + public void initializeObject( Object anObject, EOGlobalID aGlobalID, + EOEditingContext aContext ) + { //System.out.println( "initializeObject: " + aGlobalID ); + try + { + String entity = entityForGlobalIDOrObject( aGlobalID, null ); + EOClassDescription classDesc = + EOClassDescription.classDescriptionForEntityName( entity ); + if ( classDesc == null ) + { + throw new WotonomyException( "Unknown entity type: " + entity ); + } + + Collection attributes = classDesc.attributeKeys(); + Map data = readFromCache( aGlobalID, attributes ); + String key; + Iterator iterator = attributes.iterator(); + while ( iterator.hasNext() ) + { + key = iterator.next().toString(); + + // write the snapshot's reference into the object + if ( anObject instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)anObject).takeStoredValueForKey( data.get( key ), key ); + } + else + { + EOKeyValueCodingSupport.takeStoredValueForKey( anObject, data.get( key ), key ); + } + + //NOTE: our objects are expected to make a copy + // of their data before it is modified, so it's okay + // to return them our copy of the data: + // we trust that they will not modify it. + } + } + catch ( Exception exc ) + { + exc.printStackTrace(); + } + } + + /** + * Reads the local data snapshot for the specified id. + * If no snapshot exists, a new snapshot is created. + * If the specified keys are not in the snapshot, + * new data is fetched into the snapshot. + * If null is specified, all known keys are returned. + * Will not return null. + * Result will have values for those keys and only + * those keys requested. Missing keys indicate an + * error occurred. + */ + protected Map readFromCache( EOGlobalID aGlobalID, Collection keys ) + { + Map snapshot = (Map) snapshots.get( aGlobalID ); + + // if no snapshot for this id, create an empty one + if ( snapshot == null ) + { + snapshot = new HashMap(); + snapshots.put( aGlobalID, snapshot ); + } + + // if we don't have all the necessary keys + if ( ( keys == null ) || ( ! snapshot.keySet().containsAll( keys ) ) ) + { + // we need to make a server call + try + { + Map data = readObject( aGlobalID, keys ); + + // compare timestamps + Comparable localTimestamp = (Comparable) timestampForData( snapshot ); + // if our local snapshot has an timestamp (new snapshots don't have timestamp) + if ( localTimestamp != null ) + { + Comparable incomingTimestamp = (Comparable) timestampForData( data ); + if ( incomingTimestamp == null ) + { + // not allowed to happen + new RuntimeException( "Server returned data without an timestamp" ).printStackTrace(); + // however, we can just assume it's a newer timestamp and continue + } + + // if timestamps don't match + if ( ( incomingTimestamp == null ) || ( ! incomingTimestamp.equals( localTimestamp ) ) ) + { + // dump our existing snapshot's data + snapshot.clear(); + // queue for a notification on this oid as updated + //TODO: implement this + } + } + + // copy new data into our local snapshot + snapshot.putAll( data ); + } + catch ( Exception exc ) + { + exc.printStackTrace(); + } + } + + // return just the requested keys from our updated snapshot + Map result = new HashMap(); + if ( keys == null ) + { + result.putAll( snapshot ); + } + else + { + Object key; + Iterator iterator = keys.iterator(); + while ( iterator.hasNext() ) + { + key = iterator.next(); + result.put( key, snapshot.get( key ) ); + } + } + return snapshot; + } + + /** + * Returns a comparable object (typically a Date or Long) for + * the given data map or snapshot. This is used to determine + * whether a local snapshot should be dumped in favor of fetched + * data from the server. + * Returns null if no timestamp can be determined, in which + * case the fetched data will assumed to be more recent than + * any local snapshot. + */ + abstract protected Comparable timestampForData( Map aDataMap ); + + /** + * Extracts the global id for the fetched data or snapshot. + * Some entities have multi-attribute keys that would be + * assembled into a single instance of EOGlobalID. + */ + abstract protected EOGlobalID globalIDForData( Map aDataMap ); + + /** + * Returns the entity that corresponds to the specified global id + * and/or object. Either may be null, but both will not be null. + * //FIXME: This is less than elegant. + */ + abstract protected String entityForGlobalIDOrObject( + EOGlobalID aGlobalID, Object anObject ); + + /** + * Returns the keys that have changed on the specified object. + * If null, all keys are presumed changed, including relationships. + */ + abstract protected Collection changedKeysForObject( Object anObject ); + + /** + * Returns the data for the row corresponding to the specified id + * containing at least the specified keys. Implementations are allowed + * to return more data than requested, and callers are advised to take + * advantage of the returned data. + */ + abstract protected Map readObject( EOGlobalID aGlobalID, Collection keys ); + + /** + * Returns the data for the row corresponding to the specified id. + * //TODO: Need a better return value? How to return invalidated list? + */ + abstract protected Map insertObject( EOGlobalID aGlobalID, Map aDataMap ); + + /** + * Returns the data for the row corresponding to the specified id. + * //TODO: Need a better return value? How to return invalidated list? + */ + abstract protected Object updateObject( EOGlobalID aGlobalID, Map aDataMap ); + + /** + * Returns the data for the row corresponding to the specified id. + * //TODO: Need a better return value? How to return invalidated list? + */ + abstract protected Object deleteObject( EOGlobalID aGlobalID ); + + /** + * Creates a new instance of an object that corresponds to the + * specified global id and is registered in the specified context. + * This implementation extracts the entity type from getEntityForGlobaID + * and construct a new instance from the class description that + * corresponds to the entity type. Override to change this behavior. + */ + protected Object createInstanceWithEditingContext( + EOGlobalID aGlobalID, EOEditingContext aContext ) + { + String entity = entityForGlobalIDOrObject( aGlobalID, null ); + EOClassDescription classDesc = + EOClassDescription.classDescriptionForEntityName( entity ); + if ( classDesc == null ) + { + throw new WotonomyException( "Unknown entity type: " + entity ); + } + + Object result = classDesc.createInstanceWithEditingContext( aContext, aGlobalID ); + if ( result instanceof EOFaulting ) + { + ((EOFaulting)result).turnIntoFault( null ); + } + return result; + } + + /** + * Dumps the snapshot corresponding to the specified id. + */ + protected void invalidateObject( EOGlobalID aGlobalID ) + { + snapshots.remove( aGlobalID ); + } + + /** + * Dumps all snapshots. + */ + protected void invalidateAllCache() + { + snapshots.clear(); + } + + /** + * Remove all values from all objects in memory, turning them into faults, + * and posts a notification that all objects have been invalidated. + */ + public void invalidateAllObjects() + { + invalidateAllCache(); + + // post notification + NSNotificationQueue.defaultQueue().enqueueNotification( + new NSNotification( + InvalidatedAllObjectsInStoreNotification, this ), + NSNotificationQueue.PostNow ); + } + + /** + * Removes values with the specified ids from memory, turning them into + * faults, and posts a notification that those objects have been invalidated. + */ + public void invalidateObjectsWithGlobalIDs( List aList ) + { + NSArray empty = new NSArray(); + NSMutableArray invalidated = new NSMutableArray(); + + Object object; + Iterator iterator = aList.iterator(); + while ( iterator.hasNext() ) + { + object = iterator.next(); + invalidateObject( (EOGlobalID) object ); + invalidated.addObject( object ); + } + + NSMutableDictionary info = new NSMutableDictionary(); + info.setObjectForKey( empty, InsertedKey ); + info.setObjectForKey( empty, UpdatedKey ); + info.setObjectForKey( empty, DeletedKey ); + info.setObjectForKey( invalidated, InvalidatedKey ); + + // post notification + NSNotificationQueue.defaultQueue(). + enqueueNotificationWithCoalesceMaskForModes( new NSNotification( + ObjectsChangedInStoreNotification, this, info ), + NSNotificationQueue.PostNow, + NSNotificationQueue.NotificationNoCoalescing, null ); + } + + /** + * Returns false because locking is not currently permitted. + */ + public boolean isObjectLockedWithGlobalID( EOGlobalID aGlobalID, + EOEditingContext aContext ) + { + return false; + } + + /** + * Does nothing because locking is not currently permitted. + */ + public void lockObjectWithGlobalID( EOGlobalID aGlobalID, + EOEditingContext aContext ) + { + // does nothing + } + + /** + * Returns a List of objects associated with the object + * with the specified id for the specified property + * relationship. This method may not return an array fault + * because array faults call this method to fetch on demand. + * All objects must be registered the specified editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public NSArray objectsForSourceGlobalID( EOGlobalID aGlobalID, + String aRelationship, EOEditingContext aContext ) + { // System.out.println( "objectsForSourceGlobalID: " + aGlobalID + " : " + aRelationship + " : " ); + + Map snapshot = readFromCache( aGlobalID, new NSArray( aRelationship ) ); + Object value = snapshot.get( aRelationship ); + if ( value == null ) value = new NSArray(); // empty list + if ( ! ( value instanceof Collection ) ) + { + throw new RuntimeException( "Specified relationship is not a collection: " + + aRelationship + " : " + aGlobalID + " : " + value ); + } + + NSArray result = new NSMutableArray(); + + // get fault for each id + EOGlobalID id; + Object fault; + Iterator iterator = ((Collection)value).iterator(); + while ( iterator.hasNext() ) + { + id = (EOGlobalID) iterator.next(); + + // get registered fault + fault = aContext.faultForGlobalID( id, aContext ); + + // assert fault + if ( fault == null ) + { + // this should never happen + throw new RuntimeException( + "Could not find fault for ID: " + id ); + } + + result.add( fault ); + } + + fireObjectsChangedInStore(); + +//System.out.println( "done" ); + return result; + } + + /** + * Returns a List of objects the meet the criteria of + * the supplied specification. Faults are not allowed in the array. + * Each object is registered with the specified editing context. + * If any object is already fetched in the specified context, + * it is not refetched and that object should be used in the array. + */ + public NSArray objectsWithFetchSpecification( + EOFetchSpecification aFetchSpec, EOEditingContext aContext ) + { + NSMutableArray result = new NSMutableArray(); + + //TODO: implement this + + return result; + } + + /** + * Fires ObjectsChangedInStoreNotification + * with contents of buffers and then clears buffers. + * If buffers are empty, does nothing. + */ + private void fireObjectsChangedInStore() + { + // check for changes to broadcast + if ( insertedIDsBuffer.size() + updatedIDsBuffer.size() + + deletedIDsBuffer.size() + invalidatedIDsBuffer.size() == 0 ) + { + return; + } + + // broadcast ObjectsChangedInStoreNotification + // for the benefit of child editing contexts + + NSMutableDictionary storeInfo = new NSMutableDictionary(); + + storeInfo.setObjectForKey( + new NSArray( (Collection) insertedIDsBuffer ), + EOObjectStore.InsertedKey ); + storeInfo.setObjectForKey( + new NSArray( (Collection) updatedIDsBuffer ), + EOObjectStore.UpdatedKey ); + storeInfo.setObjectForKey( + new NSArray( (Collection) deletedIDsBuffer ), + EOObjectStore.DeletedKey ); + storeInfo.setObjectForKey( + new NSArray( (Collection) invalidatedIDsBuffer ), + EOObjectStore.InvalidatedKey ); + + // clear buffers + + insertedIDsBuffer.removeAllObjects(); + updatedIDsBuffer.removeAllObjects(); + deletedIDsBuffer.removeAllObjects(); + invalidatedIDsBuffer.removeAllObjects(); + + // post notification + NSNotificationQueue.defaultQueue(). + enqueueNotificationWithCoalesceMaskForModes( new NSNotification( + ObjectsChangedInStoreNotification, this, storeInfo ), + NSNotificationQueue.PostNow, + NSNotificationQueue.NotificationNoCoalescing, null ); + } + + /** + * Removes all values from the specified object, + * converting it into a fault for the specified id. + * New or deleted objects should not be refaulted. + */ + public void refaultObject( Object anObject, EOGlobalID aGlobalID, + EOEditingContext aContext ) + { +//System.out.println( "refaultObject: " + aGlobalID ); +//new net.wotonomy.ui.swing.util.StackTraceInspector(); + if ( anObject instanceof EOFaulting ) + { + ((EOFaulting)anObject).turnIntoFault( null ); + } + } + + /** + * Writes all changes in the specified editing context + * to the respository. + */ + public void saveChangesInEditingContext ( EOEditingContext aContext ) + { + Object result; // need a container result? + Map updateMap; + Object object; + EOGlobalID id; + Iterator iterator; + + //TODO: the ordering of operations here + // needs to be a lot more sophisticated. + + // process deletes first + iterator = aContext.deletedObjects().iterator(); + while ( iterator.hasNext() ) + { + object = iterator.next(); + id = aContext.globalIDForObject( object ); + try + { + result = deleteObject( id ); + } + catch ( Exception exc ) + { + System.out.println( "Error deleting object: " + id ); + exc.printStackTrace(); + } + } + + // process inserts next + iterator = aContext.insertedObjects().iterator(); + while ( iterator.hasNext() ) + { + object = iterator.next(); + processInsert( aContext, object ); + } + + // process updates last + iterator = aContext.updatedObjects().iterator(); + while ( iterator.hasNext() ) + { + object = iterator.next(); + id = aContext.globalIDForObject( object ); + try + { + updateMap = getUpdateMap( aContext, object ); + result = updateObject( id, updateMap ); + } + catch ( Exception exc ) + { + System.out.println( "Error updating object: " + id ); + exc.printStackTrace(); + } + } + + //aContext.invalidateAllObjects(); + } + + protected Object processInsert( EOEditingContext aContext, Object object ) + { + Map result = null; + EOGlobalID id = aContext.globalIDForObject( object ); + try + { + Map updateMap; + updateMap = getUpdateMap( aContext, object ); + result = insertObject( id, updateMap ); + id = globalIDForData( result ); // read new permanent id + + // broadcast that the global id has changed. + NSMutableDictionary userInfo = new NSMutableDictionary(); + userInfo.setObjectForKey( id, aContext.globalIDForObject( object ) ); + NSNotificationQueue.defaultQueue().enqueueNotification( + new NSNotification( EOGlobalID.GlobalIDChangedNotification, + null , userInfo ), NSNotificationQueue.PostNow ); + + } + catch ( Exception exc ) + { + System.out.println( "Error inserting object: " + id ); + exc.printStackTrace(); + } + return result; + } + + /** + * This method returns a map containing just the keys that are modified + * for a given object, converting any to-one or to-many relationships + * to id references. + */ + protected Map getUpdateMap( EOEditingContext aContext, Object anObject ) + { + Map result = new HashMap(); + EOEditingContext context = aContext; + + String entity = entityForGlobalIDOrObject( null, anObject ); + EOClassDescription classDesc = + EOClassDescription.classDescriptionForEntityName( entity ); + if ( classDesc == null ) + { + throw new WotonomyException( "Unknown entity type: " + entity ); + } + + NSArray oneKeys = classDesc.toOneRelationshipKeys(); + NSArray manyKeys = classDesc.toManyRelationshipKeys(); + + String key; + Object value; + EOGlobalID id; + + Collection changedKeys = changedKeysForObject( anObject ); + if ( changedKeys == null ) + { + // assume all keys changed + changedKeys = classDesc.attributeKeys(); + changedKeys.addAll( oneKeys ); + changedKeys.addAll( manyKeys ); + } + Iterator iterator = changedKeys.iterator(); + while ( iterator.hasNext() ) + { + key = iterator.next().toString(); + if ( anObject instanceof EOKeyValueCoding ) + { + value = ((EOKeyValueCoding)anObject).storedValueForKey( key ); + } + else + { + value = EOKeyValueCodingSupport.storedValueForKey( anObject, key ); + } + + // convert to-one relationship to oid + if ( oneKeys.contains( key ) ) + { + id = context.globalIDForObject( value ); + + // if this id hasn't been persisted, save it first + // NOTE: this won't work for self-referential graphs of objects! + if ( id.isTemporary() ) + { + processInsert( aContext, value ); + id = context.globalIDForObject( value ); + } + + value = id; + } + else + // convert to-many relationship list to oid list + if ( manyKeys.contains( key ) ) + { + //NOTE: we can assume that array faults that + // are marked as changed have been fired. + if ( value instanceof Collection ) + { + Object object; + Collection newValue = new LinkedList(); + Iterator jiterator = ((Collection)value).iterator(); + while ( jiterator.hasNext() ) + { + object = jiterator.next(); + id = context.globalIDForObject( object ); + + // if this id hasn't been persisted, save it first + // NOTE: this won't work for self-referential graphs of objects! + if ( id.isTemporary() ) + { + processInsert( aContext, object ); + id = context.globalIDForObject( object ); + } + newValue.add( id ); + } + value = newValue; + } + else + { + // should never happen + new RuntimeException( + "Can't update to-many relationship because it's not a Collection." ) + .printStackTrace(); + } + } + + // place value in map + result.put( key, value ); + } + +System.out.println( result ); + return result; + } + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/12/18 15:37:38 mpowers + * Changes to retain ability to work with objects that don't necessarily + * implement EOEnterpriseObject. I would still like to preserve this case + * for general usage, however the access package is free to assume that + * those objects will be EOs and cast appropriately. + * + * Revision 1.4 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.3 2002/10/24 18:18:12 mpowers + * NSArray's are now considered read-only, so we can return our internal + * representation to reduce unnecessary object allocation. + * + * Revision 1.2 2002/01/19 17:27:49 mpowers + * Implemented most of it. + * + * Revision 1.1 2001/11/25 22:44:02 mpowers + * Contributing draft of AbstractObjectStore. + * + * + */ +} + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java new file mode 100644 index 0000000..59e135b --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ArrayFault.java @@ -0,0 +1,219 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import net.wotonomy.foundation.NSArray; + +/** +* A class that extends NSArray to intercept any accessor calls +* in order to defer loading until the last possible moment.<br><br> +* +* Because ArrayFault inherits from NSArray which implements +* List which implements Collection, data objects may declare +* their relationships to be of type NSArray, List, or Collection.<br><br> +* +* This class should be returned by implementations of +* EOObjectStore.arrayFaultForSourceGlobalID(). +*/ +public class ArrayFault extends NSArray +{ + private EOEditingContext editingContext; + private EOGlobalID sourceID; + private String relationshipKey; + private boolean fetched; + + public ArrayFault( + EOGlobalID aSourceID, + String aRelationshipKey, + EOEditingContext aContext ) + { + super(); + editingContext = aContext; + sourceID = aSourceID; + relationshipKey = aRelationshipKey; + fetched = false; + } + + public boolean isFetched() + { + return fetched; + } + + protected void fireFault() + { + if ( !fetched ) + { +//new net.wotonomy.ui.swing.util.StackTraceInspector(); +//System.out.println( "ArrayFault.fireFault: before:" + this ); + fetched = true; + super.protectedAddAll( + editingContext.parentObjectStore().objectsForSourceGlobalID( + sourceID, + relationshipKey, + editingContext ) ); +//System.out.println( "ArrayFault.fireFault: after:" + this ); + } + } + + public Object clone() + { + fireFault(); + return super.clone(); + } + + public boolean contains(Object elem) + { + fireFault(); + return super.contains( elem ); + } + + public boolean equals(Object o) + { + fireFault(); + return super.equals( o ); + } + + public Object get(int index) + { + fireFault(); + return super.get( index ); + } + + /** + * Overridden to return the identity hash. + * This somewhat violates the List contract, + * but otherwise calling hash code would + * fire the fault. Bottom line: don't use + * array faults as keys in hash maps. + */ + public int hashCode() + { + return System.identityHashCode( this ); + } + + public int indexOf(Object o) + { + fireFault(); + return super.indexOf( o ); + } + + public boolean isEmpty() + { + fireFault(); + return super.isEmpty(); + } + + public Iterator iterator() + { + fireFault(); + return super.iterator(); + } + + public int lastIndexOf(Object o) + { + fireFault(); + return super.lastIndexOf( o ); + } + + public ListIterator listIterator() + { + fireFault(); + return super.listIterator(); + } + + public ListIterator listIterator(int index) + { + fireFault(); + return super.listIterator( index ); + } + + public int size() + { + fireFault(); + return super.size(); + } + + public List subList(int fromIndex, int toIndex) + { + fireFault(); + return super.subList( fromIndex, toIndex ); + } + + public Object[] toArray() + { + fireFault(); + return super.toArray(); + } + + public Object[] toArray(Object[] a) + { + fireFault(); + return super.toArray( a ); + } + + /** + * Overridden to display information about + * the fault only if not fetched. + * Calls to super if fetched. + */ + public String toString() + { + if ( isFetched() ) + { + return super.toString(); + } + return "[ArrayFault@"+Integer.toHexString( System.identityHashCode( this ) )+":"+sourceID+":"+relationshipKey+"]"; + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/12/18 15:37:38 mpowers + * Changes to retain ability to work with objects that don't necessarily + * implement EOEnterpriseObject. I would still like to preserve this case + * for general usage, however the access package is free to assume that + * those objects will be EOs and cast appropriately. + * + * Revision 1.4 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.3 2002/10/24 18:17:37 mpowers + * ArrayFaults are now read-only. + * + * Revision 1.2 2001/05/06 22:22:55 mpowers + * Debugging. + * + * Revision 1.1 2001/05/05 23:05:42 mpowers + * Implemented Array Faults. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java new file mode 100644 index 0000000..8123668 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ChildDataSource.java @@ -0,0 +1,187 @@ +package net.wotonomy.control; + +import java.util.Collection; +import java.util.Iterator; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; + +/** +* A data source that automates the process of +* creating a child editing context and copying +* objects from a parent context into it. +* Attach this data source to a display group +* that represents a "detail" or "drill-down" +* view. <br><br> +* +* Once created, editingContext() will return the +* child context, and fetch() will return the objects +* that were copied into the child context. +*/ +public class ChildDataSource extends EODataSource +{ + private EODataSource parent; + private EOEditingContext context; + private EOClassDescription classDescription; + private NSMutableArray objects; + + /** + * Creates a child editing context for the + * specified parent's context and copies the + * specified object into the child context. + * The object must exist in the parent context. + * fetch() will return the child's object. + */ + public ChildDataSource( + EODataSource aParentSource, + Object anObject ) + { + this( aParentSource, new NSArray( (Object) anObject ) ); + } + + /** + * Creates a child editing context for the + * specified parent's context and copies the + * specified objects into the child context. + * The objects must exist in the parent context. + * The order of the parent's objects in the + * collection will determine the order in + * which the child objects are returned from + * fetch(). + */ + public ChildDataSource( + EODataSource aParentSource, + Collection anObjectList ) + { + EOEditingContext parentContext = + aParentSource.editingContext(); + + parent = aParentSource; + context = new EOEditingContext( parentContext ); +//!new net.wotonomy.ui.swing.util.ObjectInspector( context ); + objects = new NSMutableArray(); + classDescription = null; + + Object o; + Object copy; + boolean allSameClass = true; + Iterator it = anObjectList.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + + // determine class + if ( allSameClass == true ) + { + Class c = o.getClass(); + if ( classDescription == null ) + { + classDescription = + EOClassDescription.classDescriptionForClass( c ); + } + else + { + if ( c != classDescription.getDescribedClass() ) + { + allSameClass = false; + classDescription = null; + } + } + } + + // copy and add to list + objects.addObject( parentContext.faultForGlobalID( + parentContext.globalIDForObject( o ), context ) ); + } + } + + /** + * Returns the editing context for this data source, + * which was created in the constructor and whose + * parent is the editing context specified in the + * constructor. + */ + public EOEditingContext editingContext() + { + return context; + } + + /** + * This implementation does nothing. + */ + public void insertObject ( Object anObject ) + { + + } + + /** + * This implementation does nothing. + */ + public void deleteObject ( Object anObject ) + { + + } + + /** + * Returns a List containing the objects in this + * data source. This implementation returns all + * TestObjects that have been persisted to the + * datastore in the data directory. + */ + public NSArray fetchObjects () + { + return new NSArray( (Collection) objects ); + } + + /** + * Returns a data source that is capable of + * manipulating objects of the type returned by + * applying the specified key to objects + * vended by this data source. + * This implementation forwards the call to + * the parent data source. + * @see #qualifyWithRelationshipKey + */ + public EODataSource + dataSourceQualifiedByKey ( String aKey ) + { + //FIXME: This is fundamentally broken. + // Objects vended from the returned source + // are not registered in our editing context. + // We probably need yet another utility data + // source class that would wrap another source + // and convert vended objects into a different + // context. + + return parent.dataSourceQualifiedByKey( aKey ); + } + + /** + * Restricts this data source to vend those + * objects that are associated with the specified + * key on the specified object. + * This implementation forwards the call to + * the parent data source. + */ + public void + qualifyWithRelationshipKey ( + String aKey, Object anObject ) + { + parent.qualifyWithRelationshipKey( aKey, anObject ); + } + + /** + * Returns the description of the class of the + * objects that is vended by this data source, + * or null if this cannot be determined. + * This implementation returns the class of the + * objects passed to the constructor if they are + * all the same class, otherwise returns null. + */ + public EOClassDescription + classDescriptionForObjects () + { + return classDescription; + } + +} diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java new file mode 100644 index 0000000..049eb33 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOAndQualifier.java @@ -0,0 +1,167 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOAndQualifier contains other EOQualifiers, +* evaluating as true only if all of the contained +* qualifiers evaluate as true. +* +* @author michael@mpowers.net +* @author yjcheung@intersectsoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOAndQualifier extends EOQualifier + implements EOKeyValueArchiving, EOQualifierEvaluation +{ + private List qualifiers; + + public EOAndQualifier( + List aQualifierList ) + { + qualifiers = new LinkedList( aQualifierList ); + } + + /** + * Returns a List of qualifiers contained by this qualifier. + */ + public NSArray qualifiers() + { + return new NSArray( qualifiers ); + } + + /** + * Add a new qualifier to the list. + */ + public void addQualifier(EOQualifier qualifier) + { + qualifiers.add(qualifier); + } + + /** + * Evaluates this qualifier for the specified object, + * and returns whether the object is qualified. + * selector() is invoked on the value for key() on the + * specified object, with value() as the parameter. + * + * Note: this has the lazy "and" implementation. Ex. + * Qal1 and Qal2. If the Qal1 is evaluated to be false, then it returns + * false without evaluating Qa12. + */ + public boolean evaluateWithObject( Object anObject ) + { + boolean retVal = true; + Iterator it = qualifiers.iterator(); + while (it.hasNext() && retVal) + { + retVal = ((EOQualifier) it.next()).evaluateWithObject(anObject); + } + return retVal; + } + + /** + * Returns a string representation of this qualifier. + */ + public String toString() + { + StringBuffer myBuf = new StringBuffer("("); + Iterator it = qualifiers.iterator(); + while (it.hasNext()) + { + myBuf = myBuf.append(((EOQualifier) it.next()).toString()).append(" and "); + } + String myStr = myBuf.toString(); + myStr = myStr.substring(0, myStr.lastIndexOf(" and")).concat(")"); + return myStr; + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { + NSArray a = (NSArray)arch.decodeObjectForKey("qualifiers"); + if (a == null) + return null; + NSMutableArray l = new NSMutableArray(); + for (int i = 0; i < a.count(); i++) { + NSDictionary d = (NSDictionary)a.objectAtIndex(i); + EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d); + EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua); + if (q != null) + l.addObject(q); + } + return new EOAndQualifier(l); + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOAndQualifier", "class"); + NSMutableArray arr = new NSMutableArray(qualifiers.size()); + for (int i = 0; i < qualifiers.size(); i++) { + EOQualifier q = (EOQualifier)qualifiers.get(i); + if (q instanceof EOKeyValueArchiving) { + EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); + ((EOKeyValueArchiving)q).encodeWithKeyValueArchiver(ar2); + arr.addObject(ar2.dictionary()); + } else + throw new WotonomyException("Cannot archive instance of " + q.getClass().getName()); + } + arch.encodeObject(arr, "qualifiers"); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.6 2003/08/12 01:43:04 chochos + * formally implement EOQualifierEvaluation + * + * Revision 1.5 2003/08/09 01:22:51 chochos + * qualifiers implement EOKeyValueArchiving + * + * Revision 1.4 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.3 2001/10/31 15:25:14 mpowers + * Cleanup of qualifiers. + * + * Revision 1.2 2001/10/30 22:57:28 mpowers + * EOQualifier framework is now working. + * + * Revision 1.1 2001/09/13 15:25:56 mpowers + * Started implementation of the EOQualifier framework. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java new file mode 100644 index 0000000..cd07ebb --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOClassDescription.java @@ -0,0 +1,601 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.HashMap; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOClassDescription provides meta-information about a class +* and is used to customize certain behaviors within wotonomy +* and specifically within editing contexts and object stores. +* <br><br> +* +* The default implementation works for most well-formed java beans, +* but you will want to create your own subclass most typically +* to customize the toOne and toMany relationships for your +* class to ensure that an entire graph of objects is not +* persisted in order to perist a single object. +* <br><br> +* +* The easiest way to register your subclass is to create it +* in the same package as the class it describes but with +* a "ClassDesc" suffix. For example, "my.package.MyEntity" +* would be described by "my.package.MyEntityClassDesc". <br><br> +* +* Note that while the interface is the same, the implementation +* of this class differs substantially from the specification +* in order to be more useful for java classes. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 900 $ +*/ +public class EOClassDescription +{ + /** + * A delete rule specifying that object(s) that reference + * this object should have those references set to null + * when this object is deleted. + */ + public static final int DeleteRuleNullify = 0; + + /** + * A delete rule specifying that object(s) referenced by + * this object should also be deleted when this object + * is deleted. + */ + public static final int DeleteRuleCascade = 1; + + /** + * A delete rule specicying that this object should + * not be allowed to be deleted if it references any + * object(s). + */ + public static final int DeleteRuleDeny = 2; + + /** + * A delete rule specifying that no action be taken + * when this object is deleted. This is the default. + */ + public static final int DeleteRuleNoAction = 3; + + /** + * Notification fired when a class description has been requested + * for a class. Observers should watch for this notification and + * call registerClassDescription so that class descriptions can be + * loaded on-demand. + * The notification's object is the requested class and the + * user info dictionary is null. + */ + public static final String ClassDescriptionNeededForClassNotification = + "ClassDescriptionNeededForClassNotification"; + + /** + * Notification fired when a class description has been requested + * for an entity name. Observers should watch for this notification and + * call registerClassDescription so that class descriptions can be + * loaded on-demand. + * The notification's object is the requested name and the + * user info dictionary is null. + */ + public static final String ClassDescriptionNeededForEntityNameNotification = + "ClassDescriptionNeededForEntityNameNotification"; + + public EOClassDescription() { + super(); + } + + /** + * Returns the class description that corresponds to the specified class. + * If the class description has not already been loaded, a + * ClassDescriptionNeededForClassNotification is posted. + * If the class description is still not found, the class' loader + * is consulted for a class in the same package and named the same as the + * specified class but appended with "ClassDesc", e.g. "EmployeeObjectClassDesc". + * If the class description is still not found, a class description is + * returned that uses java bean introspection to provide reasonable values. + */ + public static EOClassDescription classDescriptionForClass( + Class aClass ) + { + if ( classMap == null ) classMap = new HashMap(); + EOClassDescription result = (EOClassDescription) classMap.get( aClass ); + if ( result == null ) + { + // if not found, post notification + NSNotificationCenter.defaultCenter().postNotification( + ClassDescriptionNeededForClassNotification, aClass, null ); + result = (EOClassDescription) classMap.get( aClass ); + } + if ( result == null ) + { + // if not found, look for similarly named class + String className = aClass.getName() + ClassNameSuffix; + Class classDesc; + try + { + classDesc = aClass.getClassLoader().loadClass( className ); + if ( classDesc != null ) + { + result = (EOClassDescription) classDesc.newInstance(); + registerClassDescription( result, aClass ); + } + } + catch ( Exception exc ) + { + // ignore exceptions and resume + } + } + if ( result == null ) + { + // if not found, default to this class + result = new EOClassDescription( aClass ); + registerClassDescription( result, aClass ); + } + return result; + } + + /** + * Returns the class description that corresponds to the specified + * entity name. If the class description has not already been + * loaded, a ClassDescriptionNeededForEntityNameNotification is posted. + * Returns null if no class description can be found for the entity name. + */ + public static EOClassDescription classDescriptionForEntityName( + String aName ) + { + if ( entityMap == null ) entityMap = new HashMap(); + EOClassDescription result = (EOClassDescription) entityMap.get( aName ); + if ( result == null ) + { + // if not found, post notification + NSNotificationCenter.defaultCenter().postNotification( + ClassDescriptionNeededForEntityNameNotification, aName, null ); + result = (EOClassDescription) entityMap.get( aName ); + } + return result; + } + + /** + * Clears all cached class descriptions so that new requests + * for class descriptions will be re-loaded on-demand. + */ + public static void invalidateClassDescriptionCache() + { + classMap.clear(); + entityMap.clear(); + } + + /** + * Registers the specified class descriptiong for the specified class. + * Nulls are not allowed - to clear the cache call invalidateClassDescriptionCache(). + */ + public static void registerClassDescription( + EOClassDescription description, + Class aClass ) + { + if ( classMap == null ) classMap = new HashMap(); + if ( entityMap == null ) entityMap = new HashMap(); + description.theClass = aClass; + classMap.put( aClass, description ); + entityMap.put( description.entityName(), description ); + } + +/* + public static Object classDelegate() + { + throw new WotonomyException( "Not implemented yet." ); + } + + public static void setClassDelegate( + Object aDelegate) + { + throw new WotonomyException( "Not implemented yet." ); + } +*/ + + /** + * The string appended to the java class name when + * searching the class path for an appropriate description. + */ + private final static String ClassNameSuffix = "ClassDesc"; + + private static Map classMap; + private static Map entityMap; + + protected Class theClass; + private NSMutableArray attributes; + + /** + * Constructor may only be called by subclasses. + */ + protected EOClassDescription( Class aClass ) + { + theClass = aClass; + } + + /** + * Returns a List of all the attributes for this class. + * This implementation reflects on the java class to produce + * a list of attributes, and then removes those keys that + * are returned by toOneRelationshipKeys and toManyRelationhipKeys. + */ + public NSArray attributeKeys() + { + if ( attributes == null ) + { + NSMutableArray readProperties = new NSMutableArray(); + String[] read = Introspector.getReadPropertiesForClass( theClass ); + for ( int i = 0; i < read.length; i++ ) + { + readProperties.addObject( read[i] ); + } + + attributes = new NSMutableArray(); + String[] write = Introspector.getWritePropertiesForClass( theClass ); + for ( int i = 0; i < write.length; i++ ) + { + attributes.addObject( write[i] ); + } + + // only use properties on both lists: read/write + attributes.retainAll( readProperties ); + + // remove relationship keys + attributes.removeAll( toOneRelationshipKeys() ); + attributes.removeAll( toManyRelationshipKeys() ); + } + return attributes; + } + + /** + * This method is called when the specified object has been + * fetched into the specified editing context. Fetch means + * an object was fetched using a fetch specification - it is + * not the same thing as an insertion. + * This implementation does nothing. + */ + public void awakeObjectFromFetch( + Object object, + EOEditingContext anEditingContext ) + { + } + + /** + * This method is called when the specified object has been + * inserted into the specified editing context. Insertion + * means an object was inserted by a display group - it does + * not mean the same thing as a fetch. + * This implementation does nothing. + */ + public void awakeObjectFromInsertion( + Object object, + EOEditingContext anEditingContext ) + { + // does nothing + } + + /** + * Returns the class decription for the object referenced + * by the specified relationship key, or null if the + * class description cannot be determined for that key. + * This implementation returns null. + */ + public EOClassDescription classDescriptionForDestinationKey( + String detailKey ) + { + return null; + } + + /** + * Creates a new instance of the class represented by this + * class description, registering it with the specified + * editing context and global id. The class description + * may not keep references to the newly created object. + * The editing context and/or the id may be null. + * This implementation constructs a new instance of the class + * and registers it with the specified editing context. + * If the global id is specified, the object will be populated + * with the appropriate data, otherwise the object will be + * treated as a newly inserted object. + * If no editing context is specified, the global id is + * ignored and the new instance of the class is returned. + */ + public Object createInstanceWithEditingContext( + EOEditingContext anEditingContext, + EOGlobalID globalID ) + { +//System.out.println( "createInstanceWithEditingContext: " + this + " : " + theClass ); + Object result = null; + try + { + result = theClass.newInstance(); + if ( anEditingContext != null ) + { + if ( globalID != null ) + { + if ( result instanceof EOEnterpriseObject ) + { + ((EOEnterpriseObject)result).awakeFromFetch( anEditingContext ); + } + // register in editing context + anEditingContext.recordObject( result, globalID ); + } + else // no global id specified + { + if ( result instanceof EOEnterpriseObject ) + { + ((EOEnterpriseObject)result).awakeFromInsertion( anEditingContext ); + } + // register as new object in editing context + anEditingContext.insertObject( result ); + } + } + } + catch ( Exception exc ) + { + // error instantiating + throw new WotonomyException( exc ); + } + return result; + } + +/* + public NSFormatter defaultFormatterForKey( + String key ) + { + throw new WotonomyException( "Not implemented yet." ); + } +*/ + + /** + * Returns the delete rule to be used for the specified + * relationship key. + * This implementation returns DeleteRuleNoAction. + */ + public int deleteRuleForRelationshipKey( + String relationshipKey ) + { + return DeleteRuleNoAction; + } + + /** + * Returns a human-readable title for the specified key. + * For example, displayNameForKey( "firstName" ) might + * return "First Name". + * This implementation attempts to construct such a string + * from the key, uppercasing the first character and + * inserting spaces before subsequent uppercase characters. + */ + public String displayNameForKey( + String key ) + { + if ( key == null ) return ""; + if ( key.length() == 0 ) return ""; + + StringBuffer result = new StringBuffer(); + result.append( Character.toUpperCase( key.charAt(0) ) ); + + char c; + int len = key.length(); + for ( int i = 1; i < len; i++ ) + { + c = key.charAt(i); + if ( Character.isUpperCase( c ) ) + { + result.append( ' ' ); + } + result.append( c ); + } + + return result.toString(); + } + + /** + * Returns a human-readable title for the class of objects + * that this class description represents. For example, + * class CustomerObject might return "Customer". + * This implementation returns the class name. + */ + public String entityName() + { + String result = theClass.getName(); + int index = result.lastIndexOf( "." ); + if ( index == -1 ) return result; + return result.substring( index+1 ); + } + + /** + * Returns the fetch specification associated with this + * class description that corresponds to the specified name, + * or null if not found. + * This implementation returns null. + */ + public EOFetchSpecification fetchSpecificationNamed( + String aString ) + { + return null; + } + + /** + * Returns the relationship key by which the object at the + * other end of the specified relationship key refers to + * this object, or null if not found. + * This implementation returns null. + */ + public String inverseForRelationshipKey( + String relationshipKey ) + { + return null; + } + + public boolean ownsDestinationObjectsForRelationshipKey( + String relationshipKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Called when this object has been deleted from the + * specified editing context. The delete rules for this + * object's relationships should be executed. + */ + public void propagateDeleteForObject( + Object object, + EOEditingContext anEditingContext ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a List of the "to many" relationships for + * this class. + * This implementation returns an empty list. + */ + public NSArray toManyRelationshipKeys() + { + return NSArray.EmptyArray; + } + + /** + * Returns a List of the "to one" relationships for + * this class. + * This implementation returns an empty list. + */ + public NSArray toOneRelationshipKeys() + { + return NSArray.EmptyArray; + } + + /** + * Returns a human-readable description of the specified object + * that should not exceed 60 characters. + * This implementation returns anObject.toString(). + */ + public String userPresentableDescriptionForObject( + Object anObject ) + { + return anObject.toString(); + } + + /** + * Verifies that the specified object may be deleted. + * Throws an exception with a user-readable error message + * if the delete operation should not be allowed. + * This implementation does nothing. + */ + public void validateObjectForDelete( + Object object ) + { + // does nothing + } + + /** + * Verifies that the specified object may be saved. + * Throws an exception with a user-readable error message + * if the save operation should not be allowed. + * This implementation does nothing. + */ + public void validateObjectForSave( + Object object ) + { + // does nothing + } + + /** + * Validates the specified value for the specified key on this + * this class. Returns null if the value is acceptable, or + * returns an object that should be used in place of the specified + * object, or throws an exception with a user-readable error message + * if no acceptable value can be determined. + * This implementation returns null. + */ + public Object validateValueForKey( Object value, String key) + { + return null; + } + + /** + * Returns the Java Class that this description describes. + * NOTE: This method is not in the specification. + */ + public Class getDescribedClass() + { + return theClass; + } + +} + +/* + * $Log$ + * Revision 1.3 2006/02/18 22:46:44 cgruber + * Add Surrogate map from .util into control's internal package, and fix imports. + * + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.11 2003/08/08 05:50:32 chochos + * theClass is protected instead of private + * + * Revision 1.10 2003/08/08 00:37:44 chochos + * default constructor is needed by subclasses + * + * Revision 1.9 2001/12/20 18:55:46 mpowers + * Hooks for awakeFromInsertion and awakeFromFetch. + * + * Revision 1.8 2001/12/01 23:51:45 mpowers + * Corrected createWithEditingContext. + * + * Revision 1.7 2001/11/25 22:43:38 mpowers + * Corrected createInstanceWithEditingContext. + * + * Revision 1.6 2001/04/29 02:29:31 mpowers + * Debugging relationship faulting. + * + * Revision 1.5 2001/04/28 22:17:51 mpowers + * Revised PropertyDataSource to be EOClassDescription-aware. + * + * Revision 1.4 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.3 2001/04/27 23:37:20 mpowers + * Now using EOClassDescription in the EODataSource class, as we should. + * + * Revision 1.2 2001/04/27 00:27:42 mpowers + * Partial implementation. + * + * Revision 1.1 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java new file mode 100644 index 0000000..072f867 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCooperatingObjectStore.java @@ -0,0 +1,82 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services Corporation + +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 + +$Id: EOCooperatingObjectStore.java 894 2006-02-16 16:47:14Z cgruber $ + +*/ + +package net.wotonomy.control; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSLocking; +/** +* A representation of a channel of communication to the database. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public abstract class EOCooperatingObjectStore extends EOObjectStore + implements NSLocking { + + public EOCooperatingObjectStore() { + } + + public abstract boolean ownsGlobalID(EOGlobalID eoglobalid); + + public abstract boolean ownsObject(EOEnterpriseObject eoenterpriseobject); + + public abstract boolean handlesFetchSpecification(EOFetchSpecification eofetchspecification); + + public abstract void prepareForSaveWithCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator, EOEditingContext eoeditingcontext); + + public abstract void recordChangesInEditingContext(); + + public abstract void recordUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary); + + public abstract void performChanges(); + + public abstract void commitChanges(); + + public abstract void rollbackChanges(); + + public abstract NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject); + + public abstract void lock(); + + public abstract void unlock(); + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:59:06 mpowers + * Contributions from cgruber. + * + * Revision 1.2 2002/06/21 22:14:30 cgruber + * Add a log trail + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java new file mode 100644 index 0000000..6b262cb --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOCustomObject.java @@ -0,0 +1,673 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSSet; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOCustomObject implements all the necessary interfaces to +* receive first-class treatment from the control framework. +* The implementation delegates as much class meta-behavior as +* possible to EOClassDescription, letting subclasses +* focus exclusively on business logic while still allowing +* them to customize as much class behavior as needed. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOCustomObject + implements EOEnterpriseObject, + EOKeyValueCodingAdditions, + EODeferredFaulting, + EORelationshipManipulation, + EOValidation +{ + private transient static EOClassDescription classDescription; + private transient EOEditingContext editingContext; + + // static configuration + + /** + * Specifies whether the implementation of EOKeyValueCoding + * is permitted to access field directly. This implementation + * returns true; subclasses may override to customize this behavior. + */ + public static boolean canAccessFieldsDirectly() + { + return true; + } + + /** + * Specifies whether the implementation of EOKeyValueCoding + * is permitted to access private accessors. This implementation + * returns true; subclasses may override to customize this behavior. + */ + public static boolean shouldUseStoredAccessors() + { + return true; + } + + /** + * Specifies whether deferred faults should be used. This implementation + * returns false; subclasses may override to customize this behavior. + */ + public static boolean usesDeferredFaultCreation() + { + return false; + } + + // constructors + + /** + * Default constructor initializes private state. + * EditingContext and ClassDescription are set to null. + */ + public EOCustomObject() + { + editingContext = null; + classDescription = null; + } + + /** + * Preferred constructor, specifying an editing context, + * a class description, and a global id, any or all of which + * may be null. Subclasses should invoke this constructor. + */ + public EOCustomObject( + EOEditingContext aContext, + EOClassDescription aClassDescription, + EOGlobalID aGlobalID ) + { + editingContext = aContext; + classDescription = aClassDescription; + } + + // interface EOEnterpriseObject + + /** + * Returns a List of all property keys defined on this object. + * This includes both attributes and relationships. + * This implementation returns the union of attributeKeys, + * toOneRelationshipKeys, and toManyRelationshipKeys. + */ + public NSArray allPropertyKeys() + { + NSSet union = new NSSet(); + union.addAll( attributeKeys() ); + union.addAll( toOneRelationshipKeys() ); + union.addAll( toManyRelationshipKeys() ); + return new NSArray( (Collection) union ); + } + + /** + * Returns a list of all attributes defined on this object. + * Attributes are all properties that are not relationships. + * This implementation retrieves the keys from the class + * description. + */ + public NSArray attributeKeys() + { + return classDescription().attributeKeys(); + } + + //void awakeFromClientUpdate(EOEditingContext aContext) + + /** + * Called when the object has first been fetched into the + * specified editing context. This implementation calls + * awakeObjectFromFetch on the class description. + */ + public void awakeFromFetch(EOEditingContext anEditingContext) + { + classDescription().awakeObjectFromFetch( this, anEditingContext ); + } + + /** + * Called when the object has been inserted into the + * specified editing context. This implementation calls + * awakeObjectFromInsertion on the class description. + */ + public void awakeFromInsertion(EOEditingContext anEditingContext) + { + classDescription().awakeObjectFromInsertion( this, anEditingContext ); + } + + /** + * Returns a Map representing the delta of the current state + * from the state represented in the specified snapshot. + * The result will contain only the keys that have changed + * and their values. Relationship keys will map to an NSArray + * that contains an NSArray of added objects and an NSArray + * of removed objects, in that order. + */ + public NSDictionary changesFromSnapshot(NSDictionary snapshot) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a class description for this object. + * Calls EOClassDescription.classDescriptionForClass. + */ + public EOClassDescription classDescription() + { + if ( classDescription == null ) + { + classDescription = EOClassDescription.classDescriptionForClass( getClass() ); + if ( classDescription == null ) + { + throw new WotonomyException( + "No class description found for class: " + getClass() ); + } + } + return classDescription; + } + + /** + * Returns a class description for the object at the + * other end of the specified relationship key. + * This implementation calls to the classDescription. + */ + public EOClassDescription classDescriptionForDestinationKey(String aKey) + { + return classDescription().classDescriptionForDestinationKey( aKey ); + } + + /** + * Clears all property values for this object. + * This method is called to clean-up an object that + * will no longer be used, and implementations should + * ensure that all references are set to null to + * prevent problems with garbage-collection. + */ + public void clearProperties() + { + //FIXME: clear properties here + } + + /** + * Returns the delete rule constant defined on EOClassDescription + * for the relationship defined by the specified key. + * This implementation calls to the classDescription. + */ + public int deleteRuleForRelationshipKey(String aRelationshipKey) + { + return classDescription().deleteRuleForRelationshipKey( aRelationshipKey ); + } + + /** + * Returns the editing context in which this object is registered. + */ + public EOEditingContext editingContext() + { + return editingContext; + } + + /** + * Returns the name of the entity that this object represents. + */ + public String entityName() + { + return classDescription().entityName(); + } + + /** + * Returns a String containing all property keys and values for + * this object. Relationships should be represented by calling + * eoShallowDescription() on the object. + */ + public String eoDescription() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a String containing all attribute keys and values for + * this object. Relationships are not included. + */ + public String eoShallowDescription() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns the key used to reference this object on the + * object at the other end of the specified relationship. + * This implementation calls to the class description. + */ + public String inverseForRelationshipKey(String aRelationshipKey) + { + return classDescription().inverseForRelationshipKey( aRelationshipKey ); + } + + //Object invokeRemoteMethod( + // String aMethodName, Class[] aTypeArray Object[] anArgumentArray) + + /** + * Returns whether the specified relationship key represents + * a to-many relationship. + */ + public boolean isToManyKey(String aKey) + { + return toManyRelationshipKeys().containsObject( aKey ); + } + + /** + * Returns whether the objects at the other end of the specified + * relationship should be deleted when this object is deleted. + * This implementation calls to the class description. + */ + public boolean ownsDestinationObjectsForRelationshipKey(String aKey) + { + return classDescription().ownsDestinationObjectsForRelationshipKey( aKey ); + } + + //void prepareValuesForClient() + + /** + * Called to perform the delete propagation for this object + * on the specified editing context. All relationships + * should be processed according to their corresponding + * delete rule. + * This implementation calls to the class description. + */ + public void propagateDeleteWithEditingContext(EOEditingContext aContext) + { + classDescription().propagateDeleteForObject( this, aContext ); + } + + /** + * Applies the changes from the specified snapshot to + * this object. + * @see #changesFromSnapshot(NSDictionary) + */ + public void reapplyChangesFromDictionary(NSDictionary aDeltaSnapshot) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a snapshot of the current state of this object. + * All property keys are mapped to their values; nulls are + * represented by NSNull. + */ + public NSDictionary snapshot() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a List of the to-many relationship keys + * for this object. + * This implementation calls to the class description. + */ + public NSArray toManyRelationshipKeys() + { + return classDescription().toManyRelationshipKeys(); + } + + /** + * Returns a List of the to-one relationship keys + * for this object. + * This implementation calls to the class description. + */ + public NSArray toOneRelationshipKeys() + { + return classDescription().toOneRelationshipKeys(); + } + + /** + * Applies the specified snapshot to this object, + * converting NSNulls to null and calling + * takeStoredValueForKey for each key in the Map. + */ + public void updateFromSnapshot(NSDictionary aSnapshot) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a short, stateful string representation + * of this object. + * This implementation calls to the class description. + */ + public String userPresentableDescription() + { + return classDescription().userPresentableDescriptionForObject( this ); + } + + /** + * This method should be called by each setter method + * on this object before changes are made to the + * object's internal state. This implementation calls + * EOObserverCenter.notifyObserversObjectWillChange( this ), + */ + public void willChange() + { + EOObserverCenter.notifyObserversObjectWillChange( this ); + } + + // interface EOKeyValueCoding + + /** + * Returns the value for the specified property. + * If the property does not exist, this method should + * call handleQueryWithUnboundKey. + */ + public Object valueForKey( String aKey ) + { + return EOKeyValueCodingSupport.valueForKey( this, aKey ); + } + + /** + * Sets the property to the specified value. + * If the property does not exist, this method should + * call handleTakeValueForUnboundKey. + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey. + */ + public void takeValueForKey( Object aValue, String aKey ) + { + EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey ); + } + + /** + * Returns the value for the private field that + * corresponds to the specified property. + */ + public Object storedValueForKey( String aKey ) + { + return EOKeyValueCodingSupport.storedValueForKey( this, aKey ); + } + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value. + */ + public void takeStoredValueForKey( Object aValue, String aKey ) + { + EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey ); + } + + /** + * Called by valueForKey when the specified key is + * not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + public Object handleQueryWithUnboundKey( String aKey ) + { + return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); + } + + /** + * Called by takeValueForKey when the specified key + * is not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + public void handleTakeValueForUnboundKey( Object aValue, String aKey ) + { + EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); + } + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types. Implementing + * classes should handle this case appropriately + * or otherwise throw an exception. + */ + public void unableToSetNullForKey( String aKey ) + { + EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); + } + + // interface EOKeyValueCodingAdditions + + /** + * Returns the value for the specified key path, which is + * a series of keys delimited by ".", for example: + * "createTime.year.length". + */ + public Object valueForKeyPath( String aKeyPath ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Sets the value for the specified key path, which is + * a series of keys delimited by ".", for example: + * "createTime.year.length". + * The value is set for the last object referenced by + * the key path. + */ + public void takeValueForKeyPath( Object aValue, String aKeyPath ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns a Map of the specified keys to their values, + * each of which might be obtained by calling valueForKey. + */ + public NSDictionary valuesForKeys( List aKeyList ) + { + return KeyValueCodingUtilities.valuesForKeys( this, aKeyList ); + } + + /** + * Takes the keys from the specified map as properties + * and applies the corresponding values, each of which + * might be set by calling takeValueForKey. + */ + public void takeValuesFromDictionary( Map aMap ) + { + KeyValueCodingUtilities.takeValuesFromDictionary( this, aMap ); + } + + // interface EOFaulting + + /** + * Called by EOFaultHandler to prepare the object to be turned into a fault. + */ + public void clearFault() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns this object's EOFaultHandler. + */ + public EOFaultHandler faultHandler() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Returns whether this object is currently a fault. + * Returns true if this object has not yet retrieved any values. + */ + public boolean isFault() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Turns this object into a fault using the specified fault handler. + */ + public void turnIntoFault( EOFaultHandler aFaultHandler ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Called to completely fire the fault, reading all attributes. + * This method may be implemented to call willRead(null). + */ + public void willRead() + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Called to fire the fault for the specified key. + * The fault manager is required to populate the specified key + * with a value, and may populate any or all of the other values + * on this object. A null key will populate all values on the object. + * NOTE: This method is not part of the specification. + */ + public void willRead( String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + // interface EODeferredFaulting + + /** + * Returns a fault for the specified deferred fault. + */ + public Object willReadRelationship( Object anObject ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + // interface EORelationshipManipulation + + /** + * Adds the specified object to the relationship on this + * object specified by the key. For to-one relationships, + * this operation is the same as valueForKey. + */ + public void addObjectToPropertyWithKey( + Object anObject, String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Removes the specified object from the relationship on + * this object specified by the key. For to-one relationships, + * this operation is the same as takeValueForKey with a null + * value. + */ + public void removeObjectFromPropertyWithKey( + Object anObject, String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * As addObjectToProperty with key, but also performs the + * reciprocal operation on the other side of the relationship. + */ + public void addObjectToBothSidesOfRelationshipWithKey( + EORelationshipManipulation anObject, String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * As removeObjectFromPropertyWithKey with key, but also performs the + * reciprocal operation on the other side of the relationship. + */ + public void removeObjectFromBothSidesOfRelationshipWithKey( + EORelationshipManipulation anObject, String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + // interface EOValidation + + /** + * Validates this object for delete. + * Throws an exception if this object cannot be deleted. + * This implementation calls to the class description. + */ + public void validateForDelete() + { + classDescription().validateObjectForDelete( this ); + } + + /** + * Validates this object for insertion into the external store. + * Throws an exception if this object cannot be inserted. + * Validations here should be specific to insertion. + * This implementation calls validateForSave(). + */ + public void validateForInsert() + { + validateForSave(); + } + + /** + * Validates this object for a commit to the external store. + * Throws an exception if this object cannot be committed. + * Validations here are not specific to either inserts or updates. + * This implementation calls to the class description. + */ + public void validateForSave() + { + classDescription().validateObjectForSave( this ); + } + + /** + * Validates this object for update to the external store. + * Throws an exception if this object cannot be updated. + * Validations here should be specific to updates. + * This implementation calls validateForSave(). + */ + public void validateForUpdate() + { + validateForSave(); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2001/12/06 16:42:29 mpowers + * Added appropriate constructor. + * + * Revision 1.2 2001/11/24 17:37:29 mpowers + * Implemented static methods. + * + * Revision 1.1 2001/11/17 17:18:15 mpowers + * Initial implementation of EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java new file mode 100644 index 0000000..c7e5284 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODataSource.java @@ -0,0 +1,164 @@ +/* +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.control; + +import net.wotonomy.foundation.NSArray; + +/** +* EODataSource is used by EODisplayGroup.fetch() to retrieve +* a list of objects to display. When a display group has a +* data source, the display group will use the data source to +* populate the object list and to create new objects to be +* displayed in the list, and will update the data source when +* objects are inserted or removed from the list. <br><br> +* +* In certain cases, as when a display group needs to populate +* a child display group to show a one-to-many relationship, +* the display group will call dataSourceQualifiedByKey to +* return a new data source that can vend objects associated +* with the specified key and then call qualifyWithRelationshipKey +* to specify the object and key that are the source of the +* child relationship. <br><br> +* +* Concrete subclasses are expected to override fetch() and +* are required to override insertObject and removeObject. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EODataSource +{ + /** + * Creates a new object. You should call + * insertObject() to insert the new object into + * this data source. + * This implementation attempts to create a new + * instance of the class returned by + * classDescriptionForObjects(). + * Override to return an object specific to + * your implementation. + * @return The newly created object, or null if + * new objects are not supported by this data source. + * @see #classDescriptionForObjects + */ + public Object createObject () + { + Object result = null; + EOClassDescription c = classDescriptionForObjects(); + if ( c != null ) + { + result = c.createInstanceWithEditingContext( editingContext(), null ); + } + return result; + } + + /** + * Inserts the specified object into this data source. + */ + public abstract void insertObject ( Object anObject ); + + /** + * Deletes the specified object from this data source. + */ + public abstract void deleteObject ( Object anObject ); + + /** + * Returns the editing context for this data source, + * or null if no editing context is used. + * This implementation returns null. + */ + public EOEditingContext editingContext () + { + return null; + } + + /** + * Returns a List containing the objects in this + * data source. This implementation returns null. + */ + public NSArray fetchObjects () + { + return null; + } + + /** + * Returns a data source that is capable of + * manipulating objects of the type returned by + * applying the specified key to objects + * vended by this data source. + * @see #qualifyWithRelationshipKey + */ + public abstract EODataSource + dataSourceQualifiedByKey ( String aKey ); + + /** + * Restricts this data source to vend those + * objects that are associated with the specified + * key on the specified object. + */ + public abstract void + qualifyWithRelationshipKey ( + String aKey, Object anObject ); + + /** + * Returns the description of the class of the + * objects that is vended by this data source, + * or null if this cannot be determined. + * This implementation returns null. + */ + public EOClassDescription + classDescriptionForObjects () + { + return null; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2001/05/21 14:02:44 mpowers + * Corrected javadoc. + * + * Revision 1.4 2001/04/27 23:37:20 mpowers + * Now using EOClassDescription in the EODataSource class, as we should. + * + * Revision 1.3 2001/02/27 23:11:07 mpowers + * Removed object registration from createObject(). + * + * Revision 1.2 2001/02/16 18:34:19 mpowers + * Implementing nested contexts. + * + * Revision 1.1.1.1 2000/12/21 15:46:38 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:34 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java new file mode 100644 index 0000000..2e350f1 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODatabaseDataSource.java @@ -0,0 +1,339 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.util.Collection; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSSet; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EODatabaseSource is a general-purpose implementation +* of EODataSource that is EOClassDescription-aware and +* that can vend appropriate EODetailDataSources. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EODatabaseDataSource +{ + EOQualifier auxiliaryQualifier; + EOEditingContext editingContext; + String entityName; + String fetchSpecificationName; + EOFetchSpecification fetchSpecification; + NSDictionary qualifierBindings; + EOClassDescription classDescription; + boolean fetchEnabled; + + /** + * Constructs a data source that fetches all objects of + * the specified entity type. + */ + public EODatabaseDataSource( + EOEditingContext aContext, String anEntityName) + { + this( aContext, anEntityName, null ); + } + + /** + * Constructs a data source that fetches objects of the + * specified entity type according to the fetch specification + * with the specified name. + */ + public EODatabaseDataSource( + EOEditingContext aContext, String anEntityName, String aFetchSpecName) + { + fetchEnabled = true; + editingContext = aContext; + entityName = anEntityName; + setFetchSpecificationByName( fetchSpecificationName ); + } + + /** + * Returns the qualifier that is applied to the results fetched by the fetch + * specification before objects are returned by fetch objects, or null if no + * such qualifier has been specified. + */ + public EOQualifier auxiliaryQualifier() + { + return auxiliaryQualifier; + } + + /** + * Returns the description of the class of the + * objects that is vended by this data source, + * or null if no entity name is specified. + */ + public EOClassDescription classDescriptionForObjects () + { + if ( entityName == null ) return null; + return EOClassDescription.classDescriptionForEntityName( entityName ); + } + + /** + * Returns the object store at the root of the + * editing context's editing hierarchy. + */ + public EOObjectStore databaseContext() + { + EOObjectStore store = editingContext(); + while ( store instanceof EOEditingContext ) + { + store = ((EOEditingContext)store).parentObjectStore(); + } + return store; + } + + /** + * Returns a detail data source that is capable of + * manipulating objects of the type returned by + * applying the specified key to objects + * vended by this data source. + * @see #qualifyWithRelationshipKey + */ + public EODataSource dataSourceQualifiedByKey ( String aKey ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + /** + * Deletes the specified object from this data source. + * This implementation deletes the specified object from + * the editing context. + */ + public void deleteObject ( Object anObject ) + { + editingContext.deleteObject( anObject ); + } + + /** + * Returns the editing context for this data source, + * or null if no editing context was specified. + */ + public EOEditingContext editingContext () + { + return editingContext; + } + +/* + public EOEntity entity() {} +*/ + + /** + * Returns a List containing the objects of the current + * entity type that conform to the specified fetch specification. + * If an auxiliary qualifier has been specified, that qualifier + * is applied to the objects before returning the result. + * If fetch is not enabled, this method returns null. + */ + public NSArray fetchObjects () + { + if ( ! isFetchEnabled() ) return null; + NSArray result = + editingContext.objectsWithFetchSpecification( fetchSpecification() ); + if ( auxiliaryQualifier() != null ) + { + result = EOQualifier.filteredArrayWithQualifier( result, auxiliaryQualifier() ); + } + return result; + } + + /** + * Returns the fetch specification currently used by this data + * source to fetch objects, or null if none is specified. + * If null, this fetchObjects() will return all objects of the + * specified entity type. + */ + public EOFetchSpecification fetchSpecification() + { + return fetchSpecification; + } + + /** + * Returns a copy of the fetch specification that will be used to + * determine fetch for this data source. If this data source has + * an auxiliary qualifier, that qualifier will be inserted into + * the returned fetch specification's qualifier. + */ + public EOFetchSpecification fetchSpecificationForFetch() + { + EOFetchSpecification result = (EOFetchSpecification) fetchSpecification.clone(); + if ( auxiliaryQualifier() != null ) + { + NSMutableArray join = new NSMutableArray(); + join.addObject( fetchSpecification.qualifier() ); + join.addObject( auxiliaryQualifier() ); + result.setQualifier( new EOAndQualifier( join ) ); + } + return result; + } + + /** + * Returns the name of the current fetch specification, or null + * if no name has been specified. + */ + public String fetchSpecificationName() + { + return fetchSpecificationName; + } + + /** + * Inserts the specified object into this data source. + * This implementation registers the object as an inserted + * object with the editing context. + */ + public void insertObject ( Object anObject ) + { + editingContext.insertObject( anObject ); + } + + /** + * Returns whether fetching is currently allowed. + * If false, fetchObjects() will return null. + * Default is true. + */ + public boolean isFetchEnabled() + { + return fetchEnabled; + } + + /** + * Returns a List of the union of the binding keys for the fetch spec's + * qualifier and the auxiliary qualifier. + */ + public NSArray qualifierBindingKeys() + { + NSSet union = new NSSet(); + if ( ( fetchSpecification != null ) + && ( fetchSpecification.qualifier() != null ) ) + { + union.addAll( fetchSpecification.qualifier().bindingKeys() ); + } + if ( auxiliaryQualifier() != null ) + { + union.addAll( auxiliaryQualifier().bindingKeys() ); + } + return new NSArray( (Collection) union ); + } + + /** + * Returns a Map of the bindings that will be applied against + * the fetch spec's qualifier and the auxiliary qualifier, + * or null if no bindings exist. + */ + public NSDictionary qualifierBindings() + { + if ( qualifierBindings == null ) return null; + return new NSDictionary( (Map) qualifierBindings ); + } + + /** + * Restricts this data source to vend those + * objects that are associated with the specified + * key on the specified object. + */ + public void qualifyWithRelationshipKey ( + String aKey, Object anObject ) + { + throw new WotonomyException( "Not implemented yet" ); + } + + /** + * Sets the auxiliary qualifier that will be applied to + * objects returned from the fetch described by the fetch specification. + */ + public void setAuxiliaryQualifier(EOQualifier aQualifier) + { + auxiliaryQualifier = aQualifier; + } + + /** + * Sets whether fetches are currently allowed. + * If false, fetchObjects() will return null. + */ + public void setFetchEnabled(boolean isFetchEnabled) + { + fetchEnabled = isFetchEnabled; + } + + /** + * Sets the fetch specification used by this data source. + * If null, all objects of the specified entity type will + * be returned by fetchObjects(). + */ + public void setFetchSpecification( EOFetchSpecification aFetchSpec) + { + fetchSpecificationName = null; + fetchSpecification = aFetchSpec; + } + + /** + * Sets the fetch specification used by this data source, + * requesting it from the class description for this data source's + * entity class description, if any. If the name cannot be resolved, + * the fetch specification will be set to null. + */ + public void setFetchSpecificationByName(String aName) + { + fetchSpecificationName = aName; + fetchSpecification = EOFetchSpecification.fetchSpecificationNamed( aName, entityName ); + } + + /* + public void setParentDataSourceRelationshipKey( EODataSource aDataSource, String aKey) + */ + + /** + * Sets the bindings to be applied to the fetch specification and the auxiliary qualifier. + */ + public void setQualifierBindings(Map aBindingMap) + { + if ( aBindingMap == null ) + { + qualifierBindings = null; + } + else + { + qualifierBindings = new NSDictionary( (Map) aBindingMap ); + } + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/24 17:38:00 mpowers + * Contributing EODatabaseDataSource. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java new file mode 100644 index 0000000..c87a097 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODeferredFaulting.java @@ -0,0 +1,48 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** +* EODeferredFaulting defines a method +* to handle relationships that are deferred faults. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EODeferredFaulting extends EOFaulting +{ + /** + * Returns a fault for the specified deferred fault. + */ + Object willReadRelationship( Object anObject ); +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java new file mode 100644 index 0000000..758fb40 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserver.java @@ -0,0 +1,152 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +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.control; + +import java.util.Observable; +import java.util.Observer; + +/** +* This is an abstract class for receiving coalesced +* notifications of changes from objects. +* This class also implements Observer for greater +* compatibility. +* The point of EODelayedObservers is that when +* they receive a willChange message, they +* queue themselves with a EODelayedObserverQueue +* so they can receive a single subjectChanged() +* after all changes from an observed object take +* place. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EODelayedObserver + implements EOObserving, Observer +{ + /** + * Notified immediately. + */ + public static final int ObserverPriorityImmediate = 0; + public static final int ObserverPriorityFirst = 1; + public static final int ObserverPrioritySecond = 2; + public static final int ObserverPriorityThird = 3; + public static final int ObserverPriorityFourth = 4; + public static final int ObserverPriorityFifth = 5; + public static final int ObserverPrioritySixth = 6; + public static final int ObserverPriorityLater = 7; + public static final int ObserverNumberOfPriorities = 8; + + /** + * Default constructor. + */ + public EODelayedObserver () + { + } + + /** + * Removes this observer from the observer queue + * for a currently pending notification. + */ + public void discardPendingNotification () + { + observerQueue().dequeueObserver( this ); + } + + /** + * Returns the observer queue to which this observer + * belongs. This implementation returns the default + * EODelayedObserverQueue. + * Override to use a different one. + */ + public EODelayedObserverQueue observerQueue () + { + return EODelayedObserverQueue.defaultObserverQueue(); + } + + /** + * Returns the priority of this observer in the queue. + * This implementation returns ObserverPriorityThird. + * Override to be notified before other observers. + */ + public int priority () + { + return ObserverPriorityThird; + } + + /** + * Notifies observer that one or more objects that + * it is observing have changed. The observer should + * check all objects it is observing for changes. + */ + public abstract void subjectChanged (); + + // interface EOObserving + + /** + * Called when the specified object is about to change. + * This implementation puts this observer on a + * notification queue. + */ + public void objectWillChange ( Object anObject ) + { + observerQueue().enqueueObserver( this ); + } + + // interface Observer + + /** + * Called when the specified object has changed, + * with the specified argument. + * This method is included for interacting with + * the java.lang.Observer pattern. + * This implementation simply objectWillChange(anObject) + * so that the observer still gets a single subjectChanged + * call in response to multiple changes. + */ + public void update ( Observable anObject, Object aValue ) + { + objectWillChange( anObject ); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2001/10/26 18:38:10 mpowers + * Reordered priorities. + * + * Revision 1.1.1.1 2000/12/21 15:46:38 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java new file mode 100644 index 0000000..6b9b9c3 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EODelayedObserverQueue.java @@ -0,0 +1,323 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +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.control; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import net.wotonomy.foundation.NSRunLoop; +import net.wotonomy.foundation.NSSelector; + +/** +* EODelayedObserverQueue allows EODelayedObservers +* to receive only one subjectChanged() message +* after numerous willChange() messages have +* been sent. Observers are then notified in order +* of their priority property, +* so that certain observers can be notified before +* others for whatever application-specific purpose. +* This class is not thread-safe and should be used +* only for single-threaded GUI clients (AWT and Swing). +* <br><br> +* +* Important note: because AWT's event queue does +* not allow for priority-based scheduling, this +* class installs a custom event queue, replacing +* the existing queue on the AWT dispatch thread. +* We know of no way around this problem. +* <br><br> +* +* Implementation note: this queue relies on the +* result of equals() for maintaining a set of +* objects on the queue. If two EODelayedObservers +* evaluate to the same value using equals(), only +* one of them will exist on the queue. If this, +* starts to suck, we can change it. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public class EODelayedObserverQueue +{ + /** + * The default run loop ordering flushes the delayed observers + * up to ObserverPrioritySixth before dispatching the AWT event + * queue. ObserverPriorityLater is run last. + */ + public static int FlushDelayedObserversRunLoopOrdering = 400000; + + private static EODelayedObserverQueue + defaultObserverQueue = null; + + private static NSSelector runLaterSelector = + new NSSelector( "flushObserverQueue", + new Class[] { Object.class } ); + + private boolean willRunLater; + private LinkedList priorityQueue; + + /** + * Default constructor. + */ + public EODelayedObserverQueue () + { + willRunLater = false; + priorityQueue = new LinkedList(); + } + + /** + * Returns the system default observer queue. + */ + public static EODelayedObserverQueue defaultObserverQueue () + { + if ( defaultObserverQueue == null ) + { + defaultObserverQueue = new EODelayedObserverQueue(); + } + return defaultObserverQueue; + } + + /** + * Removes the specified observer from the queue. + */ + public void dequeueObserver ( + EODelayedObserver anObserver ) + { +//System.out.println( "dequeueObserver: " + anObserver ); + //synchronized ( priorityQueue ) + //{ + priorityQueue.remove( anObserver ); + //} + } + + /** + * Adds the specified observer to the queue. + * An already enqueued observer will not be + * added again. + * If the observer's priority is + * ObserverPriorityImmediate, it will be + * notified immediately and not added to the + * queue. + * Otherwise, the queue sets itself up to + * call notifyObserversUpToPriority during the + * run loop as specified by + * FlushDelayedObserversRunLoopOrdering. + */ + public void enqueueObserver ( + EODelayedObserver anObserver ) + { + // syntactic glue for Runnables + final EODelayedObserver observer = anObserver; + + if ( observer.priority() == + EODelayedObserver.ObserverPriorityImmediate ) + { + // invoke immediately + observer.subjectChanged(); + } + else + { + // place in the delayed observer queue + + //synchronized ( priorityQueue ) + //{ + int i = 0; + int priority = observer.priority(); + Object o; + + Iterator iterator = priorityQueue.iterator(); + + // scan entire list to ensure we're not already queued + while ( iterator.hasNext() ) + { + o = iterator.next(); + if ( o == observer ) + { + // already queued + return; + } + if ( ((EODelayedObserver)o).priority() > priority ) + { + // insert at this index: break now + break; + } + i++; + } + + // if we broke early, we found a threshhold: + // continue scanning to ensure we're not already queued + while ( iterator.hasNext() ) + { + if ( iterator.next() == observer ) + { + // already queued + return; + } + } + + // insert before items of lower priority, + // otherwise insert at end of list. + priorityQueue.add( i, observer ); + + //} + runLater(); + } +//System.out.println( "enqueueObserver: " + anObserver + " : " + priorityQueue ); + } + + /** + * Notifies all observers with priority equal to + * or greater than the specified priority. + */ + public void notifyObserversUpToPriority ( int priority ) + { +//System.out.println( "notifyObserversUpToPriority: priorityQueue size = " + priorityQueue.size() ); + EODelayedObserver o; + while ( ! priorityQueue.isEmpty() ) + { + o = (EODelayedObserver) priorityQueue.getFirst(); + if ( o.priority() > priority ) break; + priorityQueue.removeFirst(); + + try + { + o.subjectChanged(); + } + catch ( Exception exc ) + { + System.out.println( "Error notifying observer: " + o ); + exc.printStackTrace(); + } + } + } + + /** + * Called to ensure that notifyObserversUpToPriority + * will be called on the next event loop. + */ + private void runLater() + { + if ( ! willRunLater ) + { + willRunLater = true; + NSRunLoop.currentRunLoop().performSelectorWithOrder( + runLaterSelector, this, null, FlushDelayedObserversRunLoopOrdering, null ); + } + } + + /** + * This method is called by the event queue run loop + * and calls notifyObserversUpToPriority with + * ObserverPriorityLater. + * NOTE: This method is not part of the specification. + */ + public void flushObserverQueue( Object anObject ) + { +//System.out.println( "EODelayedObserverQueue: running" ); + notifyObserversUpToPriority( EODelayedObserver.ObserverPrioritySixth ); + if ( ! priorityQueue.isEmpty() ) + { + // assumes all remaining on queue are ObserverPriorityLater + NSRunLoop.invokeLater( + new PriorityLaterRunnable( new LinkedList( priorityQueue ) ) ); + priorityQueue.clear(); + } + willRunLater = false; + } + + /** + * A runnable for dispatching remaining observers running at ObserverPriorityLater. + */ + class PriorityLaterRunnable implements Runnable + { + List observers; + + public PriorityLaterRunnable( List anObserverList ) + { + observers = anObserverList; + } + + public void run() + { + EODelayedObserver o = null; + Iterator i = observers.iterator(); + while ( i.hasNext() ) + { + try + { + o = (EODelayedObserver) i.next(); + o.subjectChanged(); + } + catch ( Exception exc ) + { + System.out.println( "Error notifying observer: " + o ); + exc.printStackTrace(); + } + } + } + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.8 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.7 2002/05/20 15:08:35 mpowers + * Optimization for enqueueObserver: we were scanning the entire list anyway; + * now we compare priorities and ensure we're not double-queued on same pass. + * + * Revision 1.6 2002/05/15 13:45:57 mpowers + * RunLater now appropriately runs later: at the end of the current awt queue. + * + * Revision 1.5 2002/03/11 03:18:39 mpowers + * Now properly handling ObserverChangesLater. + * + * Revision 1.4 2001/10/26 18:37:15 mpowers + * Now using NSRunLoop instead of AWT EventQueue. + * + * Revision 1.3 2001/10/22 21:54:16 mpowers + * Removed swing dependency in favor of jdk1.3 event queue. + * Optimized priority queue population. + * + * Revision 1.2 2001/10/12 18:01:59 mpowers + * Now catching exceptions before they disrupt the awt event queue. + * + * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers + * Contributing wotonomy. + * + * Revision 1.5 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java new file mode 100644 index 0000000..b01727d --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java @@ -0,0 +1,3247 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSNotification; +import net.wotonomy.foundation.NSNotificationCenter; +import net.wotonomy.foundation.NSRunLoop; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.WotonomyException; + +// swing dependency for undo manager +//import javax.swing.undo.UndoManager; + +/** +* EOEditingContext provides transactional support for +* fetching, editing, and committing changes made on a +* collection of objects to a parent object store. <br><br> +* +* EOEditingContext is itself a subclass of EOObjectStore, +* and this means that EOEditingContexts can use other +* EOEditingContexts as their parent. However, there +* still must exist an EOObjectStore as the root of the +* editing hierarchy that can maintain persistent state. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOEditingContext + extends EOObjectStore + implements EOObserving +{ + /** + * Key for the NSNotification posted after this editing context + * saves changes. Object of the notification will be this editing + * context, and user info will contain InsertedKey, UpdatedKey, + * and DeletedKey (keys are defined in EOObjectStore). + */ + public static final String + EditingContextDidSaveChangesNotification = + "EOEditingContextDidSaveChangesNotification"; + + /** + * Key for the NSNotification posted after this editing context + * observes changes. Object of the notification will be this editing + * context, and user info will contain InsertedKey, UpdatedKey, InvalidatedKey, + * and DeletedKey (keys are defined in EOObjectStore), however + * the objects in the corresponding Lists will be the actual + * objects, not their ids. + */ + public static final String + ObjectsChangedInEditingContextNotification = + "EOObjectsChangedInEditingContextNotification"; + + /** + * The default run loop ordering processes recent changes + * before delayed observers are notified and before dispatching + * the AWT event queue. + */ + public static int + EditingContextFlushChangesRunLoopOrdering = 300000; + + private static NSSelector runLaterSelector = + new NSSelector( "flushRecentChanges", + new Class[] { Object.class } ); + + private static EOObjectStore defaultParentObjectStore = null; + private static double defaultFetchTimestampLag = 0; + private static boolean retainsRegisteredObjects = true; + + private EOObjectStore parentStore; + private WeakReference delegate; + private WeakReference messageHandler; + private List editorSet; + private double fetchTimestamp; + private boolean lockBeforeModify; + private boolean propagateDeletesAfterEvent; + private boolean stopValidationAfterError; + private NSMutableArray insertedObjects; + private NSMutableArray insertedObjectsBuffer; + private NSArray insertedObjectsProxy; + private NSMutableArray updatedObjects; + private NSMutableArray updatedObjectsBuffer; + private NSArray updatedObjectsProxy; + private NSMutableArray deletedObjects; + private NSMutableArray deletedObjectsBuffer; + private NSArray deletedObjectsProxy; + private NSMutableArray deletedIDsBuffer; + private NSMutableArray invalidatedObjectsBuffer; + private NSMutableArray invalidatedIDsBuffer; + private Registrar registrar; +// private UndoManager undoManager; + + // so we don't have to trouble EOObserverCenter + private boolean ignoreChanges; + + // for delayed handling of processRecentChanges + private boolean willRunLater; + + // for handling of notifications posted + // while we're in the saveChanges method + private boolean isInvalidating; + + // for i18n or other customization + static protected String MessageChangeConflict = + "Another user changed an object you are editing: "; + + /** + * Default constructor creates a new editing context + * that uses the default object store. If the default + * object store has not been set, an exception is thrown. + */ + public EOEditingContext() + { + this( defaultParentObjectStore() ); + } + + /** + * Creates a new editing context that uses the specified + * object store as its parent object store. + */ + public EOEditingContext( EOObjectStore anObjectStore ) + { + if ( anObjectStore == null ) + { + throw new IllegalArgumentException( + "A parent object store must be specified." ); + } + + parentStore = anObjectStore; + delegate = null; + messageHandler = null; + editorSet = new LinkedList(); + fetchTimestamp = 0; + lockBeforeModify = false; + propagateDeletesAfterEvent = true; + stopValidationAfterError = true; + insertedObjects = new NSMutableArray(); + insertedObjectsBuffer = new NSMutableArray(); + insertedObjectsProxy = NSArray.arrayBackedByList( insertedObjects ); + updatedObjects = new NSMutableArray(); + updatedObjectsBuffer = new NSMutableArray(); + updatedObjectsProxy = NSArray.arrayBackedByList( updatedObjects ); + deletedObjects = new NSMutableArray(); + deletedObjectsBuffer = new NSMutableArray(); + deletedObjectsProxy = NSArray.arrayBackedByList( deletedObjects ); + deletedIDsBuffer = new NSMutableArray(); + invalidatedObjectsBuffer = new NSMutableArray(); + invalidatedIDsBuffer = new NSMutableArray(); + + if ( instancesRetainRegisteredObjects() ) + { + registrar = new Registrar( this ); + } + else + { + registrar = new WeakRegistrar( this ); + } + + ignoreChanges = false; + willRunLater = false; + isInvalidating = false; + + // create undo manager + //TODO: this should be NSUndoManager +// undoManager = new UndoManager(); + + // register for notifications + NSSelector handleNotification = + new NSSelector( "handleNotification", + new Class[] { NSNotification.class } ); + // any from parent store + NSNotificationCenter.defaultCenter().addObserver( + this, handleNotification, null, parentStore ); + // global id change from any + NSNotificationCenter.defaultCenter().addObserver( + this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null ); +//new net.wotonomy.ui.swing.NotificationInspector( null, parentStore ); + } + + /** + * Registers the specified object as an editor for this + * context. The object is expected to implement + * EOEditingContext.Editor. + */ + public void addEditor ( Object anEditor ) + { + if ( anEditor == null ) return; + editorSet.add( new WeakReference( anEditor ) ); + } + + /** + * Returns a read-only List of objects associated with the object + * with the specified id for the specified property + * relationship, or may return a placeholder array that + * will defer the fetch until needed (aka an array fault). + * All objects must be registered in the specified editing context. + * This implementation calls to its parent object store's + * implementation if the requested source object is not + * registered in this editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public NSArray arrayFaultWithSourceGlobalID ( + EOGlobalID aGlobalID, + String aRelationshipKey, + EOEditingContext aContext ) + { + NSArray result = null; + Object source = registrar.objectForGlobalID( aGlobalID ); + + // if not registered in our context + if ( source == null ) + { + // get the object registered into our context + result = parentStore.arrayFaultWithSourceGlobalID( + aGlobalID, aRelationshipKey, this ); + } + else // source is registered in our context + { + // get existing value + Object value; + if ( source instanceof EOKeyValueCoding ) + { + value = ((EOKeyValueCoding)source).storedValueForKey( + aRelationshipKey ); + } + else // handle directly + { + value = EOKeyValueCodingSupport.storedValueForKey( + source, aRelationshipKey ); + } + + if ( value == null ) + { + // do the same as if the source was null + result = parentStore.arrayFaultWithSourceGlobalID( + aGlobalID, aRelationshipKey, this ); + } + else + if ( value instanceof NSArray ) + { + result = (NSArray) value; + } + else // not NSArray + if ( value instanceof Collection ) + { + // convert to NSArray + result = new NSArray( (Collection) value ); + } + else + { + throw new WotonomyException( + "Relationship key did not return a collection: " + + aGlobalID + " : " + aRelationshipKey ); + } + } + + // if our context is not the specified context + if ( aContext != this ) + { + result = (NSArray) clone( this, result, aContext ); + } + + return result; + } + + /** + * Returns a snapshot of the specified object as it + * existed when it was last read or committed to the + * parent object store. + */ + public NSDictionary committedSnapshotForObject ( + Object anObject ) + { + byte[] snapshot = (byte[]) + registrar.getCommitSnapshot( anObject ); + if ( snapshot == null ) + { + // this object not modified: take a current snapshot + snapshot = takeSnapshot( anObject ); + } + return convertSnapshotToDictionary( snapshot ); + } + + /** + * Returns a snapshot of the specified object as it + * existed before the edits triggered by the current + * event loop were processed. + */ + public NSDictionary currentEventSnapshotForObject ( + Object anObject ) + { + byte[] result = (byte[]) + registrar.getCurrentSnapshot( anObject ); + if ( result == null ) + { + return committedSnapshotForObject( anObject ); + } + return convertSnapshotToDictionary( result ); + } + + /** + * Returns the delegate for this editing context, + * or null if no delegate has been set. + */ + public Object delegate () + { + if ( delegate == null ) return null; + return delegate.get(); + } + + /** + * Deletes the specified object from this editing context. + * The editing context marks the object as deleted and + * will notify the parent store when changes are committed. + */ + public void deleteObject ( + Object anObject ) + { + willChange(); + + int i; + // remove from added objects if necessary + i = insertedObjects.indexOfIdenticalObject( anObject ); + if ( i != NSArray.NotFound ) + { + insertedObjects.removeObjectAtIndex( i ); + + // if in the inserted objects buffer + int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + // remove from inserted objects buffer + insertedObjectsBuffer.removeObjectAtIndex( index ); + } + + // now forget the object ever existed. + forgetObject( anObject ); + + // we're done + return; + } + else // otherwise add to deleted objects list + { + deletedObjects.addObject( anObject ); + } + + // remove from updated objects if necessary + i = updatedObjects.indexOfIdenticalObject( anObject ); + if ( i != NSArray.NotFound ) + { + updatedObjects.removeObjectAtIndex( i ); + } + + // add to buffer + deletedObjectsBuffer.addObject( anObject ); + deletedIDsBuffer.addObject( globalIDForObject( anObject ) ); + } + + /** + * Returns a read-only List of all objects marked as deleted + * in this editing context. + */ + public NSArray deletedObjects () + { + return deletedObjectsProxy; + } + + /** + * Called by child editing contexts when they no longer + * need to track the specified id. + * This implementation forwards the call to the parent store. + */ + public void editingContextDidForgetObjectWithGlobalID ( + EOEditingContext aContext, + EOGlobalID aGlobalID ) + { + parentStore.editingContextDidForgetObjectWithGlobalID( + aContext, aGlobalID ); + } + + /** + * Returns a read-only List of registered editors of this + * editing context. + */ + public NSArray editors () + { + NSMutableArray result = new NSMutableArray(); + Object o; + Iterator i = editorSet.iterator(); + while ( i.hasNext() ) + { + o = ((WeakReference)i.next()).get(); + if ( o != null ) + { + result.addObject( o ); + } + else + { + i.remove(); + } + } + return result; + } + +/* + public static void encodeObjectWithCoder ( + Object anObject, + NSCoder aCoder ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + + /** + * Returns the object for the specified id. + * If the object is registered in in this context + * but not in the specified context, + * this implementation will create a copy of the object + * and register it in the specified context. + * Otherwise it will forward the call to the parent + * object store. + */ + public /*EOEnterpriseObject*/ Object faultForGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext ) + { + Object result = registrar.objectForGlobalID( aGlobalID ); + + // if not registered in our context + if ( result == null ) + { + // get the object registered into our context + result = parentStore.faultForGlobalID( aGlobalID, this ); + } + + // if our context is not the specified context + if ( aContext != this ) + { + result = registerClone( aGlobalID, this, result, aContext ); + } + + return result; + } + + /** + * Returns a fault representing an object of + * the specified entity type with values from + * the specified dictionary. + * This implementation calls faultForRawRow + * on the parent store. + */ + public Object faultForRawRow ( + Map aDictionary, + String anEntityName ) + { + return parentStore.faultForRawRow( + aDictionary, anEntityName, this ); + } + + /** + * Returns a fault representing an object of + * the specified entity type with values from + * the specified dictionary. The fault should + * belong to the specified editing context. + * This implementation forwards the call to + * the parent store. + */ + public /*EOEnterpriseObject*/ Object faultForRawRow ( + Map aDictionary, + String anEntityName, + EOEditingContext aContext ) + { + return parentStore.faultForRawRow( + aDictionary, anEntityName, aContext ); + } + + /** + * Returns the fetch timestamp for this editing context. + */ + public double fetchTimestamp () + { + return fetchTimestamp; + } + + /** + * Unregisters the specified object from this editing context, + * removing all references to it. Use this method to remove + * an object from the context without marking it for deletion. + */ + public void forgetObject ( + Object anObject ) + { + EOGlobalID id = registrar.globalIDForObject( anObject ); + if ( id == null ) + { + System.err.println( + "EOEditingContext.forgetObject: not registered: " + anObject ); + return; + } + + // unregister object + registrar.forgetObject( anObject ); + + // remove from all, inserted, updated, and deleted lists + int index; + index = updatedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + updatedObjects.removeObjectAtIndex( index ); + } + index = insertedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + insertedObjects.removeObjectAtIndex( index ); + return; + } + index = deletedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + deletedObjects.removeObjectAtIndex( index ); + return; + } + + // notify parent context + parentStore.editingContextDidForgetObjectWithGlobalID( this, id ); + } + + /** + * Returns the id for the specified object, or null + * if the object is not registered in this context. + */ + public EOGlobalID globalIDForObject ( + Object anObject ) + { + return registrar.globalIDForObject( anObject ); + } + + /** + * Returns an array of ids for an array of objects. + */ + private NSArray globalIDsForObjects( + List anObjectList ) + { + NSMutableArray result = new NSMutableArray(); + Iterator it = anObjectList.iterator(); + while ( it.hasNext() ) + { + result.add( globalIDForObject( it.next() ) ); + } + return result; + } + + /** + * Returns whether this editing context has changes that + * have not yet been committed to the parent object store. + */ + public boolean hasChanges () + { + if ( updatedObjects.count() > 0 ) return true; + if ( insertedObjects.count() > 0 ) return true; + if ( deletedObjects.count() > 0 ) return true; + return false; + } + + /** + * Given a newly instantiated object, this method + * initializes its properties to values appropriate + * for the specified id. The object should already + * belong to the specified editing context. + * This method is called to populate faults. + * This implementation will try to apply the values + * from an object with a matching id in this editing + * context if possible, calling to the parent object + * store only if such an object is not found. + */ + public void initializeObject ( + /*EOEnterpriseObject*/ Object anObject, + EOGlobalID aGlobalID, + EOEditingContext aContext ) + { + Object existingObject = registrar.objectForGlobalID( aGlobalID ); + + // if not registered in our context + if ( existingObject == null ) + { + // get the object registered into our context + existingObject = parentStore.faultForGlobalID( aGlobalID, this ); + } + + if ( aContext == this ) + { + // initialize the object + parentStore.initializeObject( + /*(EOEnterpriseObject)*/existingObject, aGlobalID, this ); + } + else // ( aContext != this ) + { + // translates child relationships + copy( this, existingObject, aContext, anObject ); + } + + aContext.registrar.setCommitSnapshot( anObject, null ); + aContext.registrar.setCurrentSnapshot( anObject, null ); + } + + /** + * Inserts the specified object into this editing context. + * This implementation calls insertObjectWithGlobalID + * with an EOTemporaryGlobalID. + */ + public void insertObject ( Object anObject ) + { + insertObjectWithGlobalID( + anObject, new EOTemporaryGlobalID() ); + } + + /** + * Inserts the specified object into this editing context + * with the specified id, which is expected to be a + * temporary id. + */ + public void insertObjectWithGlobalID ( + Object anObject, + EOGlobalID aGlobalID ) + { + willChange(); + + // if this object was marked for deletion + int index = deletedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + // don't need to re-register: just update the lists + + // remove from deleted list + deletedObjects.removeObjectAtIndex( index ); + + // if in the deleted ids buffer + index = deletedIDsBuffer.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + // remove from deleted ids buffer + deletedIDsBuffer.removeObjectAtIndex( index ); + } + + // if in the deleted objects buffer + index = deletedObjectsBuffer.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) + { + // remove from deleted objects buffer + deletedObjectsBuffer.removeObjectAtIndex( index ); + } + else // not in the deleted objects buffer + { + // add to the inserted objects buffer + insertedObjectsBuffer.addObject( anObject ); + } + + // we're done + return; + } + + // make sure object is not already in editing context + if ( objectForGlobalID( aGlobalID ) != null ) + { + throw new WotonomyException( + "Tried to insert but object was already registered:" + + aGlobalID ); + } + + // register object + recordObject( anObject, aGlobalID ); + + // add to inserted list + insertedObjects.addObject( anObject ); + // add to buffer + insertedObjectsBuffer.addObject( anObject ); + } + + /** + * Returns a read-only List of the objects that have been + * inserted into this editing context. + */ + public NSArray insertedObjects () + { + return insertedObjectsProxy; + } + + /** + * Turn all objects in this editing context into faults, + * so that they will be fetched the next time they are + * accessed, and calls invalidateObjectsWithGlobalIDs + * on the parent object store. + */ + public void invalidateAllObjects () + { + // register change so processRecentChanges is called + willChange(); + + invalidateAllObjectsQuietly(); + + // post notification + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification( + InvalidatedAllObjectsInStoreNotification, this ) ); + } + + /** + * Only refaults all objects, does not notify will change + * nor post notification, but does call parent store. + * Called by invalidateAllObjects() and handleNotification(). + */ + private void invalidateAllObjectsQuietly() + { + // remember the ids + NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() ); + + // track of discarded IDs (from inserted objects) + NSMutableArray discardedIDs = new NSMutableArray(); + + // refault all objects + EOGlobalID id; + Object o; + Enumeration e = ids.objectEnumerator(); + while ( e.hasMoreElements() ) + { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID( id ); + + // some objects may have been manually discarded + if ( o != null ) + { + // don't refault newly inserted objects + if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) + { + refaultObject( o, id, this ); + } + else + { + // discard inserted objects + forgetObject( o ); + discardedIDs.add( id ); + } + invalidatedObjectsBuffer.add( o ); + } + invalidatedIDsBuffer.add( id ); + } + ids.removeAll( discardedIDs ); + + // call to parent store (should call this after posting instead?) + isInvalidating = true; + parentStore.invalidateObjectsWithGlobalIDs( ids ); + isInvalidating = false; + } + + /** + * Turns the objects with the specified ids into faults, + * so that they will be fetched the next time they are + * accessed, and forwards the call to the parent object store. + */ + public void invalidateObjectsWithGlobalIDs ( + List anArray ) + { + // register change so processRecentChanges is called + willChange(); + + // call to parent to invalidate objects + parentStore.invalidateObjectsWithGlobalIDs( anArray ); + + Object o; + EOGlobalID id; + Iterator it = anArray.iterator(); + while ( it.hasNext() ) + { + id = (EOGlobalID) it.next(); + if ( id != null ) + { + o = objectForGlobalID( id ); + if ( o != null ) + { + Object result = notifyDelegate( + "editingContextShouldInvalidateObject", + new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class }, + new Object[] { this, o, id } ); + if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) + { + // refault the object + refaultObject( o, id, this ); + invalidatedObjectsBuffer.add( o ); + invalidatedIDsBuffer.add( id ); + } + } + } + else + { + throw new WotonomyException( + "Attempted to invalidate a null global id: " + anArray ); + } + } + + } +/* + public boolean invalidatesObjectsWhenFinalized ( ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } + + public boolean invalidatesObjectsWhenFreed ( ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Returns whether the object referenced by the + * specified id is locked. + * This implementation simply forwards the call to + * the parent object store. + */ + public boolean isObjectLockedWithGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext) + { + return parentStore.isObjectLockedWithGlobalID( + aGlobalID, aContext ); + } + +/* + public void lock () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Locks the specified object in this editing context + * by calling lockObjectWithGlobalID on the parent store. + */ + public void lockObject ( + Object anObject ) + { + parentStore.lockObjectWithGlobalID( + globalIDForObject( anObject ), this ); + } + + /** + * Locks the object referenced by the specified id + * in the specified editing context. + * This implementation simply forwards the call to + * the parent object store. + */ + public void lockObjectWithGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext) + { + parentStore.lockObjectWithGlobalID( + aGlobalID, aContext ); + } + + /** + * Returns whether this editing context attempts to + * lock objects when they are first modified. + */ + public boolean locksObjectsBeforeFirstModification () + { + return lockBeforeModify; + } + + /** + * Returns the message handler for this editing context, + * or null if no message handler has been set. + */ + public Object messageHandler () + { + if ( messageHandler == null ) return null; + return messageHandler.get(); + } + + /** + * Returns the object registered in this editing context + * for the specified id, or null if that id is not + * registered. + */ + public Object objectForGlobalID ( + EOGlobalID aGlobalID ) + { + return registrar.objectForGlobalID( aGlobalID ); + } + + /** + * Returns a read-only List of objects associated with the object + * with the specified id for the specified property + * relationship. This method may not return an array fault + * because array faults call this method to fetch on demand. + * All objects must be registered the specified editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public NSArray objectsForSourceGlobalID ( + EOGlobalID aGlobalID, + String aRelationshipKey, + EOEditingContext aContext ) + { +//System.out.println( "EOEditingContext.objectsForSourceGlobalID: " +//+ aGlobalID + " : " + aRelationshipKey ); + + NSArray result = null; + +if ( aContext == this ) +{ + throw new WotonomyException( "Assert failed: calling objectsForSourceGlobalID on ourself." ); +} + Object source = registrar.objectForGlobalID( aGlobalID ); + + // if not registered in our context + if ( source == null ) + { + // get the object registered into our context + result = parentStore.objectsForSourceGlobalID( + aGlobalID, aRelationshipKey, this ); + } + else // source is registered in our context + { + // get existing value + Object value; + if ( source instanceof EOKeyValueCoding ) + { + value = ((EOKeyValueCoding)source).storedValueForKey( + aRelationshipKey ); + } + else // handle directly + { + value = EOKeyValueCodingSupport.storedValueForKey( + source, aRelationshipKey ); + } + + // if we don't have a valid value on our object + if ( ( value == null ) + || ( ( value instanceof ArrayFault ) + && ( !((ArrayFault)value).isFetched() ) ) ) + { + // do the same as if the source was null + result = parentStore.objectsForSourceGlobalID( + aGlobalID, aRelationshipKey, this ); + + // set our value since we have it + if ( source instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)source).takeStoredValueForKey( + result, aRelationshipKey ); + } + else // handle directly + { + EOKeyValueCodingSupport.takeStoredValueForKey( + source, result, aRelationshipKey ); + } + } + else + if ( ( value instanceof ArrayFault ) + && ( !((ArrayFault)value).isFetched() ) ) + { + // do the same as if the source was null + result = parentStore.objectsForSourceGlobalID( + aGlobalID, aRelationshipKey, this ); + } + else + if ( value instanceof NSArray ) + { + result = (NSArray) value; + } + else // not NSArray + if ( value instanceof Collection ) + { + // convert to NSArray + result = new NSArray( (Collection) value ); + } + else + { + throw new WotonomyException( + "Relationship key did not return a collection: " + + aGlobalID + " : " + aRelationshipKey ); + } + } + + // if our context is not the specified context + if ( aContext != this ) + { + result = (NSArray) clone( this, result, aContext ); + } + + return result; + } + + /** + * Returns a read-only List of objects the meet the criteria of + * the supplied specification. This method simply calls + * objectsWithFetchSpecification on this editing context + * with this editing context as the parameter. + */ + public NSArray objectsWithFetchSpecification ( + EOFetchSpecification aFetchSpec ) + { + return objectsWithFetchSpecification( aFetchSpec, this ); + } + + /** + * Returns a read-only List of objects the meet the criteria of + * the supplied specification. Faults are not allowed in the array. + * If any objects are already fetched, they should not be + * refetched. All objects should belong to the specified editing context. + * This implementation forwards the call to the parent object + * store, which will register each object in the specified editing + * context only if it does not already exist. + */ + public NSArray objectsWithFetchSpecification ( + EOFetchSpecification aFetchSpec, + EOEditingContext aContext) + { + if ( aContext == this ) + { + Object result = notifyDelegate( + "editingContextShouldFetchObjects", + new Class[] { EOEditingContext.class, EOFetchSpecification.class }, + new Object[] { aContext, aFetchSpec } ); + if ( result instanceof NSArray ) return (NSArray) result; + } + return parentStore.objectsWithFetchSpecification( aFetchSpec, aContext ); + } + + /** + * Returns the parent object store for this editing context. + * The result will not be null. + */ + public EOObjectStore parentObjectStore () + { + return parentStore; + } + + /** + * Updates the inserted, updated, and deleted objects lists, + * and posts notifications about which objects have been changed. + * This method is called at the end of an event loop in which + * objects were modified. This method is additionally called + * by saveChanges() so that any changes in the same event loop + * will be processed correctly before calling to the parent + * object store. + * This implementation updates those lists immediately, but + * only posts notifications when this method is called. + */ + public void processRecentChanges () + { // System.out.println( "EOEditingContext.processRecentChanges: " + invalidatedObjectsBuffer ); + + /* + * This implementation actually updates those lists immediately, + * but keeps a separate buffer of changes in the current event + * loop for the purposes of posting a notification. + * NOTE: to reenable buffering, uncomment lines from this method + * body and reenable the RecentChangesObserver in the constructor. + */ + + // broadcast ObjectsChangedInStoreNotification + // for the benefit of child editing contexts + + boolean postStoreInfo = + ( insertedObjectsBuffer.size() + + updatedObjectsBuffer.size() + + deletedIDsBuffer.size() + + invalidatedIDsBuffer.size() > 0 ); + + NSMutableDictionary storeInfo = new NSMutableDictionary(); + if ( postStoreInfo ) + { + storeInfo.setObjectForKey( + globalIDsForObjects( insertedObjectsBuffer ), + // globalIDsForObjects( insertedObjects ), + EOObjectStore.InsertedKey ); + storeInfo.setObjectForKey( + globalIDsForObjects( updatedObjectsBuffer ), + // globalIDsForObjects( updatedObjects ), + EOObjectStore.UpdatedKey ); + storeInfo.setObjectForKey( + new NSArray( (Collection) deletedIDsBuffer ), + // globalIDsForObjects( deletedObjects ), + EOObjectStore.DeletedKey ); + storeInfo.setObjectForKey( + new NSArray( (Collection) invalidatedIDsBuffer ), + EOObjectStore.InvalidatedKey ); + } + + // broadcast ObjectsChangedInEditingContextNotification + // for the benefit of attached display groups + + boolean postContextInfo = + ( insertedObjectsBuffer.size() + + updatedObjectsBuffer.size() + + deletedObjectsBuffer.size() + + invalidatedObjectsBuffer.size() > 0 ); + + NSMutableDictionary contextInfo = new NSMutableDictionary(); + + if ( postContextInfo ) + { + + contextInfo.setObjectForKey( + new NSArray( (Collection) insertedObjectsBuffer ), + // new NSArray( (Collection) insertedObjects ), + EOObjectStore.InsertedKey ); + contextInfo.setObjectForKey( + new NSArray( (Collection) updatedObjectsBuffer ), + // new NSArray( (Collection) updatedObjects ), + EOObjectStore.UpdatedKey ); + contextInfo.setObjectForKey( + new NSArray( (Collection) deletedObjectsBuffer ), + // new NSArray( (Collection) deletedObjects ), + EOObjectStore.DeletedKey ); + contextInfo.setObjectForKey( + new NSArray( (Collection) invalidatedObjectsBuffer ), + EOObjectStore.InvalidatedKey ); + } + + // update the current snapshots + + Object o; + Iterator it; + it = insertedObjectsBuffer.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + registrar.setCurrentSnapshot( o, takeSnapshot( o ) ); + } + it = updatedObjectsBuffer.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + registrar.setCurrentSnapshot( o, takeSnapshot( o ) ); + } + + // clear buffers + + insertedObjectsBuffer.removeAllObjects(); + updatedObjectsBuffer.removeAllObjects(); + deletedObjectsBuffer.removeAllObjects(); + deletedIDsBuffer.removeAllObjects(); + invalidatedObjectsBuffer.removeAllObjects(); + invalidatedIDsBuffer.removeAllObjects(); + + // post notifications (does order matter?) + + if ( postStoreInfo ) + { + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification( + ObjectsChangedInStoreNotification, this, storeInfo ) ); + } + + if ( postContextInfo ) + { + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification( + ObjectsChangedInEditingContextNotification, this, contextInfo ) ); + } + + } + + /** + * Returns whether this editing context propagates deletes + * immediately after the event that triggered the delete. + * Otherwise, propagation occurs only before commit. + */ + public boolean propagatesDeletesAtEndOfEvent () + { + return propagateDeletesAfterEvent; + } + + /** + * Registers the specified object in this editing context + * for the specified id. This method is called by an object + * store when fetching objects for a display group, or when + * objects are inserted into a display group. + * This implementation will re-register the object under the + * new id if it is already registered under a different id. + */ + public void recordObject ( + Object anObject, + EOGlobalID aGlobalID ) + { + // find state for re-registration + boolean inserted = false; + boolean updated = false; + boolean deleted = false; + + // is the object already registered? + EOGlobalID existingID = globalIDForObject( anObject ); + if ( existingID != null ) + { + // remember object state + int index; + index = insertedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) inserted = true; + index = updatedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) updated = true; + index = deletedObjects.indexOfIdenticalObject( anObject ); + if ( index != NSArray.NotFound ) deleted = true; + // forget the object + forgetObject( anObject ); + } + + // is the global id already in use? + Object existingObject = objectForGlobalID( aGlobalID ); + if ( existingObject != null ) + { + // forget it (don't worry about state?) + forgetObject( existingObject ); + } + + registrar.registerObject( anObject, aGlobalID ); + + // restore state if necessary + if ( inserted ) insertedObjects.addObject( anObject ); + if ( updated ) updatedObjects.addObject( anObject ); + if ( deleted ) deletedObjects.addObject( anObject ); + } + + /** + * Undoes the last undo operation. + */ + public void redo () + { + //TODO: not supported yet + throw new UnsupportedOperationException("Not implemented yet."); + } + + /** + * Refaults this editing context, turning all unmodified + * objects into faults. This implementation calls + * editingContextWillSaveChanges() on all editors, and + * then calls refaultObjects(). + */ + public void refault () + { + fireWillSaveChanges(); + refaultObjects(); + } + + /** + * Refaults the specified object, turning it into a fault + * for the specified global id in the specified context. + */ + public void refaultObject ( + Object anObject, + EOGlobalID aGlobalID, + EOEditingContext aContext) + { + aContext.registrar.setCurrentSnapshot( anObject, null ); + + ignoreChanges = true; + parentStore.refaultObject( anObject, aGlobalID, aContext ); + ignoreChanges = false; + + // remove from updated objects if necessary + int i = updatedObjects.indexOfIdenticalObject( anObject ); + if ( i != NSArray.NotFound ) + { + updatedObjects.removeObjectAtIndex( i ); + } + + // add to invalidated notification queue + invalidatedObjectsBuffer.addObject( anObject ); + invalidatedIDsBuffer.addObject( aGlobalID ); + } + + /** + * Turns all unmodified objects into faults, calling + * processRecentChanges() and then refaultObject() for + * each unmodified object. + */ + public void refaultObjects () + { + // is this call really needed? + // processRecentChanges(); + + Object o; + EOGlobalID id; + Iterator it = registeredObjects().iterator(); + while ( it.hasNext() ) + { + o = it.next(); + if ( ( updatedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) + && ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) + && ( deletedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) ) + { + id = globalIDForObject( o ); + refaultObject( o, id, this ); + } + } + } + + /** + * Calls editingContextWillSaveChanges() on all editors, + * and then calls invalidateAllObjects(). + */ + public void refetch () + { + fireWillSaveChanges(); + invalidateAllObjects(); + } + + /** + * Returns a read-only List of all objects registered in this + * editing context. + */ + public NSArray registeredObjects () + { + return registrar.registeredObjects(); + } + + /** + * Unregisters the specified editor with this editing context. + */ + public void removeEditor ( Object anObject ) + { + if ( anObject == null ) return; + + Object o; + Iterator i = editorSet.iterator(); + while ( i.hasNext() ) + { + o = ((WeakReference)i.next()).get(); + if ( ( o == null ) || ( o == anObject ) ) + { + i.remove(); + } + } + } + + /** + * Unregisters all objects from this editing context, + * and resets the fetch timestamp. + */ + public void reset () + { + Iterator it = registeredObjects().iterator(); + while ( it.hasNext() ) + { + forgetObject( it.next() ); + } + fetchTimestamp = 0; //FIXME: reset timestamp properly + } + + /** + * Reverts the objects in this editing context to + * their original state. + * Calls editingContextWillSaveChanges on all editors, + * discards all inserted objects, restores deleted + * objects, and applies the fetch snapshot to all + * registered objects. + */ + public void revert () + { + willChange(); + fireWillSaveChanges(); + + Iterator it; + + // forget inserted objects + it = new NSArray( insertedObjects ).iterator(); + while ( it.hasNext() ) + { + forgetObject( it.next() ); + } + + EOGlobalID id; + Object o; + byte[] snapshot; + + // re-initialize updated objects + it = new NSArray( updatedObjects ).iterator(); + while ( it.hasNext() ) + { + o = it.next(); + snapshot = (byte[]) registrar.getCommitSnapshot( o ); + if ( snapshot != null ) + { + applySnapshot( snapshot, o ); + } + registrar.setCommitSnapshot( o, null ); + updatedObjectsBuffer.addObject( o ); + } + + // re-initialize deleted objects + it = new NSArray( deletedObjects ).iterator(); + while ( it.hasNext() ) + { + o = it.next(); + snapshot = (byte[]) registrar.getCommitSnapshot( o ); + if ( snapshot != null ) + { + applySnapshot( snapshot, o ); + } + registrar.setCommitSnapshot( o, null ); + updatedObjectsBuffer.addObject( o ); + } + + // reset lists + insertedObjects.removeAllObjects(); // unneccessary? + deletedObjects.removeAllObjects(); + updatedObjects.removeAllObjects(); + + // post notification + processRecentChanges(); + } + + /** + * Returns the root object store, which is the parent + * of all parent object stores of this editing context. + */ + public EOObjectStore rootObjectStore () + { + EOObjectStore parent = parentObjectStore(); + while ( parent instanceof EOEditingContext ) + { + parent = ((EOEditingContext)parent).parentObjectStore(); + } + return parent; + } + + /** + * Calls editingContextWillSaveChanges on all editors, + * and commits all changes in this editing context to + * the parent editing context by calling + * saveChangesInEditingContext to the parent. + * Then posts EditingContextDidSaveChangeNotification. + */ + public void saveChanges () + { +//System.out.println( "EOEditingContext.saveChanges: " + this ); + willChange(); + + // process any changes + processRecentChanges(); + + // set up user info for notification to be posted. + NSMutableDictionary userInfo = new NSMutableDictionary(); + userInfo.setObjectForKey( + new NSArray( (Collection) insertedObjects ), + EOObjectStore.InsertedKey ); + userInfo.setObjectForKey( + new NSArray( (Collection) updatedObjects ), + EOObjectStore.UpdatedKey ); + userInfo.setObjectForKey( + new NSArray( (Collection) deletedObjects ), + EOObjectStore.DeletedKey ); + + // notify the editors + fireWillSaveChanges(); + + // notify the delegate + notifyDelegate( + "editingContextWillSaveChanges", + new Class[] { EOEditingContext.class }, + new Object[] { this } ); + + // needed for notification handling + isInvalidating = true; + try + { + // ask parent to save us + parentStore.saveChangesInEditingContext( this ); + } + catch ( RuntimeException e ) + { + // unset save flag and rethrow + isInvalidating = false; + throw e; + } + isInvalidating = false; + + // no exceptions: proceed! + + Object o, key; + Iterator it; + + // update the committed snapshots + it = insertedObjects.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + registrar.setCommitSnapshot( o, null ); + registrar.setCurrentSnapshot( o, null ); + } + it = updatedObjects.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + registrar.setCommitSnapshot( o, null ); + registrar.setCurrentSnapshot( o, null ); + } + + // clear the lists + updatedObjects.removeAllObjects(); + insertedObjects.removeAllObjects(); + it = new NSArray( deletedObjects() ).iterator(); + while ( it.hasNext() ) + { // parent is doing this as well? + forgetObject( it.next() ); + } + + // post notification + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification( + EditingContextDidSaveChangesNotification, this, userInfo ) ); + } + + /** + * Commits all changes in the specified editing context + * to this one. Called by child editing contexts in + * their saveChanges() method. + */ + public void saveChangesInEditingContext ( + EOEditingContext aContext) + { + Object o; + Iterator it; + + // process deletes + List deletes = new NSArray( aContext.deletedObjects() ); + it = deletes.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + EOGlobalID id = aContext.globalIDForObject( o ); + Object localVersion = objectForGlobalID( id ); + if ( localVersion == null ) + { + // make a local copy and register it + localVersion = registerClone( id, aContext, o, this ); + if ( localVersion == null ) + { + throw new WotonomyException( + "Deleted object could not be serialized: " + + id + " : " + o ); + } + } + else // we have a local version, copy changes + { + copy( aContext, o, this, localVersion ); + // copy marks the object as updated: will be on both lists + } + // delete our copy -- marks context as changed + deleteObject( localVersion ); + } + + // process inserts - all inserts are new objects + List inserts = new NSArray( aContext.insertedObjects() ); + it = inserts.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + // make a local copy and register it + EOGlobalID id = aContext.globalIDForObject( o ); + willChange(); // need to mark editing context as changed + Object localVersion = registerClone( id, aContext, o, this ); + if ( localVersion == null ) + { + throw new WotonomyException( + "Inserted object could not be serialized: " + + o ); + } + // insert our copy manually so a new id is not generated + insertedObjects.addObject( localVersion ); + insertedObjectsBuffer.addObject( localVersion ); + } + + // process updates + List updates = new NSArray( aContext.updatedObjects() ); + it = updates.iterator(); + while ( it.hasNext() ) + { + willChange(); // need to mark editing context as changed + o = it.next(); + EOGlobalID id = aContext.globalIDForObject( o ); + Object localVersion = objectForGlobalID( id ); + if ( localVersion == null ) + { + // make a local copy and register it + localVersion = registerClone( id, aContext, o, this ); + if ( localVersion == null ) + { + throw new WotonomyException( + "Updated object could not be serialized: " + + id + " : " + o ); + } + if ( id.isTemporary() ) + { + // mark this object as inserted + insertedObjects.addObject( localVersion ); + insertedObjectsBuffer.addObject( localVersion ); + } + else + { + // mark this object as updated + updatedObjects.addObject( localVersion ); + + // notify of update only if not on deleted list + if ( deletedObjectsBuffer.indexOfIdenticalObject( + localVersion ) == NSArray.NotFound ) + { + updatedObjectsBuffer.addObject( localVersion ); + } + } + } + else // we have a local version, copy changes + { + copy( aContext, o, this, localVersion ); + // copy marks the object as updated + } + } + + } + + /** + * Sets the delegate for this editing context. + * Note: this implementation retains only a + * weak reference to the specified object. + */ + public void setDelegate ( + Object anObject ) + { + if ( anObject == null ) delegate = null; + delegate = new WeakReference( anObject ); + } + + /** + * Sets the fetch timestamp for this editing context. + */ + public void setFetchTimestamp ( + double aDouble ) + { + fetchTimestamp = aDouble; + } +/* + public void setInvalidatesObjectsWhenFinalized ( + boolean invalidatesObjects ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } + + public void setInvalidatesObjectsWhenFreed ( + boolean invalidatesObjects ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Sets whether this editing context attempts to + * lock objects when they are first modified. + * Default is false. + */ + public void setLocksObjectsBeforeFirstModification ( + boolean locksObjects ) + { + lockBeforeModify = locksObjects; + } + + /** + * Sets the message handler for this editing context. + * Note: this implementation retains only a + * weak reference to the specified object. + */ + public void setMessageHandler ( + Object anObject ) + { + if ( anObject == null ) messageHandler = null; + messageHandler = new WeakReference( anObject ); + } + + /** + * Sets whether this editing context propagates deletes + * immediately after the event that triggered the delete. + * Otherwise, propagation occurs only before commit. + * Default is true. + */ + public void setPropagatesDeletesAtEndOfEvent ( + boolean propagatesDeletes ) + { + propagateDeletesAfterEvent = propagatesDeletes; + } +/* + public void setSharedEditingContext ( + EOSharedEditingContext aSharedEditingContext ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Sets whether validation is stopped after the + * first error occurs. Otherwise, validation will + * continue for all other objects. + * Default is true. + */ + public void setStopsValidationAfterFirstError ( + boolean stopsValidation ) + { + stopValidationAfterError = stopsValidation; + } + + /** + * Sets the undo manager to be used for this context. + * Note: This is currently javax.swing.undo.UndoManager, + * until we have a implementation of NSUndoManager. + */ +/* + public void setUndoManager ( + UndoManager anUndoManager ) + { + undoManager = anUndoManager; + } +*/ +/* + public EOSharedEditingContext sharedEditingContext () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Returns whether validation is stopped after the + * first error occurs. Otherwise, validation will + * continue for all other objects. + */ + public boolean stopsValidationAfterFirstError () + { + return stopValidationAfterError; + } + + /** + * Reverts the last change on the undo stack. + */ + public void undo () + { + //TODO: not supported yet + throw new UnsupportedOperationException("Not implemented yet."); + } +/* + public NSUndoManager undoManager () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ +/** + public void unlock () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + /** + * Returns a read-only list of all objects marked as modified, + * but not inserted or deleted, in this editing context. + */ + public NSArray updatedObjects () + { + return updatedObjectsProxy; + } + + /** + * Notify editors of changes. + */ + private void fireWillSaveChanges() + { + Object o = null; + Iterator i = editors().iterator(); + while ( i.hasNext() ) + { + try + { + o = i.next(); + NSSelector.invoke( "editingContextWillSaveChanges", + new Class[] { EOEditingContext.class }, o, this ); + } + catch ( NoSuchMethodException e ) + { + // ignore: not implemented + } + catch ( Exception exc ) + { + // log to standard error + System.err.println( "Error while notifying editor of pending save: " + o ); + exc.printStackTrace(); + } + } + } + + /** + * Handles notifications from parent store, looking for + * InvalidatedAllObjectsInStoreNotification and + * ObjectsChangedInStoreNotification. + * The former causes all objects in this store to be + * invalidated. + * The latter refaults the invalidated ids, merges changes + * from the updated ids, and forgets the deleted ids, then + * posts a ObjectsChangedInStoreNotification. + * Note: This method is not in the public specification. + */ + public void handleNotification( NSNotification aNotification ) + { // System.out.println( "EOEditingContext: " + this + " : " + aNotification ); + + willChange(); + if ( InvalidatedAllObjectsInStoreNotification + .equals( aNotification.name() ) ) + { + refaultObjects(); + + // relay notification + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification( + InvalidatedAllObjectsInStoreNotification, this ) ); + } + else + if ( EOGlobalID.GlobalIDChangedNotification + .equals( aNotification.name() ) ) + { + NSDictionary userInfo = aNotification.userInfo(); + + // if any keys in userInfo are registered ids, + // re-register with new permanent values. + + Object o; + EOGlobalID id; + Enumeration e = userInfo.keyEnumerator(); + while ( e.hasMoreElements() ) + { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID( id ); + if ( o != null ) + { + // record object is assumed to handle key updates + recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) ); + } + } + } + else + if ( EOObjectStore.ObjectsChangedInStoreNotification + .equals( aNotification.name() ) ) + { + //System.out.println( "EOEditingContext.handleNotification: " + aNotification + " : " + this ); + // post so child contexts are notified + willChange(); + + Object o; + EOGlobalID id; + Enumeration e; + NSDictionary userInfo = aNotification.userInfo(); + + // inserted objects are ignored + + // existing deleted objects are removed + NSArray deletes = (NSArray) userInfo.objectForKey( + EOObjectStore.DeletedKey ); + e = deletes.objectEnumerator(); + while ( e.hasMoreElements() ) + { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID( id ); + if ( o != null ) + { + //System.out.println( "EOEditingContext: deleted: " + id ); + forgetObject( o ); + deletedObjectsBuffer.addObject( o ); + deletedIDsBuffer.addObject( id ); + } + } + + // existing updated objects are merged + NSArray updates = (NSArray) userInfo.objectForKey( + EOObjectStore.UpdatedKey ); + e = updates.objectEnumerator(); + while ( e.hasMoreElements() ) + { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID( id ); + if ( o != null ) + { + //System.out.println( "EOEditingContext: updated: " + id ); + if ( updatedObjects // only update if unchanged + .indexOfIdenticalObject( o ) == NSArray.NotFound ) + { + refaultObject( o, id, this ); + updatedObjectsBuffer.addObject( o ); + } + else + { + // notify user and/or merge + handleUpdateConflict( id, o ); + } + } + } + + // existing invalidated objects are refaulted + NSArray invalidates = (NSArray) userInfo.objectForKey( + EOObjectStore.InvalidatedKey ); + e = invalidates.objectEnumerator(); + while ( e.hasMoreElements() ) + { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID( id ); + if ( o != null ) + { + if ( updatedObjects // only invalidate if unchanged + .indexOfIdenticalObject( o ) == NSArray.NotFound ) + { + refaultObject( o, id, this ); + } + else + { + // notify user and/or merge + handleUpdateConflict( id, o ); + } + if ( invalidatedObjectsBuffer + .indexOfIdenticalObject( o ) == NSArray.NotFound ) + { + invalidatedObjectsBuffer.addObject( o ); + } + if ( invalidatedIDsBuffer + .indexOfIdenticalObject( id ) == NSArray.NotFound ) + { + invalidatedIDsBuffer.addObject( id ); + } + } + } + + } + } + + /** + * Called by handleNotification to resolve the case where we have + * received notification that another user or context has updated + * an object that is currently marked as edited in this context. + * This implementation first asks the delegate if it should merge. + * If true or no delegate, the changes are merged. True or false, + * the delegate is then sent editingContextDidMergeChanges. + */ + private void handleUpdateConflict( EOGlobalID anID, Object anObject ) + { + // if we're causing the invalidation, ignore + // (this is probably better handled by the caller...) + if ( isInvalidating ) + { + ignoreChanges = true; + parentStore.refaultObject( anObject, anID, this ); + ignoreChanges = false; + return; + } + + Boolean result = (Boolean) notifyDelegate( + "editingContextShouldMergeChangesForObject", + new Class[] { EOEditingContext.class, Object.class }, + new Object[] { this, anObject } ); + + if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) + { + // do merge + mergeExternalChanges( anID, anObject ); + } + else // Boolean.FALSE + { + // do nothing: don't lose the user's changes + } + + // notify merge did happen + notifyDelegate( + "editingContextDidMergeChanges", + new Class[] { EOEditingContext.class }, + new Object[] { this } ); + } + + /** + * For the currently modified object with the specified global id, + * this method merges changes with the updated version in the parent + * object store. This implementation looks at the fetch snapshot + * to determine which changes where made by the user, fetches the + * updated version of the object, and then determine what external + * changes were made. If the changes do not overlap, the original + * changes are applied to the updated version. If there is a conflict, + * notifies the user of the conflict. + */ + private boolean mergeExternalChanges( EOGlobalID anID, Object anObject ) + { + try + { + Iterator i; + Object key; + + // get fetch snapshot + Map fetchSnapshot = committedSnapshotForObject( anObject ); + + // get current snapshot + Map currentSnapshot = currentEventSnapshotForObject( anObject ); + + // diff against fetch snapshot + Map currentDiff = new HashMap(); + i = currentSnapshot.keySet().iterator(); + while ( i.hasNext() ) + { + key = i.next(); + if ( ! currentSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) ) + { + currentDiff.put( key, currentSnapshot.get( key ) ); + } + } + + // refault + ignoreChanges = true; + parentStore.refaultObject( anObject, anID, this ); + ignoreChanges = false; + + // get updated snapshot + Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) ); + + // diff against fetch snapshot + Map updatedDiff = new HashMap(); + i = updatedSnapshot.keySet().iterator(); + while ( i.hasNext() ) + { + key = i.next(); + if ( ! updatedSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) ) + { + updatedDiff.put( key, updatedSnapshot.get( key ) ); + } + } + + // determine if there's a conflict + boolean proceed = true; + Set updatedKeys = updatedDiff.keySet(); + i = currentDiff.keySet().iterator(); + while ( i.hasNext() ) + { + if ( updatedKeys.contains( i.next() ) ) + { + proceed = false; + break; + } + } + + // if no conflicts, apply original diff to current object and exit + if ( proceed ) + { + KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff ); + return true; // exit! + } + } + catch ( Exception exc ) + { + // log error to standard out + exc.printStackTrace(); + } + + // notify user we're unable to merge + notifyMessageHandler( MessageChangeConflict + anObject ); + return false; + } + + /** + * Sends the specified message to the message handler. + */ + private void notifyMessageHandler( String aMessage ) + { + Object handler = null; + try + { + handler = messageHandler(); + if ( handler == null ) return; + NSSelector.invoke( "editingContextPresentErrorMessage", + new Class[] { EOEditingContext.class, String.class }, + handler, this, aMessage ); + } + catch ( NoSuchMethodException e ) + { + // ignore: not implemented + } + catch ( Exception exc ) + { + // log to standard error + System.err.println( + "Error while notifying message handler: " + + handler + " : " + aMessage ); + exc.printStackTrace(); + } + } + + /** + * Sends the specified message to the delegate. + * Returns the return value of the method, + * or null if no return value or no delegate + * or no implementation. + */ + private Object notifyDelegate( + String aMethodName, Class[] types, Object[] params ) + { + try + { + Object delegate = delegate(); + if ( delegate == null ) return null; + return NSSelector.invoke( + aMethodName, types, delegate, params ); + } + catch ( NoSuchMethodException e ) + { + // ignore: not implemented + } + catch ( Exception exc ) + { + // log to standard error + System.err.println( + "Error while messaging delegate: " + + delegate + " : " + aMethodName ); + exc.printStackTrace(); + } + + return null; + } + + // interface EOObserving + + /** + * Implementation of the EOObserving interface. + * Called before objects are modified. + */ + public void objectWillChange ( + Object anObject ) + { + if ( ignoreChanges ) return; +//NSNotificationCenter.defaultCenter().postNotification( "objectWillChange", this, new NSDictionary( "object", anObject ) ); +//new RuntimeException().printStackTrace(); + + willChange(); + + // mark as updated if not marked already + int i = updatedObjects.indexOfIdenticalObject( anObject ); + if ( i == NSArray.NotFound ) + { + // don't mark inserted objects as updated + i = insertedObjects.indexOfIdenticalObject( anObject ); + if ( i == NSArray.NotFound ) + { + i = deletedObjects.indexOfIdenticalObject( anObject ); + if ( i == NSArray.NotFound ) + { + // add object + updatedObjects.addObject( anObject ); + + // record revert snapshot + registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) ); + } + } + } + + // add to buffer + if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject ) + == NSArray.NotFound ) + { + updatedObjectsBuffer.addObject( anObject ); + } + } + + // static methods + + public static double defaultFetchTimestampLag () + { + return defaultFetchTimestampLag; + } + + /** + * Returns the default parent object store for all + * object stores created with the parameterless + * constructor. + */ + public static EOObjectStore defaultParentObjectStore () + { + return defaultParentObjectStore; + } + +/* + public static Object initObjectWithCoder ( + Object anObject, + NSCoder aCoder ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + + /** + * Returns whether editing contexts are configured to retain strong + * references to their registered objects. If false, editing contexts + * will only retain weak references to their registered objects. + */ + public static boolean instancesRetainRegisteredObjects() + { + return retainsRegisteredObjects; + } + + /** + * Sets the global default fetch timestamp lag. + */ + public static void setDefaultFetchTimestampLag ( + double aDouble ) + { + defaultFetchTimestampLag = aDouble; + } + + /** + * Sets the global default parent object store, + * used for the parameterless constructor. + */ + public static void setDefaultParentObjectStore ( + EOObjectStore anObjectStore ) + { + defaultParentObjectStore = anObjectStore; + } + + public static void setInstancesRetainRegisteredObjects ( + boolean retainsObjects ) + { + retainsRegisteredObjects = retainsObjects; + } + +/* + public static void setSubstitutionEditingContext ( + EOEditingContext aContext) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } + + public static void setUsesContextRelativeEncoding ( + boolean usesRelativeEncoding ) + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } + + public static EOEditingContext substitutionEditingContext () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } + + public static boolean usesContextRelativeEncoding () + { + throw new net.wotonomy.util.WotonomyException("Not implemented yet."); + } +*/ + + public String toString() + { + return "[EOEditingContext@"+Integer.toHexString(System.identityHashCode(this))+":"+ + " inserted="+idsForObjects(insertedObjects)+ + " updated="+idsForObjects(updatedObjects)+ + " deleted="+idsForObjects(deletedObjects)+ + " registered="+registrar.registeredGlobalIDs()+" ]"; + } + private List idsForObjects( List objects ) + { + List result = new LinkedList(); + Iterator i = objects.iterator(); + while ( i.hasNext() ) result.add( globalIDForObject( i.next() ) ); + return result; + } + + // snapshots + + /** + * Returns a NSDictionary containing only the mutable properties + * for the specified object and deep clones of their values. + * Nulls are represented by NSNull.nullValue(). + */ + private byte[] takeSnapshot( Object anObject ) + { // System.out.println( "takeSnapshot: " + anObject ); + return KeyValueCodingUtilities.freeze( anObject, this, anObject, true ); + } + + /** + * Applies the map of properties and values to the + * specified object. Null values for properties must + * be represented by the NSNull.nullValue(). + * Posts a willChange event before applying changes. + */ + private void applySnapshot( byte[] aSnapshot, Object anObject ) + { + // must clone snapshot to avoid changing existing snapshot + NSDictionary values = convertSnapshotToDictionary( aSnapshot ); + +//System.out.println( "applySnapshot: " + aSnapshot + " : " + values ); +//ignoreChanges = true; + willChange(); + KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values ); +//ignoreChanges = false; + } + + /** + * Snapshots are stored internally in binary format, + * but exposed to the user as NSDictionaries. + */ + private NSDictionary convertSnapshotToDictionary( byte[] aSnapshot ) + { + // get the object + Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true ); + // get all keys for this object + EOClassDescription classDesc = + EOClassDescription.classDescriptionForClass( clone.getClass() ); + List keys = new LinkedList(); + keys.addAll( classDesc.attributeKeys() ); + keys.addAll( classDesc.toOneRelationshipKeys() ); + keys.addAll( classDesc.toManyRelationshipKeys() ); + + return KeyValueCodingUtilities.valuesForKeys( clone, keys ); + } + /** + * Creates a deep clone of the specified object. + * (Object.clone() only creates a shallow clone.) + * Returns null if operation fails. + */ + static private Object clone( + EOEditingContext aSourceContext, + Object aSource, + EOEditingContext aDestinationContext ) + { // System.out.println( "clone: " + aSource ); + return KeyValueCodingUtilities.clone( + aSourceContext, aSource, aDestinationContext ); + } + + /** + * Creates a deep clone of the specified object. + * but does not transpose references. This allows + * us to register an object before transposing + * references so that child objects will be able + * to resolve references to their parent. + * After recording the object, we copy the source + * object into the clone, which does transpose + * and resolve properly. + * Returns null if operation fails. + */ + static private Object registerClone( + EOGlobalID aGlobalID, + EOEditingContext aSourceContext, + Object aSource, + EOEditingContext aDestinationContext ) + { + // while we'd like to just transpose/clone at the same time + // we must record a clone without transposing: this + // avoids a endless loop if the object graph has a cycle + Object clone = KeyValueCodingUtilities.thaw( + KeyValueCodingUtilities.freeze( + aSource, aSourceContext, aSource, false ), + aDestinationContext, false ); + + aDestinationContext.recordObject( clone, aGlobalID ); + + // need to copy to transpose references into this context + // while preserving the same instance of the object + aDestinationContext.ignoreChanges = true; + copy( aSourceContext, aSource, aDestinationContext, clone ); + aDestinationContext.ignoreChanges = false; + + // return our clone + return clone; + } + + /** + * Copies values from one object to another. + * Returns the destination object, or throws exception + * if operation fails. + */ + static private Object copy( + EOEditingContext aSourceContext, + Object aSource, + EOEditingContext aDestinationContext, + Object aDestination ) + { // System.out.println( "copy: " ); + EOObserverCenter.notifyObserversObjectWillChange( aDestination ); + KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination ); + return aDestination; + } + + // process recent changes + + /** + * Called to notify observers of changes. + * Also calls runLater(). + */ + private void willChange() + { + EOObserverCenter.notifyObserversObjectWillChange( this ); + runLater(); + } + + /** + * Called to ensure that processRecentChanges + * will be called on the next event loop. + */ + private void runLater() + { + if ( ! willRunLater ) + { + willRunLater = true; + NSRunLoop.currentRunLoop().performSelectorWithOrder( + runLaterSelector, this, null, EditingContextFlushChangesRunLoopOrdering, null ); + } + } + + /** + * This method is called by the event queue run loop + * and calls processRecentChanges. + * NOTE: This method is not part of the specification. + */ + public void flushRecentChanges( Object anObject ) + { +//System.out.println( "EODelayedObserverQueue: running" ); + processRecentChanges(); + willRunLater = false; + } + + // inner classes + + /** + * Gatekeeper for all access to registered objects. + */ + static private class Registrar + { + EOEditingContext context; + NSMutableDictionary IDsToObjects; + NSMutableDictionary objectsToIDs; + NSMutableDictionary objectsToCommitSnapshots; + NSMutableDictionary objectsToCurrentSnapshots; + + ReferenceKey comparisonKey; //FIXME not thread safe! + + public Registrar( EOEditingContext aContext ) + { + context = aContext; + IDsToObjects = new NSMutableDictionary(); + objectsToIDs = new NSMutableDictionary(); + objectsToCommitSnapshots = new NSMutableDictionary(); + objectsToCurrentSnapshots = new NSMutableDictionary(); + comparisonKey = new ReferenceKey(); + } + + public NSArray registeredObjects() + { + return IDsToObjects.allValues(); + } + + public NSArray registeredGlobalIDs() + { + return IDsToObjects.allKeys(); + } + + public Object objectForGlobalID( EOGlobalID aGlobalID ) + { + return IDsToObjects.objectForKey( aGlobalID ); + } + + public EOGlobalID globalIDForObject( Object anObject ) + { + comparisonKey.set( anObject ); + return (EOGlobalID) objectsToIDs.objectForKey( comparisonKey ); + } + + public byte[] getCommitSnapshot( Object anObject ) + { + comparisonKey.set( anObject ); + return (byte[]) objectsToCommitSnapshots.objectForKey( comparisonKey ); + } + + public void setCommitSnapshot( Object anObject, byte[] aSnapshot ) + { + if ( aSnapshot == null ) + { + comparisonKey.set( anObject ); + objectsToCommitSnapshots.removeObjectForKey( comparisonKey ); + } + else + { + objectsToCommitSnapshots.setObjectForKey( + aSnapshot, new ReferenceKey( anObject ) ); + } + } + + public byte[] getCurrentSnapshot( Object anObject ) + { + comparisonKey.set( anObject ); + return (byte[]) objectsToCurrentSnapshots.objectForKey( comparisonKey ); + } + + public void setCurrentSnapshot( Object anObject, byte[] aSnapshot ) + { + if ( aSnapshot == null ) + { + comparisonKey.set( anObject ); + objectsToCurrentSnapshots.removeObjectForKey( comparisonKey ); + } + else + { + objectsToCurrentSnapshots.setObjectForKey( + aSnapshot, new ReferenceKey( anObject ) ); + } + } + + public void registerObject( Object anObject, EOGlobalID aGlobalID ) + { + IDsToObjects.setObjectForKey( anObject, aGlobalID ); + objectsToIDs.setObjectForKey( aGlobalID, new ReferenceKey( anObject ) ); + EOObserverCenter.addObserver( context, anObject ); + } + + public void forgetObject( Object anObject ) + { + comparisonKey.set( anObject ); + Object id = objectsToIDs.objectForKey( comparisonKey ); + IDsToObjects.removeObjectForKey( id ); + objectsToIDs.removeObjectForKey( comparisonKey ); + EOObserverCenter.removeObserver( context, anObject ); + } + + public void disposeSnapshots( Object anObject ) + { + setCommitSnapshot( anObject, null ); + setCurrentSnapshot( anObject, null ); + } + + } + + /** + * Registrar that uses only WeakReferences. + * Used if retainsRegisteredObjects is false. + */ + static private class WeakRegistrar extends Registrar + { + private WeakReferenceKey weakComparisonKey; //FIXME not thread safe! + + public WeakRegistrar( EOEditingContext aContext ) + { + super( aContext ); + weakComparisonKey = new WeakReferenceKey(); + } + + public NSArray registeredObjects() + { + Object object; + WeakReferenceKey weakKey; + NSMutableArray result = new NSMutableArray(); + Enumeration e = new NSArray( objectsToIDs.allKeys() ).objectEnumerator(); + while ( e.hasMoreElements() ) + { + weakKey = (WeakReferenceKey) e.nextElement(); + object = weakKey.get(); + if ( object != null ) + { + result.addObject( object ); + } + else + { + // object has been released: perform cleanup + disposeObject( null, weakKey ); + } + } + return result; + } + + public Object objectForGlobalID( EOGlobalID aGlobalID ) + { + WeakReference ref = (WeakReference) super.objectForGlobalID( aGlobalID ); + if ( ref == null ) return null; + Object result = ref.get(); + if ( result == null ) + { + // clean up manually + IDsToObjects.removeObjectForKey( aGlobalID ); + Iterator i = new LinkedList( objectsToIDs.allKeysForObject( ref ) ).iterator(); + while ( i.hasNext() ) + { + objectsToIDs.removeObjectForKey( i.next() ); + } + disposeSnapshots( aGlobalID ); + } + return result; + } + + public byte[] getCommitSnapshot( Object anObject ) + { + weakComparisonKey.set( anObject ); + return (byte[]) objectsToCommitSnapshots.objectForKey( weakComparisonKey ); + } + + public void setCommitSnapshot( Object anObject, byte[] aSnapshot ) + { + if ( aSnapshot == null ) + { + weakComparisonKey.set( anObject ); + objectsToCommitSnapshots.removeObjectForKey( weakComparisonKey ); + } + else + { + objectsToCommitSnapshots.setObjectForKey( + aSnapshot, new WeakReferenceKey( anObject ) ); + } + } + + public byte[] getCurrentSnapshot( Object anObject ) + { + weakComparisonKey.set( anObject ); + return (byte[]) objectsToCurrentSnapshots.objectForKey( weakComparisonKey ); + } + + public void setCurrentSnapshot( Object anObject, byte[] aSnapshot ) + { + if ( aSnapshot == null ) + { + weakComparisonKey.set( anObject ); + objectsToCurrentSnapshots.removeObjectForKey( weakComparisonKey ); + } + else + { + objectsToCurrentSnapshots.setObjectForKey( + aSnapshot, new WeakReferenceKey( anObject ) ); + } + } + + public void registerObject( Object anObject, EOGlobalID aGlobalID ) + { // new net.wotonomy.ui.swing.ReferenceInspector( anObject ); + IDsToObjects.setObjectForKey( new WeakReference( anObject ), aGlobalID ); + objectsToIDs.setObjectForKey( aGlobalID, new WeakReferenceKey( anObject ) ); + EOObserverCenter.addObserver( context, anObject ); + } + + public void forgetObject( Object anObject ) + { + disposeObject( anObject, null ); + } + + // must specify one or the other + private void disposeObject( Object anObject, WeakReferenceKey key ) + { + if ( key == null ) key = new WeakReferenceKey( anObject ); + EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey( key ); + if ( id != null ) IDsToObjects.removeObjectForKey( id ); + objectsToIDs.removeObjectForKey( key ); + disposeSnapshots( id ); + if ( anObject != null ) + { + EOObserverCenter.removeObserver( context, anObject ); + } + } + } + + /** + * Private class used to force a hashmap to + * perform key comparisons by reference. + */ + static private class ReferenceKey + { + int hashCode; + Object referent; + + public ReferenceKey() + { + referent = null; + hashCode = -1; + } + + public ReferenceKey( Object anObject ) + { + set( anObject ); + } + + public Object get() + { + return referent; + } + + public void set( Object anObject ) + { + referent = anObject; + hashCode = anObject.hashCode(); + } + + /** + * Returns the actual key's hash code. + */ + public int hashCode() + { + return hashCode; + } + + /** + * Compares by reference. + */ + public boolean equals( Object anObject ) + { + if ( anObject == this ) return true; + if ( anObject instanceof ReferenceKey ) + { + return ((ReferenceKey)anObject).get() == referent; + } + return false; + } + } + + /** + * Private class used to force a hashmap to + * perform key comparisons by reference. + */ + static private class WeakReferenceKey extends ReferenceKey + { + public WeakReferenceKey() + { + super(); + } + + public WeakReferenceKey( Object anObject ) + { + super( anObject ); + } + + public Object get() + { + return ((WeakReference)referent).get(); + } + + public void set( Object anObject ) + { + referent = new WeakReference( anObject ); + hashCode = anObject.hashCode(); + } + + /** + * Compares by reference. + */ + public boolean equals( Object anObject ) + { + if ( anObject == this ) return true; + if ( anObject instanceof ReferenceKey ) + { + return ((ReferenceKey)anObject).get() == get(); + } + return false; + } + } + + /** + * Key combining an object with a string. + * Object is compared by reference. + */ + static private class CompoundKey + { + private Object object; + private String string; + private int hashCode; + + /** + * Creates compound key. + * Neither name nor object may be null. + */ + public CompoundKey ( + Object anObject, String aString ) + { + object = anObject; + string = aString; + hashCode = object.hashCode() + string.hashCode(); + } + + public int hashCode() + { + return hashCode; + } + + public boolean equals( Object anObject ) + { + if ( anObject instanceof CompoundKey ) + { + CompoundKey key = (CompoundKey) anObject; + return ( ( key.object == object ) && ( key.string.equals( string ) ) ); + } + return false; + } + + public String toString() + { + return "[CompoundKey:"+object+":"+string+"]"; + } + } + + /** + * Used by EditingContext to delegate behavior to another class. + * Note that EditingContext doesn't require its delegates to implement + * this interface: rather, this interface defines the methods that + * EditingContext will attempt to invoke dynamically on its delegate. + * The delegate may choose to implement only a subset of the methods + * on the interface. + */ + public interface Delegate + { + /** + * Called after the editing context has completed merge operations + * on one or more objects after receiving an ObjectChangedInStore + * notification. + */ + void editingContextDidMergeChanges( + EOEditingContext anEditingContext ); + + /** + * Called by objectsWithFetchSpecification. + * If null, the editing context will pass the fetch specification + * on to its parent store, as normal. Otherwise, the context + * will use the returned array to service the request. + */ + NSArray editingContextShouldFetchObjects( + EOEditingContext anEditingContext, + EOFetchSpecification fetchSpecification ); + + /** + * Called to determine whether an object should be invalidated. + * Return false to prevent the object from being invalidated. + * Default is true. + */ + boolean editingContextShouldInvalidateObject( + EOEditingContext anEditingContext, + Object anObject, + EOGlobalID aGlobalID ); + + /** + * Called to determine whether the editing context should attempt + * to merge changes in the specified object that the parent store + * says has changed via an ObjectChangedInStore notification. + * Default is true. Return false if you wish to handle the merge + * yourself, by extracting the values in the object now and comparing + * them to the values when editingContextDidMergeChanges is called. + */ + boolean editingContextShouldMergeChangesForObject( + EOEditingContext anEditingContext, + Object anObject ); + + /** + * Returns whether the editing context should ask its message handler + * to display a message. Return false if the delegate will display the error. + * Default is true. + */ + boolean editingContextShouldPresentException( + EOEditingContext anEditingContext, + Throwable exception ); + + /** + * Returns whether the editing context should undo the most + * recent set of changes that resulted in a validation failure. + * Default is true. + */ + boolean editingContextShouldUndoUserActionsAfterFailure( + EOEditingContext anEditingContext ); + + /** + * Returns whether the editing context should validate the + * most recent set of changes. Default is true. + */ + boolean editingContextShouldValidateChanges( + EOEditingContext anEditingContext ); + + /** + * Called before the editing context saves its changes + * to the parent object store. + */ + void editingContextWillSaveChanges( + EOEditingContext anEditingContext ); + + } + + /** + * Editors register themselves with the editing context + * so that they may receive notification before the context + * commits changes. This is useful for associations whose + * components do not immediately commit their changes to + * the object they are editing. + */ + public interface Editor + { + /** + * Called before the editing context saves its changes + * to the parent object store. + */ + void editingContextWillSaveChanges( + EOEditingContext anEditingContext ); + + /** + * Called to determine whether this editor has changes + * that have not been committed to the object in the context. + */ + boolean editorHasChangesForEditingContext( + EOEditingContext anEditingContext ); + + } + + /** + * Used by EditingContext to delegate messaging handling to another class, + * typically the display group that has the currently active association. + * Note that EditingContext doesn't require its message handlers to implement + * this interface: rather, this interface defines the methods that + * EditingContext will attempt to invoke dynamically on its delegate. + * The delegate may choose to implement only a subset of the methods + * on the interface. + */ + public interface MessageHandler + { + /** + * Called to display a message for an error that occurred + * in the specified editing context. + */ + void editingContextPresentErrorMessage( + EOEditingContext anEditingContext, + String aMessage ); + + /** + * Called by the specified object store to determine whether + * fetching should continue, where count is the current count + * and limit is the limit as specified by the fetch specification. + * Default is false. + */ + boolean editingContextShouldContinueFetching( + EOEditingContext anEditingContext, + int count, + int limit, + EOObjectStore anObjectStore ); + + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.86 2003/12/18 15:37:38 mpowers + * Changes to retain ability to work with objects that don't necessarily + * implement EOEnterpriseObject. I would still like to preserve this case + * for general usage, however the access package is free to assume that + * those objects will be EOs and cast appropriately. + * + * Revision 1.85 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.84 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.83 2003/02/13 15:24:33 mpowers + * hasChanges is now derived, not tracked. + * refaultObject now more consistently removes object from updated list. + * + * Revision 1.82 2002/12/16 15:46:00 mpowers + * Major refactoring to implement setInstancesRetainRegisteredObjects(). + * + * Revision 1.81 2002/11/18 22:10:58 mpowers + * Now resetting hasChanges flag on reset. + * + * Revision 1.80 2002/10/24 21:15:33 mpowers + * New implementations of NSArray and subclasses. + * + * Revision 1.79 2002/10/24 18:18:12 mpowers + * NSArray's are now considered read-only, so we can return our internal + * representation to reduce unnecessary object allocation. + * + * Revision 1.78 2002/06/21 21:44:33 mpowers + * No longer marking deleted objects as updated (thanks to dwang). + * + * Revision 1.77 2002/05/20 15:10:17 mpowers + * No longer refaulting if delegate does not wish to handle the merge. + * + * Revision 1.76 2002/03/26 21:46:06 mpowers + * Contributing EditingContext as a java-friendly convenience. + * + * Revision 1.75 2002/03/06 16:14:57 mpowers + * More attempts at ignoring update conflicts that come from ourself. + * + * Revision 1.74 2002/02/21 21:57:50 mpowers + * Implemented default merge behavior. + * + * Revision 1.73 2002/02/20 16:46:54 mpowers + * Implemented better support for EOEditingContext.Delegate. + * + * Revision 1.70 2002/02/19 22:26:05 mpowers + * Implemented EOEditingContext.MessageHandler support. + * + * Revision 1.69 2002/02/19 16:33:42 mpowers + * Implemented support for EditingContext.Editor + * + * Revision 1.68 2002/02/13 22:00:34 mpowers + * Fixed: invalidateAllObjects tries to invalidate inserted objects, + * typically causing class cast exceptions involving EOTemporaryGlobalID. + * + * Revision 1.67 2002/02/06 21:15:35 mpowers + * No longer refaulting a dirty object when we receive an invalidation notif. + * + * Revision 1.66 2002/01/08 19:31:03 mpowers + * refaultObject now correctly refaults the object. + * + * Revision 1.65 2001/12/20 18:56:15 mpowers + * Refinements to snapshotting and calling processRecentChanges. + * + * Revision 1.64 2001/12/10 15:11:41 mpowers + * Now only tracking a commit snapshot after an object has been modified. + * + * Revision 1.63 2001/11/14 00:08:10 mpowers + * Now marking context changed when objects are inserted or deleted + * and when child contexts save their changes into this context. + * + * Revision 1.62 2001/11/07 14:49:31 mpowers + * invalidateAllObjects now handles objects manually discarded in the course + * of invalidation. + * + * Revision 1.61 2001/10/26 20:02:49 mpowers + * No longer using NSNotificationQueue: all notifications are posted immed. + * + * Revision 1.60 2001/10/26 18:37:50 mpowers + * Now using NSRunLoop to correctly flush recent changes before delayed + * observers and AWT events. + * + * Revision 1.59 2001/10/23 22:29:59 mpowers + * Now posting notifications immediately. + * Recent changes are flushed at ObserverPrioritySixth, soon to change. + * + * Revision 1.58 2001/09/10 14:16:51 mpowers + * EditingContexts now relay InvalidatedAllObjectsInStore notifications. + * + * Revision 1.57 2001/06/18 14:11:15 mpowers + * Inserting a deleted object simply cancels the delete operation. + * + * Revision 1.56 2001/06/07 22:07:59 mpowers + * Now handling delete notifications before update notifications. + * Eliminated the case where deleted objects were also being put + * on the updated list when notifying child contexts. + * + * Revision 1.55 2001/05/18 21:04:33 mpowers + * Reimplemented EditingContext.initializeObject. + * + * Revision 1.54 2001/05/05 23:05:42 mpowers + * Implemented Array Faults. + * + * Revision 1.53 2001/05/05 15:00:06 mpowers + * Tested load-on-demand: still works. + * Now using registerClone for consistency. + * Editing context is temporarily posting notification on objectWillChange. + * + * Revision 1.52 2001/05/05 14:11:48 mpowers + * Implemented registerClone. + * + * Revision 1.51 2001/05/04 16:57:56 mpowers + * Now correctly transposing references to editing contexts when + * cloning/copying between editing contexts. + * + * Revision 1.50 2001/05/02 17:58:41 mpowers + * Removed debugging code, added comments. + * + * Revision 1.49 2001/05/02 15:47:40 mpowers + * Fixed the pernicious problem with reverts: recordObject was recording + * a snapshot of the clone before the transposition-copy happened, + * so the revert object would lose all of its transposed relationships. + * + * Revision 1.48 2001/05/02 12:39:05 mpowers + * Fixed a nasty problem with transpose-cloning and faultForGlobalID. + * Now we're forced to create a deep clone, registered it, and then + * transpose it after it has been registered. + * + * Revision 1.47 2001/04/30 13:15:24 mpowers + * Child contexts re-initializing objects invalidated in parent now + * propery transpose relationships. + * + * Revision 1.46 2001/04/29 22:02:45 mpowers + * Work on id transposing between editing contexts. + * + * Revision 1.45 2001/04/29 02:29:31 mpowers + * Debugging relationship faulting. + * + * Revision 1.44 2001/04/28 16:18:44 mpowers + * Implementing relationships. + * + * Revision 1.43 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.42 2001/04/27 00:27:11 mpowers + * Provided description to not-implemented exceptions. + * + * Revision 1.41 2001/04/26 01:16:44 mpowers + * Major bug fix so we no longer accumulate objects in the all objects + * list with every InvalidateAllObjectsInStore. + * + * Revision 1.40 2001/04/21 23:07:49 mpowers + * Now only broadcasts notifications if there's actually a change. + * + * Revision 1.39 2001/04/13 16:33:11 mpowers + * Corrected the refaulting behavior. + * + * Revision 1.38 2001/04/09 21:42:10 mpowers + * Debugging and optimizing notifications. + * + * Revision 1.37 2001/04/08 20:59:47 mpowers + * objectsForFetchSpecification now relies on faultForGlobalID. + * + * Revision 1.36 2001/04/03 20:36:01 mpowers + * Fixed refaulting/reverting/invalidating to be self-consistent. + * + * Revision 1.35 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.34 2001/03/28 14:06:29 mpowers + * Implemented snapshots. Revert now uses snapshots. + * + * Revision 1.33 2001/03/20 23:20:33 mpowers + * invalidating all objects now sets the dirty flag to false. + * + * Revision 1.32 2001/03/19 21:44:36 mpowers + * Reverts reinitialize for now. + * Testing for inserted objects instead of temp id when invalidating object. + * + * Revision 1.31 2001/03/15 21:10:26 mpowers + * Implemented global id re-registration for newly saved inserts. + * + * Revision 1.30 2001/03/13 21:41:34 mpowers + * Broadcasting willChange for any change to hasChanges. + * Fixed major bug with inserted objects treated as updated objects + * in child display groups. + * + * Revision 1.29 2001/03/09 22:10:30 mpowers + * Fine tuned initializeObject. + * + * Revision 1.28 2001/03/06 23:23:55 mpowers + * objectForGlobalID now returns null if not found. + * objectsForFetchSpecification again does things the old way, for now. + * + * Revision 1.27 2001/03/02 16:31:45 mpowers + * Trying to better handle fetches from child contexts. + * No longer trying to invalidate temporary objects. + * + * Revision 1.26 2001/02/28 16:25:19 mpowers + * Now calling globalIDForObject internally. + * + * Revision 1.25 2001/02/27 17:36:55 mpowers + * Objects inserted from child now preserve the existing temporary id. + * + * Revision 1.24 2001/02/27 02:11:17 mpowers + * Now throwing exception when cloning fails. + * Removed debugging printlns. + * + * Revision 1.23 2001/02/26 22:41:51 mpowers + * Implemented null placeholder classes. + * Duplicator now uses NSNull. + * No longer catching base exception class. + * + * Revision 1.22 2001/02/26 21:18:45 mpowers + * Now marking edited objects from child contexts that were not already + * recorded in parent as changed in saveChangesInEditingContext. + * + * Revision 1.21 2001/02/26 15:53:22 mpowers + * Fine-tuning notification firing. + * Child display groups now update properly after parent save or invalidate. + * + * Revision 1.20 2001/02/24 17:03:22 mpowers + * Implemented the notification queue, and changed editing context to use it. + * + * Revision 1.19 2001/02/23 23:44:15 mpowers + * Fine-tuning notification handling. + * + * Revision 1.18 2001/02/22 22:56:57 mpowers + * Only refaulting edited objects on parent store invalidateAll. + * + * Revision 1.17 2001/02/22 20:54:39 mpowers + * Implemented notification handling. + * + * Revision 1.16 2001/02/21 22:10:55 mpowers + * Editing context is now posting appropriate notifications. + * + * Revision 1.15 2001/02/21 21:17:32 mpowers + * Now retaining a reference to the recent changes observer. + * Better documented need to retain reference. + * Started implementing notifications. + * + * Revision 1.14 2001/02/20 17:24:22 mpowers + * Now using reference keys in objectToID. + * + * Revision 1.13 2001/02/20 16:45:36 mpowers + * Child data sources now accept a data source instead of an editing context + * for more flexibility. Child data sources now forward relationship + * methods to parent source. + * + * Revision 1.12 2001/02/16 22:51:29 mpowers + * Now deep-cloning objects passed between editing contexts. + * + * Revision 1.11 2001/02/16 18:34:19 mpowers + * Implementing nested contexts. + * + * Revision 1.9 2001/02/15 21:13:30 mpowers + * First draft implementation is complete. Now on to debugging. + * + * Revision 1.7 2001/02/13 23:24:29 mpowers + * Implementing more of editing context. + * + * Revision 1.4 2001/02/09 22:09:34 mpowers + * Completed implementation of EOObjectStore. + * + * Revision 1.3 2001/02/06 15:24:11 mpowers + * Widened parameters on abstract method to fix build. + * + * Revision 1.2 2001/02/05 03:45:37 mpowers + * Starting work on EOEditingContext. + * + * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers + * Contributing wotonomy. + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java new file mode 100644 index 0000000..112531c --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEnterpriseObject.java @@ -0,0 +1,219 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; + +/** +* EOEnterpriseObject defines the required methods a data object +* must implement to take full advantage of the control package. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public interface EOEnterpriseObject + extends EOKeyValueCodingAdditions, + EORelationshipManipulation, + EODeferredFaulting, + EOValidation +{ + /** + * Returns a List of all property keys defined on this object. + * This includes both attributes and relationships. + */ + NSArray allPropertyKeys(); + + /** + * Returns a list of all attributes defined on this object. + * Attributes are all properties that are not relationships. + */ + NSArray attributeKeys(); + + //void awakeFromClientUpdate(EOEditingContext aContext) + + /** + * Called when the object has first been fetched into the + * specified editing context. + */ + void awakeFromFetch(EOEditingContext anEditingContext); + + /** + * Called when the object has been inserted into the + * specified editing context. + */ + void awakeFromInsertion(EOEditingContext anEditingContext); + + /** + * Returns a Map representing the delta of the current state + * from the state represented in the specified snapshot. + * The result will contain only the keys that have changed + * and their values. Relationship keys will map to an NSArray + * that contains an NSArray of added objects and an NSArray + * of removed objects, in that order. + */ + NSDictionary changesFromSnapshot(NSDictionary snapshot); + + /** + * Returns a class description for this object. + */ + EOClassDescription classDescription(); + + /** + * Returns a class description for the object at the + * other end of the specified relationship key. + */ + EOClassDescription classDescriptionForDestinationKey(String aKey); + + /** + * Clears all property values for this object. + * This method is called to clean-up an object that + * will no longer be used, and implementations should + * ensure that all references are set to null to + * prevent problems with garbage-collection. + */ + void clearProperties(); + + /** + * Returns the delete rule constant defined on EOClassDescription + * for the relationship defined by the specified key. + */ + int deleteRuleForRelationshipKey(String aRelationshipKey); + + /** + * Returns the editing context in which this object is registered. + */ + EOEditingContext editingContext(); + + /** + * Returns the name of the entity that this object represents. + */ + String entityName(); + + /** + * Returns a String containing all property keys and values for + * this object. Relationships should be represented by calling + * eoShallowDescription() on the object. + */ + String eoDescription(); + + /** + * Returns a String containing all attribute keys and values for + * this object. Relationships are not included. + */ + String eoShallowDescription(); + + /** + * Returns the key used to reference this object on the + * object at the other end of the specified relationship. + */ + String inverseForRelationshipKey(String aRelationshipKey); + + //Object invokeRemoteMethod( + // String aMethodName, Class[] aTypeArray Object[] anArgumentArray) + + /** + * Returns whether the specified relationship key represents + * a to-many relationship. + */ + boolean isToManyKey(String aKey); + + /** + * Returns whether the objects at the other end of the specified + * relationship should be deleted when this object is deleted. + */ + boolean ownsDestinationObjectsForRelationshipKey(String aKey); + + //void prepareValuesForClient() + + /** + * Called to perform the delete propagation for this object + * on the specified editing context. All relationships + * should be processed according to their corresponding + * delete rule. + */ + void propagateDeleteWithEditingContext(EOEditingContext aContext); + + /** + * Applies the changes from the specified snapshot to + * this object. + * @see #changesFromSnapshot(NSDictionary) + */ + void reapplyChangesFromDictionary(NSDictionary aDeltaSnapshot); + + /** + * Returns a snapshot of the current state of this object. + * All property keys are mapped to their values; nulls are + * represented by NSNull. + */ + NSDictionary snapshot(); + + /** + * Returns a List of the to-many relationship keys + * for this object. + */ + NSArray toManyRelationshipKeys(); + + /** + * Returns a List of the to-one relationship keys + * for this object. + */ + NSArray toOneRelationshipKeys(); + + /** + * Applies the specified snapshot to this object, + * converting NSNulls to null and calling + * takeStoredValueForKey for each key in the Map. + */ + void updateFromSnapshot(NSDictionary aSnapshot); + + /** + * Returns a short, stateful string representation + * of this object. + */ + String userPresentableDescription(); + + /** + * This method should be implemented to call + * EOObserverCenter.objectWillChange( this ), + * and it should be called by each setter method + * on this object before changes are made to the + * object's internal state. + */ + void willChange(); +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java new file mode 100644 index 0000000..19dc2df --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaultHandler.java @@ -0,0 +1,102 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** +* EOFaultHandler defines the contract for objects that can +* create and populate faults. In wotonomy, this interface is +* currently only a marker interface. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public abstract class EOFaultHandler { + + protected Class _targetClass; + + public EOFaultHandler() { + super(); + } + + public static EOFaultHandler handlerForFault(Object obj) { + if (!(obj instanceof EOFaulting)) + throw new IllegalArgumentException("Object must implement EOFaulting"); + return ((EOFaulting)obj).faultHandler(); + } + + public static boolean isFault(Object obj) { + if (obj == null) + return false; + boolean isit = (obj instanceof EOFaulting); + if (isit) + isit = ((EOFaulting)obj).isFault(); + return isit; + } + + public static void makeObjectIntoFault(Object obj, EOFaultHandler handler) { + if (!(obj instanceof EOFaulting)) + throw new IllegalArgumentException("Object must implement EOFaulting"); + ((EOFaulting)obj).turnIntoFault(handler); + } + + public static void clearFault(Object obj) { + if (!(obj instanceof EOFaulting)) + throw new IllegalArgumentException("Object must implement EOFaulting"); + ((EOFaulting)obj).clearFault(); + } + + public Class targetClass() { + return _targetClass; + } + + public Object createFaultForDeferredFault(Object fault, EOEnterpriseObject source) { + return fault; + } + + public String descriptionForObject(Object obj) { + if (obj == null) + return "<null>"; + return obj.toString(); + } + + public String eoShallowDescription(Object obj) { + return null; + } + + public abstract void completeInitializationOfObject(Object obj); + + public abstract void faultWillFire(Object obj); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java new file mode 100644 index 0000000..95b3e35 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFaulting.java @@ -0,0 +1,79 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** +* EOFaulting defines the requirements for objects that +* can be faulted by an EOFaultManager. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOFaulting +{ + /** + * Called by EOFaultHandler to prepare the object to be turned into a fault. + */ + void clearFault(); + + /** + * Returns this object's EOFaultHandler. + */ + EOFaultHandler faultHandler(); + + /** + * Returns whether this object is currently a fault. + * Returns true if this object has not yet retrieved any values. + */ + boolean isFault(); + + /** + * Turns this object into a fault using the specified fault handler. + */ + void turnIntoFault( EOFaultHandler aFaultHandler ); + + /** + * Called to completely fire the fault, reading all values. + * This method may be implemented to call willRead(null). + */ + void willRead(); + + /** + * Called to fire the fault for the specified key. + * The fault manager is required to populate the specified key + * with a value, and may populate any or all of the other values + * on this object. A null key will populate all values on the object. + * NOTE: This method is not part of the specification. + */ + void willRead( String aKey ); +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java new file mode 100644 index 0000000..71f3b78 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOFetchSpecification.java @@ -0,0 +1,565 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOFetchSpecification defines the parameters used to request +* objects from an EOObjectStore. They are commonly created +* and passed to a EODataSource which fetches from its +* EOEditingContext, which passes the call up to its root +* EOObjectStore's objectsWithFetchSpecification method. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOFetchSpecification implements EOKeyValueArchiving { + private boolean fetchesRawRows; + private String entityName; + private NSDictionary hints; + private boolean deep; + private int fetchLimit; + private boolean locksObjects; + private NSArray prefetchingRelationshipKeyPaths; + private boolean promptsAfterFetchLimit; + private EOQualifier qualifier; + private NSArray rawRowKeyPaths; + private boolean refreshesRefetchedObjects; + private boolean requiresAllQualifierBindingVariables; + private NSArray sortOrderings; + private boolean distinct; + + /** + * Default constructor initializes internal state. + */ + public EOFetchSpecification() + { + fetchesRawRows = false; + entityName = null; + hints = null; + deep = true; + fetchLimit = 0; + locksObjects = false; + prefetchingRelationshipKeyPaths = null; + promptsAfterFetchLimit = false; + qualifier = null; + rawRowKeyPaths = null; + refreshesRefetchedObjects = false; + requiresAllQualifierBindingVariables = false; + sortOrderings = null; + distinct = false; + } + + /** + * Constructs a fetch specification for the specified entity type using + * the specified qualifier and sort ordering. + */ + public EOFetchSpecification( String anEntityName, EOQualifier aQualifier, List aSortOrderingList ) + { + this(); + entityName = anEntityName; + qualifier = aQualifier; + sortOrderings = new NSArray( (Collection) aSortOrderingList ); + } + + /** + * Constructs a fetch specification for the specified entity type using + * the specified qualifier and sort ordering, distinct flag, deep flag, + * and hints dictionary. + */ + public EOFetchSpecification( String anEntityName, EOQualifier aQualifier, NSArray aSortOrderingList, + boolean usesDistinct, boolean isDeep, Map aHintMap) + { + this(); + entityName = anEntityName; + qualifier = aQualifier; + sortOrderings = new NSArray( (Collection) aSortOrderingList ); + distinct = usesDistinct; + deep = isDeep; + hints = new NSMutableDictionary( (Map) aHintMap ); + } + + /** + * Convenience to return the named fetch specification from the class description + * corresponding to the specified entity name. Returns null if either entityName + * or spec name cannot be resolved. + */ + public static EOFetchSpecification fetchSpecificationNamed( String name, String entityName) + { + EOClassDescription classDesc = EOClassDescription.classDescriptionForEntityName( entityName ); + if ( classDesc == null ) return null; + return classDesc.fetchSpecificationNamed( name ); + } + + /** + * Implemented to return a new fetch specification that is a deep copy of this one. + */ + public Object clone() + { + EOFetchSpecification clone = new EOFetchSpecification(); + + clone.fetchesRawRows = this.fetchesRawRows; + clone.entityName = this.entityName; + if ( this.hints != null ) + clone.hints = new NSDictionary( (Map) this.hints ); + clone.deep = this.deep; + clone.locksObjects = this.locksObjects; + if ( this.prefetchingRelationshipKeyPaths != null ) + clone.prefetchingRelationshipKeyPaths = + new NSArray( (List) prefetchingRelationshipKeyPaths ); + clone.promptsAfterFetchLimit = this.promptsAfterFetchLimit; + if ( this.qualifier != null ) + clone.qualifier = this.qualifier; //FIXME: probably should clone? + if ( this.rawRowKeyPaths != null ) + clone.rawRowKeyPaths = new NSArray( (List) this.rawRowKeyPaths ); + clone.refreshesRefetchedObjects = this.refreshesRefetchedObjects; + clone.requiresAllQualifierBindingVariables = + this.requiresAllQualifierBindingVariables; + if ( this.sortOrderings != null ) + clone.sortOrderings = new NSArray( (List) this.sortOrderings ); + clone.distinct = this.distinct; + + return clone; + } + + /** + * Returns the name of the entity fetched by this fetch spec. + */ + public String entityName() + { + return entityName; + } + + /** + * Returns the current fetch limit. + * A fetch limit of zero indicates no fetch limit. + * Zero is the default. + */ + public int fetchLimit() + { + return fetchLimit; + } + + /** + * Returns whether this fetch spec will fetch raw rows. + * Default is false. + */ + public boolean fetchesRawRows() + { + return fetchesRawRows; + } + + /** + * Returns a fetch specification that resolves the bindings + * in the specified map. + */ + public EOFetchSpecification + fetchSpecificationWithQualifierBindings(Map aBindingMap) + { + throw new WotonomyException( "Not implemented yet" ); + } + + /** + * Returns a Map containing the hints used by this fetch specification, + * or null if no hints have been specified. + */ + public NSDictionary hints() + { + if ( hints == null ) return null; + return new NSDictionary( (NSDictionary) hints ); + } + + /** + * Returns whether entities related to the primary + * entities are fetched by this fetch spec. If true, all relationships + * whose destinations meet the qualifier criteria will be returned + * in addition to primary results. If false, only the primary entities + * will be returned. Default is true. + */ + public boolean isDeep() + { + return deep; + } + + /** + * Returns whether this data source should lock objects that + * are fetched. Default is false. + */ + public boolean locksObjects() + { + return locksObjects; + } + + /*** + * Returns a List of relationships for the fetched objects that + * should also be fetched, or null if no such list has been specified. + * Use this to avoid additional calls to the server to fetch + * relationships that you know you will use. + * NOTE: wotonomy allows you to specify non-relational keys + * as well. + */ + public NSArray prefetchingRelationshipKeyPaths() + { + return prefetchingRelationshipKeyPaths; + } + + /** + * Returns whether the user should be prompted to continue + * when the fetch limit has been exceeded. + * Default is false. + */ + public boolean promptsAfterFetchLimit() + { + return promptsAfterFetchLimit; + } + + /** + * Returns the qualifier used by this fetch specification, + * or null if none has been specified. + */ + public EOQualifier qualifier() + { + return qualifier; + } + + /** + * Returns a List of keys or key paths for which + * values should be returned when fetching raw rows, + * or null if no raw row key paths have been specified. + */ + public NSArray rawRowKeyPaths() + { + return rawRowKeyPaths; + } + + /** + * Returns whether fetched objects should replace + * modified versions already fetched into an editing context. + * If true, those changes will be lost. + * Default is false. + */ + public boolean refreshesRefetchedObjects() + { + return refreshesRefetchedObjects; + } + + /** + * Returns whether all qualifier bindings must be specified + * in order to fetch. If true, an exception is thrown if + * unspecified bindings exist. If false, unspecified bindings + * will be removed from the qualifier. Default is false. + */ + public boolean requiresAllQualifierBindingVariables() + { + return requiresAllQualifierBindingVariables; + } + + /** + * Sets the name of the entity fetched by this spec. + */ + public void setEntityName(String aName) + { + entityName = aName; + } + + /** + * Sets whether this fetch spec will return raw rows. + */ + public void setFetchesRawRows(boolean shouldFetchRawRows) + { + fetchesRawRows = shouldFetchRawRows; + } + + /** + * Sets the limit on the number of records returned for this fetch spec. + * Zero indicates no limit on fetches. + */ + public void setFetchLimit(int aLimit) + { + fetchLimit = aLimit; + } + + /** + * Sets the hints passed by this fetch spec. + */ + public void setHints(Map aHintMap) + { + if ( aHintMap == null ) + { + hints = null; + } + else + { + hints = new NSDictionary( (Map) aHintMap ); + } + } + + /** + * Sets whether this fetch specification fetches deeply. + */ + public void setIsDeep(boolean isDeep) + { + deep = isDeep; + } + + /** + * Sets whether this fetch spec locks objects that + * are returned by the fetch. + */ + public void setLocksObjects(boolean shouldLockObjects) + { + locksObjects = shouldLockObjects; + } + + /** + * Sets the prefetch key paths that should be used as an optimization + * hint to the server. NOTE: wotonomy allows you to specify non-relationship + * keys as well. + */ + public void setPrefetchingRelationshipKeyPaths(List aKeyPathList) + { + if ( aKeyPathList == null ) + { + prefetchingRelationshipKeyPaths = null; + } + else + { + prefetchingRelationshipKeyPaths = new NSArray( (List) aKeyPathList ); + } + } + + /** + * Sets whether the user should be prompted when the fetch limit has been + * reached. + */ + public void setPromptsAfterFetchLimit(boolean shouldPrompt) + { + promptsAfterFetchLimit = shouldPrompt; + } + + /** + * Sets the qualifier used by this fetch specification. + */ + public void setQualifier(EOQualifier aQualifier) + { + qualifier = aQualifier; + } + + /** + * Sets the key paths to be returned if this fetch spec + * is returning raw rows. + */ + public void setRawRowKeyPaths(List aKeyPathList) + { + if ( aKeyPathList == null ) + { + rawRowKeyPaths = null; + } + else + { + rawRowKeyPaths = new NSArray( (List) aKeyPathList ); + } + } + + /** + * Sets whether modified objects in an editing context should + * be replaced by newer versions returned by this fetch spec. + */ + public void setRefreshesRefetchedObjects(boolean shouldRefresh) + { + refreshesRefetchedObjects = shouldRefresh; + } + + /** + * Sets whether this fetch spec should require all bindings to be + * resolved before executing. + */ + public void setRequiresAllQualifierBindingVariables(boolean shouldRequireAll) + { + requiresAllQualifierBindingVariables = shouldRequireAll; + } + + /** + * Sets the sort orderings used by this fetch spec. + */ + public void setSortOrderings(List aSortList) + { + if ( aSortList == null ) + { + sortOrderings = null; + } + else + { + sortOrderings = new NSArray( (List) aSortList ); + } + } + + /** + * Sets whether this fetch spec should return only distinct + * objects. + */ + public void setUsesDistinct(boolean shouldUseDistinct) + { + distinct = shouldUseDistinct; + } + + /** + * Returns a List of the sort orderings used by this fetch spec, + * or null if none have been specified. + */ + public NSArray sortOrderings() + { + return sortOrderings; + } + + /** + * Returns a string representation of this fetch specification. + */ + public String toString() + { + return "[FetchSpecification:qualifier=("+qualifier+"),sortOrderings="+sortOrderings+"]"; + } + + /** + * Returns whether this fetch specification will return only one + * reference to each distinct object returned by the fetch. + * Default is false. + */ + public boolean usesDistinct() + { + return distinct; + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOFetchSpecification", "class"); + arch.encodeObject(entityName(), "entityName"); + arch.encodeInt(fetchLimit(), "fetchLimit"); + + //flags + if (isDeep()) + arch.encodeObject("YES", "isDeep"); + arch.encodeObject(qualifier(), "qualifier"); + if (refreshesRefetchedObjects()) + arch.encodeObject("YES", "refreshesRefetchedObjects"); + if (locksObjects()) + arch.encodeObject("YES", "locksObjects"); + if (fetchesRawRows()) + arch.encodeObject("YES", "fetchesRawRows"); + if (promptsAfterFetchLimit()) + arch.encodeObject("YES", "promptsAfterFetchLimit"); + if (requiresAllQualifierBindingVariables()) + arch.encodeObject("YES", "requiresAllQualifierBindingVariables"); + if (usesDistinct()) + arch.encodeObject("YES", "usesDistinct"); + + //encode arrays + if (sortOrderings() != null) { + NSMutableArray arr = new NSMutableArray(sortOrderings().count()); + for (int i = 0; i < sortOrderings.count(); i++) { + EOSortOrdering so = (EOSortOrdering)sortOrderings().objectAtIndex(i); + EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); + so.encodeWithKeyValueArchiver(ar2); + arr.addObject(ar2.dictionary()); + } + arch.encodeObject(arr, "sortOrderings"); + } + if (rawRowKeyPaths != null && rawRowKeyPaths.count() > 0) + arch.encodeObject(rawRowKeyPaths, "rawRowKeyPaths"); + if (prefetchingRelationshipKeyPaths != null && prefetchingRelationshipKeyPaths.count() > 0) + arch.encodeObject(rawRowKeyPaths, "prefetchingRelationshipKeyPaths"); + if (hints != null && hints.count() > 0) + arch.encodeObject(hints, "hints"); + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver unarch) { + EOFetchSpecification fs = new EOFetchSpecification(); + fs.setEntityName((String)unarch.decodeObjectForKey("entityName")); + fs.setFetchLimit(unarch.decodeIntForKey("fetchLimit")); + fs.setIsDeep(unarch.decodeBoolForKey("isDeep")); + fs.setRefreshesRefetchedObjects(unarch.decodeBoolForKey("refreshesRefetchedObjects")); + + //Sort orderings + NSArray arr = (NSArray)unarch.decodeObjectForKey("sortOrderings"); + if (arr != null && arr.count() > 0) { + NSMutableArray orderings = new NSMutableArray(arr.count()); + for (int i = 0; i < arr.count(); i++) { + NSDictionary so = (NSDictionary)arr.objectAtIndex(i); + String selname = (String)so.objectForKey("selectorName"); + NSSelector selector = EOSortOrdering.CompareAscending; + if (selname.startsWith("compareDescending")) + selector = EOSortOrdering.CompareDescending; + else if (selname.startsWith("compareCaseInsensitiveAscending")) + selector = EOSortOrdering.CompareCaseInsensitiveAscending; + else if (selname.startsWith("compareCaseInsensitiveDescending")) + selector = EOSortOrdering.CompareCaseInsensitiveDescending; + EOSortOrdering eoso = new EOSortOrdering((String)so.objectForKey("key"), selector); + orderings.addObject(eoso); + } + fs.setSortOrderings(orderings); + } + //raw rows + arr = (NSArray)unarch.decodeObjectForKey("rawRowKeyPaths"); + if (arr != null && arr.count() > 0) { + fs.setFetchesRawRows(true); + fs.setRawRowKeyPaths(arr); + } + //qualifier + fs.setQualifier((EOQualifier)unarch.decodeObjectForKey("qualifier")); + return fs; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2003/08/11 18:19:01 chochos + * encoding/decoding with EOKeyValueArchiving now works properly + * + * Revision 1.3 2003/08/09 01:22:20 chochos + * implements EOKeyValueArchiving (and unarchiving) + * + * Revision 1.2 2001/11/24 17:32:57 mpowers + * We now have a real implementation. + * + * Revision 1.1 2001/02/05 03:45:37 mpowers + * Starting work on EOEditingContext. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java new file mode 100644 index 0000000..4358ecf --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGenericRecord.java @@ -0,0 +1,151 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSNull; + +/** +* EOGenericRecord extends EOCustomObject to provide a +* general-purpose implementation, relying entirely on the +* class description for entity-specific behavior, and +* storing data in a dictionary. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOGenericRecord extends EOCustomObject +{ + private EOClassDescription classDescription; + private NSMutableDictionary dataDictionary; + + /** + * Default constructor. + */ + protected EOGenericRecord() + { + classDescription = null; + dataDictionary = new NSMutableDictionary(); + } + + /** + * Preferred constructor. + */ + public EOGenericRecord( EOClassDescription aDescription ) + { + this(); + classDescription = aDescription; + } + + /** + * Overridden to return true so that deferred faults are used. + */ + public static boolean usesDeferredFaultCreation() + { + return true; + } + + /** + * Compatibility constructor: aContext and anID are ignored. + */ + public EOGenericRecord( + EOEditingContext aContext, EOClassDescription aDescription, EOGlobalID anID ) + { + this( aDescription ); + } + + /** + * Returns a class description for this object. + * Overridden to return the class description passed + * into the constructor. + */ + public EOClassDescription classDescription() + { + return classDescription; + } + + // interface EOKeyValueCoding + + /** + * Calls storedValueForKey. + */ + public Object valueForKey( String aKey ) + { + return storedValueForKey( aKey ); + } + + /** + * Calls willChange and then calls takeStoredValueForKey. + */ + public void takeValueForKey( Object aValue, String aKey ) + { + willChange(); + takeStoredValueForKey( aValue, aKey ); + } + + /** + * Calls willRead for the specified key, + * and returns the corresponding value from + * the data dictionary. Keys that do not + * exist will return null. + */ + public Object storedValueForKey( String aKey ) + { + willRead( aKey ); + Object result = dataDictionary.objectForKey( aKey ); + if ( NSNull.nullValue().equals( result ) ) result = null; + return result; + } + + /** + * Writes the specified value into the data dictionary + * for the specified key. Nulls are stored as NSNulls + * in the dictionary. + * No checking is performed to determine whether the + * key is a valid attribute key. + */ + public void takeStoredValueForKey( Object aValue, String aKey ) + { + if ( aValue == null ) aValue = NSNull.nullValue(); + dataDictionary.setObjectForKey( aValue, aKey ); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2001/11/18 18:57:10 mpowers + * Implemented EOGenericRecord. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java new file mode 100644 index 0000000..1b4ccbe --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOGlobalID.java @@ -0,0 +1,83 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.io.Serializable; + +/** +* A pure java implementation of EOGlobalID. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public abstract class EOGlobalID implements Cloneable, Serializable +{ + /** + * ObjectStores broadcast this notification when they + * replace a temporary global id with a permanent one. + * EditingContexts listen for these notifications to + * update their mapping of global ids to objects. + * The object of the notification is null, and the user + * info contains a mapping of the old temporary ids to + * the new permanent ids. + */ + public static final String GlobalIDChangedNotification + = "GlobalIDChangedNotification"; + + /** + * Returns whether this id is a temporary id. + * Temporary ids are generated for newly created + * objects that have not been persisted. When + * persisted, the temporary id is discarded in favor + * of the one generated by the object store. + */ + public abstract boolean isTemporary(); + + /** + * Returns a copy of this object. + * This implementation calls super.clone(). + */ + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2001/04/29 22:02:45 mpowers + * Work on id transposing between editing contexts. + * + * Revision 1.3 2001/03/15 21:10:26 mpowers + * Implemented global id re-registration for newly saved inserts. + * + * Revision 1.2 2001/02/15 21:13:30 mpowers + * First draft implementation is complete. Now on to debugging. + * + * Revision 1.1 2001/02/05 03:45:37 mpowers + * Starting work on EOEditingContext. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java new file mode 100644 index 0000000..ee7aac1 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOIntegralKeyGlobalID.java @@ -0,0 +1,72 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.control; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOIntegralKeyGlobalID extends EOKeyGlobalID { + + protected Number keyValue; + + public EOIntegralKeyGlobalID(String entityName, Number value) { + super(entityName, 0); + keyValue = value; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#keyValues() + */ + public Object[] keyValues() { + return new Object[]{ keyValue }; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#_keyValuesNoCopy() + */ + public Object[] _keyValuesNoCopy() { + return new Object[]{ keyValue }; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#keyCount() + */ + public int keyCount() { + return 1; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOGlobalID#isTemporary() + */ + public boolean isTemporary() { + return false; + } + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/19 01:59:01 chochos + * Added the wotonomy headers + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java new file mode 100644 index 0000000..97baaab --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyComparisonQualifier.java @@ -0,0 +1,151 @@ +/* +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.control; + +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOKeyComparisonQualifier + extends EOQualifier + implements EOKeyValueArchiving, EOQualifierEvaluation { + + private String _leftKey; + private String _rightKey; + private NSSelector _selector; + + public EOKeyComparisonQualifier(String leftKey, NSSelector selector, String rightKey) { + super(); + _leftKey = leftKey; + _rightKey = rightKey; + _selector = selector; + } + + public String leftKey() { + return _leftKey; + } + + public String rightKey() { + return _rightKey; + } + + public NSSelector selector() { + return _selector; + } + + /** + * Evaluates this qualifier for the specified object, + * and returns whether the object is qualified. + * selector() is invoked on the value for key() on the + * specified object, with value() as the parameter. + */ + public boolean evaluateWithObject(Object eo) { + try { + Object lvalue, rvalue; + if ( eo instanceof EOKeyValueCoding) { + lvalue = ((EOKeyValueCoding)eo).valueForKey(leftKey()); + rvalue = ((EOKeyValueCoding)eo).valueForKey(rightKey()); + } else { + lvalue = EOKeyValueCodingSupport.valueForKey(eo, leftKey()); + rvalue = EOKeyValueCodingSupport.valueForKey(eo, rightKey()); + } + return ((Boolean)_selector.invoke( lvalue, rvalue)).booleanValue(); + } catch (Exception exc) { + throw new WotonomyException( exc ); + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyValueArchiving#encodeWithKeyValueArchiver(net.wotonomy.control.EOKeyValueArchiver) + */ + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOKeyComparisonQualifier", "class"); + arch.encodeObject(_leftKey, "key"); + NSMutableDictionary d = new NSMutableDictionary(2); + arch.encodeObject(_rightKey, "value"); + String selname = null; + if (_selector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike)) + selname = "caseInsensitiveLike:"; + else if (_selector.equals(EOQualifier.QualifierOperatorContains)) + selname = "contains:"; + else if (_selector.equals(EOQualifier.QualifierOperatorEqual)) + selname = "isEqualTo:"; + else if (_selector.equals(EOQualifier.QualifierOperatorGreaterThan)) + selname = "greaterThan:"; + else if (_selector.equals(EOQualifier.QualifierOperatorGreaterThanOrEqualTo)) + selname = "greaterThanOrEqualTo:"; + else if (_selector.equals(EOQualifier.QualifierOperatorLessThan)) + selname = "lessThan:"; + else if (_selector.equals(EOQualifier.QualifierOperatorLessThanOrEqualTo)) + selname = "lessThanOrEqualTo:"; + else if (_selector.equals(EOQualifier.QualifierOperatorLike)) + selname = "like:"; + else if (_selector.equals(EOQualifier.QualifierOperatorNotEqual)) + selname = "isNotEqualTo:"; + else + selname = _selector.name() + ":"; + arch.encodeObject(selname, "selectorName"); + } + + public static Object decodeWithKeyValueArchiver(EOKeyValueUnarchiver arch) { + String k = (String)arch.decodeObjectForKey("key"); + String v = (String)arch.decodeObjectForKey("value"); + NSSelector sel = null; + String sname = (String)arch.decodeObjectForKey("selectorName"); + if (sname.equals("isEqualTo:")) + sel = EOQualifier.QualifierOperatorEqual; + else if (sname.equals("isNotEqualTo:")) + sel = EOQualifier.QualifierOperatorNotEqual; + else if (sname.equals("caseInsensitiveLike:")) + sel = EOQualifier.QualifierOperatorCaseInsensitiveLike; + else if (sname.equals("contains:")) + sel = EOQualifier.QualifierOperatorContains; + else if (sname.equals("greaterThan:")) + sel = EOQualifier.QualifierOperatorGreaterThan; + else if (sname.equals("greaterThanOrEqualTo:")) + sel = EOQualifier.QualifierOperatorGreaterThanOrEqualTo; + else if (sname.equals("lessThan:")) + sel = EOQualifier.QualifierOperatorLessThan; + else if (sname.equals("lessThanOrEqualTo:")) + sel = EOQualifier.QualifierOperatorLessThanOrEqualTo; + else if (sname.equals("like:")) + sel = EOQualifier.QualifierOperatorLike; + EOKeyComparisonQualifier q = new EOKeyComparisonQualifier(k, sel, v); + return q; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/12 01:42:36 chochos + * this qualifier was missing + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java new file mode 100644 index 0000000..76c4d05 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyGlobalID.java @@ -0,0 +1,146 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services Corporation + +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 + +$Id: EOKeyGlobalID.java 894 2006-02-16 16:47:14Z cgruber $ + +*/ + +package net.wotonomy.control; + +import java.io.ObjectStreamException; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSCoder; +import net.wotonomy.foundation.NSCoding; + +/** +* The model object which represents the mapping between database +* fields and object properties, lists available entities, lists +* connection dictionaries, and has fetch specifications. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public abstract class EOKeyGlobalID extends EOGlobalID + implements NSCoding { + + protected String _entityName; + protected int _hash; + + protected EOKeyGlobalID(String entityName, int hashCode) { + super(); + _entityName = entityName; + _hash = hashCode; + } + + protected void _prepClone(EOKeyGlobalID gid) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static EOKeyGlobalID globalIDWithEntityName(String s, Object aobj[]) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static EOKeyGlobalID _defaultGlobalIDWithEntityName(String s, Object aobj[]) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String entityName() { + return _entityName; + } + + public String _literalEntityName() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean _isFinal() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void _setGuessedEntityName(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String _guessedEntityName() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void _setSubEntityName(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String _subEntityName() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int hashCode() { + return _hash; + } + + public abstract Object[] keyValues(); + + public abstract Object[] _keyValuesNoCopy(); + + public abstract int keyCount(); + + public NSArray keyValuesArray() { + return new NSArray(keyValues()); + } + + public static Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static EOKeyGlobalID _adjustForInheritance(EOKeyGlobalID eokeyglobalid, String s, String s1) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void encodeWithCoder(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public Class classForCoder() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected Object readResolve() throws ObjectStreamException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/19 01:50:54 chochos + * added two concrete implementations of EOKeyGlobalID. + * + * Revision 1.1 2002/07/14 21:59:06 mpowers + * Contributions from cgruber. + * + * Revision 1.1 2002/06/25 00:11:09 cgruber + * Add EOKeyGlobalID, but it won't work without NSCoder. + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java new file mode 100644 index 0000000..3e2f808 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiver.java @@ -0,0 +1,106 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; + +/** +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOKeyValueArchiver { + + //for the delegate + public static final NSSelector sel = new NSSelector("referenceToEncodeForObject", + new Class[]{ Object.class }); + NSMutableDictionary dict = new NSMutableDictionary(); + private Object _delegate; + + public EOKeyValueArchiver() { + super(); + } + + public void encodeBool(boolean flag, String key) { + String v = flag ? "true" : "false"; + dict.setObjectForKey(v, key); + } + + public void encodeInt(int value, String key) { + dict.setObjectForKey(new Integer(value), key); + } + + public void encodeObject(Object obj, String key) { + if (obj == null) + return; + if (obj instanceof NSArray || obj instanceof NSDictionary) { + dict.setObjectForKey(obj, key); + return; + } + if (obj instanceof EOKeyValueArchiving) { + EOKeyValueArchiver arch = new EOKeyValueArchiver(); + ((EOKeyValueArchiving)obj).encodeWithKeyValueArchiver(arch); + dict.setObjectForKey(arch.dictionary(), key); + return; + } + if (obj instanceof String) + dict.setObjectForKey(obj, key); + } + + public void encodeReferenceToObject(Object obj, String key) { + if (_delegate != null) { + if (sel.implementedByObject(obj)) { + try { + Object ref = sel.invoke(_delegate, obj); + if (ref != null) + dict.setObjectForKey(ref, key); + } catch (Exception e) { + } + } + } + } + + public NSDictionary dictionary() { + return new NSDictionary(dict); + } + + public void setDelegate(Object delegate) { + _delegate = delegate; + } + public Object delegate() { + return _delegate; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/09 01:17:53 chochos + * Part of the EOKeyValueArchiving protocol (archives objects into NSDictionaries and vice-versa) + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java new file mode 100644 index 0000000..0d9f78c --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueArchiving.java @@ -0,0 +1,41 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** + * Remember that this method is also part of the interface: + * public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver unarchiver) + * +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOKeyValueArchiving { + + public abstract void encodeWithKeyValueArchiver(EOKeyValueArchiver archiver); + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/09 01:17:53 chochos + * Part of the EOKeyValueArchiving protocol (archives objects into NSDictionaries and vice-versa) + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java new file mode 100644 index 0000000..b3cf926 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCoding.java @@ -0,0 +1,129 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import net.wotonomy.foundation.NSKeyValueCoding; + +/** +* EOKeyValueCoding defines an interface for classes that +* need to have more control over the wotonomy's property +* introspection facilities. <br><br> +* +* On an object that implements this interface, wotonomy +* will call these methods, and otherwise use the static +* methods on EOKeyValueCodingSupport. <br><br> +* +* EOKeyValueCodingSupport implements the default behaviors +* for each of these methods, so classes implementing this +* interface can call those methods to acheive the same +* behavior. <br><br> +* +* valueForKey and takeValueForKey are called in response +* to user actions, like viewing an object or updating its +* value in a user interface. These should call the public +* getter and setter methods on the object itself and the +* operations should be subject to validation. <br><br> +* +* storedValueForKey and takeStoredValueForKey are called +* in response to wotonomy actions, like snapshotting, +* faulting, commits, and reverts. These operations should +* bypass the public methods and directly modify the internal +* state of the object without validation. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOKeyValueCoding extends NSKeyValueCoding +{ + /** + * Returns the value for the specified property. + * If the property does not exist, this method should + * call handleQueryWithUnboundKey. + */ + Object valueForKey( String aKey ); + + /** + * Sets the property to the specified value. + * If the property does not exist, this method should + * call handleTakeValueForUnboundKey. + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey. + */ + void takeValueForKey( Object aValue, String aKey ); + + /** + * Returns the value for the private field that + * corresponds to the specified property. + */ + Object storedValueForKey( String aKey ); + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value. + */ + void takeStoredValueForKey( Object aValue, String aKey ); + + /** + * Called by valueForKey when the specified key is + * not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + Object handleQueryWithUnboundKey( String aKey ); + + /** + * Called by takeValueForKey when the specified key + * is not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + void handleTakeValueForUnboundKey( Object aValue, String aKey ); + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types. Implementing + * classes should handle this case appropriately + * or otherwise throw an exception. + */ + void unableToSetNullForKey( String aKey ); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.2 2001/03/28 16:12:30 mpowers + * Documented interface. + * + * Revision 1.1 2001/03/27 23:25:05 mpowers + * Contributing interface, no docs yet. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java new file mode 100644 index 0000000..bc48f58 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingAdditions.java @@ -0,0 +1,176 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSKeyValueCodingAdditions; + +/** +* EOKeyValueCodingAdditions defines an interface for classes +* that need to have more control over the wotonomy's bulk +* property copying and cloning facilities. <br><br> +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public interface EOKeyValueCodingAdditions extends EOKeyValueCoding, NSKeyValueCodingAdditions +{ + + /** + * Static utilities methods that + * call the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public class Utility extends NSKeyValueCodingAdditions.Utility + { + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static void takeValuesFromDictionary( + Object object, Map dictionary) + { + if (object instanceof NSKeyValueCodingAdditions) { + ((NSKeyValueCodingAdditions)object).takeValuesFromDictionary(dictionary); + } else { + DefaultImplementation.takeValuesFromDictionary(object, dictionary); + } + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ +/* + public static void takeValuesFromDictionaryWithMapping( + Object object, NSDictionary dictionary, NSDictionary mapping) + { + if (object instanceof NSKeyValueCodingAdditions) { + ((NSKeyValueCodingAdditions)object).takeValuesFromDictionaryWithMapping(dictionary, mapping); + } else { + DefaultImplementation.takeValuesFromDictionaryWithMapping(object, dictionary, mapping); + } + } +*/ + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static NSDictionary valuesForKeys( + Object object, List keys) + { + if (object instanceof NSKeyValueCodingAdditions) { + return ((NSKeyValueCodingAdditions)object).valuesForKeys(keys); + } else { + return DefaultImplementation.valuesForKeys(object, keys); + } + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ +/* + public static NSDictionary valuesForKeysWithMapping( + Object object, NSDictionary mapping) + { + if (object instanceof NSKeyValueCodingAdditions) { + return ((NSKeyValueCodingAdditions)object).valuesForKeysWithMapping(mapping); + } else { + return DefaultImplementation.valuesForKeysWithMapping(object, mapping); + } + } +*/ + } + + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public class DefaultImplementation extends NSKeyValueCodingAdditions.DefaultImplementation + { + public static void takeValuesFromDictionary( + Object object, NSDictionary dictionary) + { + throw new RuntimeException( "Not implemented yet." ); + } + + public static void takeValuesFromDictionaryWithMapping( + Object object, NSDictionary dictionary, NSDictionary mapping) + { + throw new RuntimeException( "Not implemented yet." ); + } + + public static NSDictionary valuesForKeys( + Object object, List keys) + { + throw new RuntimeException( "Not implemented yet." ); + } + + public static NSDictionary valuesForKeysWithMapping( + Object object, Map mapping) + { + throw new RuntimeException( "Not implemented yet." ); + } + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.3 2001/12/10 15:25:11 mpowers + * Now properly extending EOKeyValueCoding. + * + * Revision 1.2 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.1 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.2 2001/03/28 16:12:30 mpowers + * Documented interface. + * + * Revision 1.1 2001/03/27 23:25:05 mpowers + * Contributing interface, no docs yet. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java new file mode 100644 index 0000000..89e3e91 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueCodingSupport.java @@ -0,0 +1,235 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.IntrospectorException; +import net.wotonomy.foundation.internal.MissingPropertyException; +import net.wotonomy.foundation.internal.NullPrimitiveException; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOKeyValueCodingSupport defines default behavior for +* classes implementing EOKeyValueSupport. <br><br> +* +* On an object that does not implement EOKeyValueCoding, +* wotonomy will call the methods on this class directly. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOKeyValueCodingSupport +{ + /** + * Returns the value for the specified property key + * on the specified object. <br><br> + * + * If the property does not exist, this method calls + * handleQueryWithUnboundKey on the object if it + * implements EOKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class. <br><br> + */ + static public Object valueForKey( + Object anObject, String aKey ) + { + //TODO: may need to handle "." nesting here so + // that handleQueryWithUnboundKey gets called for + // for the nested object, not the parent object + + //Correction: need to handle key paths in + // KeyValueCodingAdditionsSupport. + + try + { + return Introspector.get( anObject, aKey ); + } + catch ( IntrospectorException exc ) + { + if ( anObject instanceof EOKeyValueCoding ) + { + return ((EOKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey ); + } + return handleQueryWithUnboundKey( anObject, aKey ); + } + } + + /** + * Sets the property to the specified value on + * the specified object. + * + * If the property does not exist, this method calls + * handleTakeValueForUnboundKey on the object if it + * implements EOKeyValueCoding, otherwise calls + * handleTakeValueForUnboundKey on this class. + * + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey + * on the object if it implements EOKeyValueCoding, + * otherwise calls unableToSetNullForKey on this class. + */ + static public void takeValueForKey( + Object anObject, Object aValue, String aKey ) + { + //TODO: may need to handle "." nesting here so + // that handleTakeValueForUnboundKey gets called for + // for the nested object, not the parent object + + try + { + Introspector.set( anObject, aKey, aValue ); + } + catch ( NullPrimitiveException exc ) + { + if ( anObject instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)anObject).unableToSetNullForKey( aKey ); + } + else + { + unableToSetNullForKey( anObject, aKey ); + } + } + catch ( MissingPropertyException exc ) + { + if ( anObject instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)anObject).handleTakeValueForUnboundKey( + aValue, aKey ); + } + else + { + handleTakeValueForUnboundKey( anObject, aValue, aKey ); + } + } + + } + + /** + * Returns the value for the private field that + * corresponds to the specified property on + * the specified object. + * + * This implementation currently calls valueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public Object storedValueForKey( + Object anObject, String aKey ) + { + //TODO: this currently just calls valueForKey + return valueForKey( anObject, aKey ); + } + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value on the + * specified object. + * + * This implementation currently calls takeValueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public void takeStoredValueForKey( + Object anObject, Object aValue, String aKey ) + { + //TODO: this currently just calls takeValueForKey + takeValueForKey( anObject, aValue, aKey ); + } + + /** + * Called by valueForKey when the specified key is + * not found on the specified object, if that object + * does not implement EOKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public Object handleQueryWithUnboundKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Key not found for object: " + + aKey + " : " + anObject ); + } + + /** + * Called by takeValueForKey when the specified key + * is not found on the specified object, if that object + * does not implement EOKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void handleTakeValueForUnboundKey( + Object anObject, Object aValue, String aKey ) + { + throw new WotonomyException( + "Key not found for object while setting value: " + + aKey + " : " + anObject + " : " + aValue ); + } + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types, if the specified + * object does not implement EOKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void unableToSetNullForKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Tried to key on object to null: " + + aKey + " : " + anObject ); + } + +} + + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.4 2001/05/18 21:04:33 mpowers + * Reimplemented EditingContext.initializeObject. + * + * Revision 1.3 2001/04/27 00:28:29 mpowers + * Fixed a return value. + * + * Revision 1.2 2001/04/03 20:36:01 mpowers + * Fixed refaulting/reverting/invalidating to be self-consistent. + * + * Revision 1.1 2001/03/28 17:49:33 mpowers + * Implemented EOKeyValueCodingSupport. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java new file mode 100644 index 0000000..799955e --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueQualifier.java @@ -0,0 +1,233 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import net.wotonomy.foundation.NSKeyValueCoding; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOKeyValueQualifier performs a property-based +* comparison against a specified value. The comparison +* is specified in the form of a NSSelector. The +* selector is expected to take two arguments, the +* property value on an object and the comparison value, +* and return a Boolean indicating whether the object +* is qualified. EOQualifier defines selectors that +* may be used in creating EOKeyValueQualifiers. +* +* @author michael@mpowers.net +* @author yjcheung@intersectsoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOKeyValueQualifier extends EOQualifier + implements EOKeyValueArchiving, EOQualifierEvaluation +{ + private String key; + private NSSelector selector; + private Object value; + + /** + * Constructor specifying a property key, a selector, + * and a value for comparison. The selector may be + * one of the constant selectors defined on EOQualifier. + */ + public EOKeyValueQualifier( + String aKey, + NSSelector aSelector, + Object aValue ) + { + key = aKey; + selector = aSelector; + value = aValue; + } + + /** + * Returns the key for this qualifier. + */ + public String key() + { + return key; + } + + /** + * Returns the selector for this qualifier. + */ + public NSSelector selector() + { + return selector; + } + + /** + * Returns the value for this qualifier. + */ + public Object value() + { + return value; + } + + /** + * Evaluates this qualifier for the specified object, + * and returns whether the object is qualified. + * selector() is invoked on the value for key() on the + * specified object, with value() as the parameter. + */ + public boolean evaluateWithObject( Object anObject ) + { + try + { + Object value; + if ( anObject instanceof EOKeyValueCoding ) + { + value = ((EOKeyValueCoding)anObject).valueForKey( key() ); + } + else + { + value = EOKeyValueCodingSupport.valueForKey( anObject, key() ); + } + return ((Boolean)selector.invoke( value(), value)).booleanValue(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + } + + /** + * Returns a string representation of this qualifier. + */ + public String toString() + { + return "( " + key + " " + selector + " " + value + " )"; + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOKeyValueQualifier", "class"); + arch.encodeObject(key, "key"); + NSMutableDictionary d = new NSMutableDictionary(2); + if (value instanceof String) + d.setObjectForKey("NSString", "class"); + else if (value instanceof java.math.BigDecimal) + d.setObjectForKey("NSDecimalNumber", "class"); + else if (value instanceof Number) + d.setObjectForKey("NSNumber", "class"); + else if (value instanceof NSKeyValueCoding.Null) + d.setObjectForKey("EONull", "class"); + if (value != null && !(value instanceof NSKeyValueCoding.Null)) + d.setObjectForKey(value.toString(), "value"); + arch.encodeObject(d, "value"); + String selname = null; + if (selector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike)) + selname = "caseInsensitiveLike:"; + else if (selector.equals(EOQualifier.QualifierOperatorContains)) + selname = "contains:"; + else if (selector.equals(EOQualifier.QualifierOperatorEqual)) + selname = "isEqualTo:"; + else if (selector.equals(EOQualifier.QualifierOperatorGreaterThan)) + selname = "greaterThan:"; + else if (selector.equals(EOQualifier.QualifierOperatorGreaterThanOrEqualTo)) + selname = "greaterThanOrEqualTo:"; + else if (selector.equals(EOQualifier.QualifierOperatorLessThan)) + selname = "lessThan:"; + else if (selector.equals(EOQualifier.QualifierOperatorLessThanOrEqualTo)) + selname = "lessThanOrEqualTo:"; + else if (selector.equals(EOQualifier.QualifierOperatorLike)) + selname = "like:"; + else if (selector.equals(EOQualifier.QualifierOperatorNotEqual)) + selname = "isNotEqualTo:"; + else + selname = selector.name() + ":"; + arch.encodeObject(selname, "selectorName"); + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { + String k = (String)arch.decodeObjectForKey("key"); + Object v = arch.decodeObjectForKey("value"); + NSSelector sel = null; + String sname = (String)arch.decodeObjectForKey("selectorName"); + if (sname.equals("isEqualTo:")) + sel = EOQualifier.QualifierOperatorEqual; + else if (sname.equals("isNotEqualTo:")) + sel = EOQualifier.QualifierOperatorNotEqual; + else if (sname.equals("caseInsensitiveLike:")) + sel = EOQualifier.QualifierOperatorCaseInsensitiveLike; + else if (sname.equals("contains:")) + sel = EOQualifier.QualifierOperatorContains; + else if (sname.equals("greaterThan:")) + sel = EOQualifier.QualifierOperatorGreaterThan; + else if (sname.equals("greaterThanOrEqualTo:")) + sel = EOQualifier.QualifierOperatorGreaterThanOrEqualTo; + else if (sname.equals("lessThan:")) + sel = EOQualifier.QualifierOperatorLessThan; + else if (sname.equals("lessThanOrEqualTo:")) + sel = EOQualifier.QualifierOperatorLessThanOrEqualTo; + else if (sname.equals("like:")) + sel = EOQualifier.QualifierOperatorLike; + EOKeyValueQualifier q = new EOKeyValueQualifier(k, sel, v); + return q; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.10 2003/08/12 01:43:04 chochos + * formally implement EOQualifierEvaluation + * + * Revision 1.9 2003/08/11 19:39:30 chochos + * special conditions for NSKeyValueCoding.NullValue -> EONull + * + * Revision 1.8 2003/08/09 01:22:51 chochos + * qualifiers implement EOKeyValueArchiving + * + * Revision 1.7 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.6 2001/10/31 15:26:06 mpowers + * Fixed typo. + * + * Revision 1.5 2001/10/31 15:25:14 mpowers + * Cleanup of qualifiers. + * + * Revision 1.4 2001/10/30 22:57:28 mpowers + * EOQualifier framework is now working. + * + * Revision 1.3 2001/09/13 15:25:56 mpowers + * Started implementation of the EOQualifier framework. + * + * Revision 1.2 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.1 2001/02/27 03:33:04 mpowers + * Initial draft of the key-value qualifier. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java new file mode 100644 index 0000000..caed8a4 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOKeyValueUnarchiver.java @@ -0,0 +1,165 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.lang.reflect.Method; + +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSKeyValueCoding; + +/** Creates objects from dictionaries that were created with + * EOKeyValueArchiver. +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOKeyValueUnarchiver { + + NSDictionary dict; + Object _delegate; + protected static final Class[] METHOD_ARGS = new Class[]{ EOKeyValueUnarchiver.class }; + + public EOKeyValueUnarchiver(NSDictionary archive) { + super(); + dict = archive; + } + + public void awakeObjects() { + } + + public boolean decodeBoolForKey(String key) { + Object x = dict.objectForKey(key); + if (x == null) + return false; + return (x.equals("true") || x.equals("YES") || x.equals("Y")); + } + + public int decodeIntForKey(String key) { + Object x = dict.objectForKey(key); + if (x == null) + return 0; + if (x instanceof Number) + return ((Number)x).intValue(); + try { + int i = Integer.parseInt(x.toString()); + return i; + } catch (NumberFormatException ex) { + return 0; + } + } + + public Object decodeObjectForKey(String key) { + Object x = dict.objectForKey(key); + if (x == null) + return null; + if (x instanceof NSDictionary) { + NSDictionary d = (NSDictionary)x; + if (d.objectForKey("class") != null) { + String cname = d.objectForKey("class").toString(); + Class _class = null; + if (cname.equals("NSNumber")) { + if (d.objectForKey("value") != null) + return new Long(d.objectForKey("value").toString()); + } else if (cname.equals("NSDecimalNumber")) { + if (d.objectForKey("value") != null) + return new java.math.BigDecimal(d.objectForKey("value").toString()); + } else if (cname.equals("NSString")) { + if (d.objectForKey("value") != null) + return d.objectForKey("value").toString(); + } else if (cname.equals("EONull")) { + return NSKeyValueCoding.NullValue; + } else if (cname.equals("EOFetchSpecification")) { + _class = EOFetchSpecification.class; + } else if (cname.equals("EOKeyValueQualifier")) { + _class = EOKeyValueQualifier.class; + } else if (cname.equals("EONotQualifier")) { + _class = EONotQualifier.class; + } else if (cname.equals("EOAndQualifier")) { + _class = EOAndQualifier.class; + } else if (cname.equals("EOOrQualifier")) { + _class = EOOrQualifier.class; + } else if (cname.equals("EOSortOrdering")) { + _class = EOSortOrdering.class; + } else if (cname.indexOf(".") < 0) { //Load a class without package + try { + _class = Class.forName("net.wotonomy.control." + cname); + } catch (ClassNotFoundException ex) { + } + //search for the class in access + if (_class == null) { + try { + _class = Class.forName("net.wotonomy.access." + cname); + } catch (ClassNotFoundException ex) { + } + } + } else { + try { + _class = Class.forName(cname); + } catch (ClassNotFoundException ex) { + } + } + if (_class == null) + return x; + try { + Method met = _class.getMethod("decodeWithKeyValueUnarchiver", METHOD_ARGS); + return met.invoke(null, new Object[]{ new EOKeyValueUnarchiver(d) }); + } catch (Exception ex) { + ex.printStackTrace(); + return x; + } + } + } + return x; + } + + public Object decodeObjectReferenceForKey(String key) { + return null; + } + + public void ensureObjectAwake(Object obj) { + } + + public void finishInitializationOfObjects() { + } + + public Object parent() { + return null; + } + + public void setDelegate(Object del) { + _delegate = del; + } + public Object delegate() { + return _delegate; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/11 18:17:41 chochos + * decoding of objects now works properly + * + */
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java new file mode 100644 index 0000000..d086840 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONotQualifier.java @@ -0,0 +1,129 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EONotQualifiier negates a specified qualifier, +* evaluating to the opposite of the specified qualifier. +* +* @author michael@mpowers.net +* @author yjcheung@intersectsoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EONotQualifier extends EOQualifier + implements EOKeyValueArchiving, EOQualifierEvaluation +{ + private EOQualifier qualifier; + + public EONotQualifier( + EOQualifier aQualifier ) + { + qualifier = aQualifier; + } + + /** + * Returns the qualifier that this qualifier negates. + */ + public EOQualifier qualifier() + { + return qualifier; + } + + /** + * Evaluates this qualifier for the specified object, + * and returns whether the object is qualified. + * evaluateWithObject is invoked on qualifier + * and the result is negated and returned. + */ + public boolean evaluateWithObject( Object anObject ) + { + return !(qualifier.evaluateWithObject(anObject)); + } + + /** + * Returns a string representation of this qualifier. + */ + public String toString() + { + return (new StringBuffer("Not ").append(qualifier.toString()).toString()); + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { + EOQualifier q = (EOQualifier)arch.decodeObjectForKey("qualifier"); + if (q == null) + return null; + return new EONotQualifier(q); + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EONotQualifier", "class"); + if (qualifier instanceof EOKeyValueArchiving) { + EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); + ((EOKeyValueArchiving)qualifier).encodeWithKeyValueArchiver(ar2); + arch.encodeObject(ar2.dictionary(), "qualifiers"); + } else + throw new WotonomyException("Cannot archive instance of " + qualifier.getClass().getName()); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2003/08/12 01:43:04 chochos + * formally implement EOQualifierEvaluation + * + * Revision 1.8 2003/08/11 19:39:30 chochos + * special conditions for NSKeyValueCoding.NullValue -> EONull + * + * Revision 1.7 2003/08/09 01:24:19 chochos + * implements EOKeyValueArchiving + * + * Revision 1.6 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.5 2001/10/31 15:25:14 mpowers + * Cleanup of qualifiers. + * + * Revision 1.4 2001/10/30 22:57:28 mpowers + * EOQualifier framework is now working. + * + * Revision 1.3 2001/09/13 15:42:20 mpowers + * Fixed another cut/paste typo. + * + * Revision 1.2 2001/09/13 15:41:34 mpowers + * Fixed typo. + * + * Revision 1.1 2001/09/13 15:38:19 mpowers + * Started implementation of the EOQualifier framework. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java new file mode 100644 index 0000000..3b4544e --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EONullValue.java @@ -0,0 +1,113 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.io.Serializable; + +import net.wotonomy.foundation.NSNull; + +/** +* EONullValue is used to represent null in Collections classes +* because List and Map do not specify whether null values +* are allowed and because NSArray and NSDictionary explicitly +* do not allow null values. <br><br> +* +* Use of the static singleton method nullValue() is required +* by this implementation because Java cannot return a singleton +* instance from a constructor. <br><br> +* +* This implementation duplicates NSNull, but the singleton instances +* are of course different. Be careful. I have no idea why this +* class was even created, given that NSNull exists. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EONullValue implements Serializable +{ + private static final EONullValue instance = new EONullValue(); + + /** + * Create a new instance of EONullValue. + */ + private EONullValue () + { + } + + /** + * Constructor specifying name and object. + */ + public static EONullValue nullValue () + { + return instance; + } + + /** + * Returns a human-readable string representation. + */ + public String toString() + { + return "[null]"; + } + + /** + * Hashcode of all instances is zero. + */ + public int hashCode() + { + return 0; + } + + /** + * Implemented to return true for any instance of EONullValue + * and for any instance of NSNull. + */ + public boolean equals( Object anObject ) + { + if ( anObject instanceof EONullValue ) return true; + if ( anObject instanceof NSNull ) return true; + return false; + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.2 2001/03/01 20:35:38 mpowers + * Implemented equals and hashCode. + * + * Revision 1.1 2001/02/26 22:41:51 mpowers + * Implemented null placeholder classes. + * Duplicator now uses NSNull. + * No longer catching base exception class. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java new file mode 100644 index 0000000..cd18e36 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStore.java @@ -0,0 +1,320 @@ +/* +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.control; + +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; + +/** +* EOObjectStore defines an object repository that tracks +* object creations, deletions, and updates made by +* EOEditingContexts. <br><br> +* +* A concrete implementation would probably write these +* changes to some kind of persistent storage, like a +* database. <br><br> +* +* EOEditingContext is itself a subclass of EOObjectStore +* that requires an EOObjectStore parent for committing +* its changes. This means that EOEditingContexts can +* use other EOEditingContexts as their parent, but there +* still must exist an EOObjectStore as the root of the +* editing graph. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EOObjectStore +{ + /** + * Key for the user info of ObjectsChangedInStoreNotifications. + * The key should retrieve an array of deleted EOGlobalIDs. + */ + public static final String DeletedKey = "deleted"; + + /** + * Key for the user info of ObjectsChangedInStoreNotifications. + * The key should retrieve an array of inserted EOGlobalIDs. + */ + public static final String InsertedKey = "inserted"; + + /** + * Key for the user info of ObjectsChangedInStoreNotifications. + * The key should retrieve an array of updated EOGlobalIDs. + * EOEditingContexts should refault their copies of these objects. + */ + public static final String UpdatedKey = "updated"; + + /** + * Key for the user info of ObjectsChangedInStoreNotification. + * The key should retrieve an array of EOGlobalIDs. + */ + public static final String InvalidatedKey = "invalidated"; + + /** + * Key for the NSNotification posted when this object store + * is asked to invalidate all objects. Object of the notification + * will be this object store, and user info will contain the + * InvalidatedKey. + */ + public static final String + InvalidatedAllObjectsInStoreNotification = + "EOInvalidatedAllObjectsInStoreNotification"; + + /** + * Key for the NSNotification posted when this object store + * is changed. Object of the notification will be this object + * store, and user info will contain InsertedKey, UpdatedKey, + * DeletedKey, and InvalidatedKey. + */ + public static final String + ObjectsChangedInStoreNotification = + "EOObjectsChangedInStoreNotification"; + + /** + * Default constructor is responsible for initializing + * internal state. + */ + public EOObjectStore () + { + } + + /** + * Called by editing contexts when they no longer + * need to track the specified id. You will not need + * to call this method, but you use use it for a hint + * that the specified global id is not in use by that + * child editing context. + */ + public void editingContextDidForgetObjectWithGlobalID ( + EOEditingContext aContext, + EOGlobalID aGlobalID ) + { + } + + /** + * Returns a List of objects associated with the object + * with the specified id for the specified property + * relationship, or may return a placeholder array that + * will defer the fetch until accessed (an array fault). + * All objects must be registered the specified editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public abstract NSArray arrayFaultWithSourceGlobalID ( + EOGlobalID aGlobalID, + String aRelationship, + EOEditingContext aContext ); + + /** + * Returns the object for the specified id. + * The returned object may be a fault. + * The object will be registered in the + * specified editing context. + */ + public abstract /*EOEnterpriseObject*/ Object faultForGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext ); + + /** + * Returns a fault representing an object of + * the specified entity type with values from + * the specified dictionary. The fault should + * belong to the specified editing context. + */ + public abstract /*EOEnterpriseObject*/ Object faultForRawRow ( + Map aDictionary, + String anEntityName, + EOEditingContext aContext ); + + /** + * Given a newly instantiated object, this method + * initializes its properties to values appropriate + * for the specified id. The object should already + * belong to the specified editing context. + * This method is called to populate faults. + */ + public abstract void initializeObject ( + /*EOEnterpriseObject*/ Object eo, + EOGlobalID aGlobalID, + EOEditingContext aContext ); + + /** + * Remove all values from all objects in memory, + * turning them into faults, and posts an NSNotification + * that all objects have been invalidated. + * The notification should be named with the string + * constant InvalidatedAllObjectsInStoreNotification + * with this object store as the object and no user info. + */ + public abstract void invalidateAllObjects (); + + /** + * Removes values with the specified ids from memory, + * turning them into faults, and posts a notification + * that those objects have been invalidated. + * The notification should be named with the string + * constant ObjectsChangedInStoreNotification + * with this object store as the object and user info + * containing a key named InvalidateKey that returns + * a List of the EOGlobalIDs of the invalidated objects. + */ + public abstract void invalidateObjectsWithGlobalIDs ( + List aList ); + + /** + * Returns whether the object corresponding to the + * specified id is locked. The concept of object + * locking is implementation-specific. + */ + public abstract boolean isObjectLockedWithGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext ); + + /** + * Locks the object corresponding to the + * specified id is locked. The concept of object + * locking is implementation-specific. + * The lock may be released when objects are + * invalidated or commited, but this behavior + * is not required. + */ + public abstract void lockObjectWithGlobalID ( + EOGlobalID aGlobalID, + EOEditingContext aContext ); + + /** + * Returns a List of objects associated with the object + * with the specified id for the specified property + * relationship. This method may not return an array fault + * because array faults call this method to fetch on demand. + * All objects must be registered the specified editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public abstract NSArray objectsForSourceGlobalID ( + EOGlobalID aGlobalID, + String aRelationship, + EOEditingContext aContext ); + + /** + * Returns a List of objects the meet the criteria of + * the supplied specification. Faults are not allowed in the array. + * Each object is registered with the specified editing context. + * If any object is already fetched in the specified context, + * it is not refetched and that object should be used in the array. + */ + public abstract NSArray objectsWithFetchSpecification ( + EOFetchSpecification aFetchSpec, + EOEditingContext aContext ); + + /** + * Removes all values from the specified object, + * converting it into a fault for the specified id. + * New or deleted objects should not be refaulted. + */ + public abstract void refaultObject ( + Object anObject, + EOGlobalID aGlobalID, + EOEditingContext aContext ); + + /** + * Writes all changes in the specified editing context + * to the respository. The object store is expected to + * post a notification that should be named with the string + * constant ObjectsChangedInStoreNotification + * with this object store as the object and user info + * containing keys named UpdatedKey, InsertedKey, and + * DeletedKey that return Lists of the EOGlobalIDs of the + * corresponding objects. + */ + public abstract void saveChangesInEditingContext ( + EOEditingContext aContext ); +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.15 2003/12/18 15:37:38 mpowers + * Changes to retain ability to work with objects that don't necessarily + * implement EOEnterpriseObject. I would still like to preserve this case + * for general usage, however the access package is free to assume that + * those objects will be EOs and cast appropriately. + * + * Revision 1.14 2003/08/19 01:53:12 chochos + * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * + * Revision 1.13 2002/02/13 21:20:15 mpowers + * Updated comments. + * + * Revision 1.12 2001/05/05 23:05:42 mpowers + * Implemented Array Faults. + * + * Revision 1.11 2001/02/21 21:17:32 mpowers + * Now retaining a reference to the recent changes observer. + * Better documented need to retain reference. + * Started implementing notifications. + * + * Revision 1.10 2001/02/16 22:51:29 mpowers + * Now deep-cloning objects passed between editing contexts. + * + * Revision 1.9 2001/02/16 18:34:19 mpowers + * Implementing nested contexts. + * + * Revision 1.8 2001/02/15 21:13:30 mpowers + * First draft implementation is complete. Now on to debugging. + * + * Revision 1.7 2001/02/14 23:03:02 mpowers + * A near-complete first draft of EOEditingContext. + * + * Revision 1.6 2001/02/13 23:24:29 mpowers + * Implementing more of editing context. + * + * Revision 1.5 2001/02/12 20:36:36 mpowers + * Documented methods. + * + * Revision 1.4 2001/02/09 22:09:34 mpowers + * Completed implementation of EOObjectStore. + * + * Revision 1.3 2001/02/06 15:24:11 mpowers + * Widened parameters on abstract method to fix build. + * + * Revision 1.2 2001/02/06 14:57:42 mpowers + * Defined abstract methods. + * + * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java new file mode 100644 index 0000000..e9a3a6a --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObjectStoreCoordinator.java @@ -0,0 +1,204 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services Corporation + +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 + +$Id: EOObjectStoreCoordinator.java 894 2006-02-16 16:47:14Z cgruber $ + +*/ + +package net.wotonomy.control; + +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSNotification; +/** +* A representation of a channel of communication to the database. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ + +public class EOObjectStoreCoordinator extends EOObjectStore { + + public static final String CooperatingObjectStoreWasAddedNotification = "EOCooperatingObjectStoreWasAddedNotification"; + public static final String CooperatingObjectStoreWasRemovedNotification = "EOCooperatingObjectStoreWasRemovedNotification"; + public static final String CooperatingObjectStoreNeededNotification = "EOCooperatingObjectStoreNeededNotification"; + public static final String GlobalIDKey = "globalID"; + public static final String FetchSpecificationKey = "fetchSpecification"; + public static final String ObjectKey = "object"; + + public static synchronized EOObjectStoreCoordinator defaultCoordinator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized void setDefaultCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public EOObjectStoreCoordinator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void dispose() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + private NSArray _sources() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSArray cooperatingObjectStores() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void addCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void removeCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public EOCooperatingObjectStore objectStoreForGlobalID(EOGlobalID eoglobalid) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public EOCooperatingObjectStore objectStoreForObject(EOEnterpriseObject eoenterpriseobject) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public EOCooperatingObjectStore objectStoreForFetchSpecification(EOFetchSpecification eofetchspecification) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + EOCooperatingObjectStore objectStoreForEntityNamed(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void forwardUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void saveChangesInEditingContext(EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSArray objectsWithFetchSpecification(EOFetchSpecification eofetchspecification, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean isObjectLockedWithGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void lockObjectWithGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public /*EOEnterpriseObject*/ Object faultForGlobalID(EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public /*EOEnterpriseObject*/ Object faultForRawRow(NSDictionary nsdictionary, String s, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSArray arrayFaultWithSourceGlobalID(EOGlobalID eoglobalid, String s, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void editingContextDidForgetObjectWithGlobalID(EOEditingContext eoeditingcontext, EOGlobalID eoglobalid) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSArray objectsForSourceGlobalID(EOGlobalID eoglobalid, String s, EOEditingContext eoeditingcontext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void invalidateObjectsWithGlobalIDs(NSArray nsarray) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void invalidateAllObjects() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void _objectsChangedInSubStore(NSNotification nsnotification) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void _invalidatedAllObjectsInSubStore(NSNotification nsnotification) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void setUserInfo(NSDictionary nsdictionary) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSDictionary userInfo() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** + * @see net.wotonomy.control.EOObjectStore#faultForRawRow(Map, String, EOEditingContext) + */ + public /*EOEnterpriseObject*/ Object faultForRawRow( + Map aDictionary, + String anEntityName, + EOEditingContext aContext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + + /** + * @see net.wotonomy.control.EOObjectStore#initializeObject(Object, EOGlobalID, EOEditingContext) + */ + public void initializeObject( + Object anObject, + EOGlobalID aGlobalID, + EOEditingContext aContext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + + /** + * @see net.wotonomy.control.EOObjectStore#invalidateObjectsWithGlobalIDs(List) + */ + public void invalidateObjectsWithGlobalIDs(List aList) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + + /** + * @see net.wotonomy.control.EOObjectStore#refaultObject(Object, EOGlobalID, EOEditingContext) + */ + public void refaultObject( + Object anObject, + EOGlobalID aGlobalID, + EOEditingContext aContext) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + +} diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java new file mode 100644 index 0000000..d42130c --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java @@ -0,0 +1,547 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +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.control; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; + +/** +* EOObserverCenter is a static singleton-like class +* that manages EOObservings that want to be notified +* of changes to those objects that call +* notifyObserversObjectWillChange() before their +* properties change. <br><br> +* +* Implementation note: Because Java implements the +* Observer pattern in java.util.Observable, this +* class knows how to register itself as an Observer +* with Observables as well. However, users should +* note that Observables only notify their Observers +* of changes after their property has changed. +* EODelayedObservers would see no difference because +* they always receive their notification after all +* changes have taken place. <br><br> +* +* This implementation uses weak references for observers +* and observables. The advantage to this approach is +* that you do not need to explicitly unregister observers +* or observables; they will be unregistered when they are +* garbage-collected. Note that you will need to retain +* a reference to any objects you register or they may +* become unregistered if no other object references them. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOObserverCenter implements Observer +{ + /** + * Would much rather use a WeakHashMap, but that class + * compares by value, and we need to compare by reference. + * This means we need to recreate a weak hashmap with + * the ReferenceKey class below. Using hashtable for + * thread safety. + */ + private static Map observableToObservers = new Hashtable(); + + private static List omniscients = new LinkedList(); + + // suppression count + private static int suppressions = 0; + + // singleton instance - needed for Observer + private static EOObserverCenter instance = null; + + // optimization: remember last request and result + private static Object lastRequest; + private static NSArray lastResult; + + /** + * Registers the specified EOObserving for + * notifications from the specified object. + * An EOObserving can only be registered + * once for a given object. + */ + public static void addObserver( + EOObserving anObserver, Object anObject ) + { + // atomic operation + synchronized ( instance() ) + { + // find observer list + List observers = (List) + observableToObservers.get( new ReferenceKey( anObject ) ); + + // if observer list not found, create and add item + if ( observers == null ) + { + observers = new ArrayList(); + observers.add( new WeakReference( anObserver ) ); + processQueue(); + observableToObservers.put( new ReferenceKey( anObject, queue ), observers ); + + // support for java.util.Observable + if ( anObject instanceof Observable ) + { + ((Observable)anObject).addObserver( instance() ); + } + } + else // observer list found - scan for observer + if ( indexOf( observers, anObserver ) < 0 ) + { + // observer not found, register it + observers.add( new WeakReference( anObserver ) ); + } + + lastRequest = null; + lastResult = null; + } + } + + /** + * Registers the specified EOObserving for notifications + * on all object changes. An EOObserving can be + * registered as an omniscient observer at most once. + * Use omniscient observers with caution. + */ + public static void addOmniscientObserver( + EOObserving anObserver ) + { + if ( indexOf( omniscients, anObserver ) < 0 ) + { + omniscients.add( anObserver ); + } + } + + /** + * Notifies all EOObservings registered for + * notifications for the specified object. + * This method is typically called by objects + * that wish to broadcast a notification before + * a property change takes place, passing itself + * as the argument. + */ + public static void notifyObserversObjectWillChange( + Object anObject ) + { + if ( observerNotificationSuppressCount() == 0 ) + { + List observers = observersForObject( anObject ); + EOObserving o; + Iterator it = observers.iterator(); + while ( it.hasNext() ) + { + o = (EOObserving) it.next(); + o.objectWillChange( anObject ); + } + } + } + + /** + * Returns an observer that is an instance of + * the specified class and + * that is registered for notifications about + * the specified object. If more than one + * such observer exists, which observer is + * returned is undetermined - use + * observersForObject instead. If no observer + * exists, returns null. + */ + public static EOObserving observerForObject( + Object anObject, Class aClass ) + { + List result = observersForObject( anObject ); + if ( result.size() == 0 ) return null; + + Object o; + Iterator it = result.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + if ( aClass.isAssignableFrom( o.getClass() ) ) + { + return (EOObserving) o; + } + } + return null; + } + + /** + * Returns the number of times that notifications + * have been suppressed. This is also the number + * of times that enableObserverNotification must + * be called to allow notifications to take place. + */ + public static int observerNotificationSuppressCount() + { + return suppressions; + } + + /** + * Returns a List of observers for the + * specified object. Returns an empty List + * if no observer has registered for that + * object. + */ + public static NSArray observersForObject( + Object anObject ) + { + synchronized ( instance() ) + { + // optimization: this is called very frequently + // from the same object calling willChange() a + // number of times in a row as it is updating. + if ( lastRequest == anObject ) + { + return lastResult; + } + + NSArray result; + + List references = observerListForObject( anObject ); + if ( references == null ) + { + result = NSArray.EmptyArray; + } + else + { + result = new NSMutableArray(); + Object observer; + Iterator it = references.iterator(); + while ( it.hasNext() ) + { + observer = ((Reference)it.next()).get(); + if ( observer != null ) + { + result.add( observer ); + } + else // reference has expired + { + processQueue(); + it.remove(); // remove from list + // if last observer, unregister observable + if ( references.size() == 0 ) + { + observableToObservers.remove( new ReferenceKey( anObject ) ); + } + } + } + } + + lastRequest = anObject; + lastResult = result; + + return result; + } + + } + + /** + * Returns a reference to the actual list of + * observers for the given object, or null if it + * doesn't exist. + */ + private static List observerListForObject( Object anObject ) + { + return (List) observableToObservers.get( new ReferenceKey( anObject ) ); + } + + /** + * Unregisters the specified observer for + * notifications from the specified object. + */ + public static void removeObserver( + EOObserving anObserver, Object anObject ) + { + // atomic operation + synchronized ( instance() ) + { + lastRequest = null; + lastResult = null; + + List result = observerListForObject( anObject ); + if ( result == null ) return; + int index = indexOf( result, anObserver ); + if ( index == -1 ) return; + + // remove observer from list + result.remove( index ); + + // if last observer, unregister observable + if ( result.size() == 0 ) + { + processQueue(); + observableToObservers.remove( new ReferenceKey( anObject ) ); + } + } + } + + /** + * Unregisters the specified omniscient observer. + */ + public static void removeOmniscientObserver( + EOObserving anObserver ) + { + int index = indexOf( omniscients, anObserver ); + if ( index != -1 ) + { + omniscients.remove( index ); + } + } + + /** + * Enables notifications after they have been + * suppressed by suppressObserverNotification. + * If notifications have been suppressed + * multiple times, this method must be called + * an equal number of times to resume notifications. + * If notifications are not currently suppressed, + * this method does nothing. + */ + public static void enableObserverNotification() + { + if ( suppressions > 0 ) suppressions--; + } + + /** + * Causes notifications to be suppressed until + * the next matching call to enableObserverNotification. + * If this method is called more than once, + * enableObserverNotification must be called an + * equal number of times for notifications to resume. + * This method always causes notifications to cease + * immediately. + */ + public static void suppressObserverNotification() + { + suppressions++; + } + + /** + * Because we're comparing by reference, we need to + * test for the existence of the object directly. + * @return the index or -1 if not found. + */ + private static int indexOf( List aList, Object anObject ) + { + if ( anObject == null ) return -1; + + synchronized ( aList ) + { + int len = aList.size(); + for ( int i = 0; i < len; i++ ) + { + // compare by reference + if ( anObject == ((Reference)aList.get(i)).get() ) + { + return i; + } + } + } + return -1; + } + + /** + * Private singleton instance, so we can be an observer. + */ + private static EOObserverCenter instance() + { + if ( instance == null ) + { + instance = new EOObserverCenter(); + } + return instance; + } + + /** + * Interface Observer + */ + public void update( Observable o, Object arg ) + { + notifyObserversObjectWillChange( o ); + } + + /* Reference queue for cleared WeakKeys */ + private static ReferenceQueue queue = new ReferenceQueue(); + + /* Remove all invalidated entries from the map, that is, remove all entries + whose keys have been discarded. This method should be invoked once by + each public mutator in this class. We don't invoke this method in + public accessors because that can lead to surprising + ConcurrentModificationExceptions. */ + private static void processQueue() + { + synchronized ( instance() ) + { + ReferenceKey rk; + while ((rk = (ReferenceKey)queue.poll()) != null) + { + //System.out.println( "EOObserverCenter.processQueue: removing object" ); + observableToObservers.remove(rk); + } + } + } + + /** + * Private class used to force a hashmap to + * perform key comparisons by reference. + * Retains a weak reference just like WeakHashMap. + */ + static private class ReferenceKey extends WeakReference + { + private int hashCode; + + /** + * Called to create a disposable reference key, + * used for retrieving values from the hashtable. + */ + public ReferenceKey( Object anObject ) + { + super( anObject ); + hashCode = anObject.hashCode(); + } + + /** + * Called to create a reference key that will be + * used as a key in the hashtable, so we need the + * reference queue to later remove the key from the + * table when referred object is no longer in use. + */ + public ReferenceKey( Object anObject, ReferenceQueue aQueue ) + { + super( anObject, aQueue ); + hashCode = anObject.hashCode(); + } + + /** + * Passes through to actual key's hash code. + */ + public int hashCode() + { + return hashCode; + } + + /** + * Compares by reference. + */ + public boolean equals( Object anObject ) + { + if ( ! ( anObject instanceof ReferenceKey ) ) return false; + Object key = get(); + if ( key == null ) return false; + return ( key == ((ReferenceKey)anObject).get() ); + } + } + + private static String debugString() + { + String result = ""; + int count = 0; + synchronized ( instance() ) + { + Object anObject; + Iterator it = observableToObservers.keySet().iterator(); + while ( it.hasNext() ) + { + result += ((Reference)it.next()).get() + " : "; + count++; + /* + Iterator values = ((List)it.next()).iterator(); + while ( values.hasNext() ) + { + anObject = ((Reference)values.next()).get(); + if ( anObject != null ) + { +// if ( anObject instanceof net.wotonomy.ui.MasterDetailAssociation ) + result += anObject.getClass().toString() + " : "; + count++; + } + } + */ + } + result += "["+count+"]"; + } + return result; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2002/10/24 18:18:12 mpowers + * NSArray's are now considered read-only, so we can return our internal + * representation to reduce unnecessary object allocation. + * + * Revision 1.8 2001/02/21 21:17:32 mpowers + * Now retaining a reference to the recent changes observer. + * Better documented need to retain reference. + * Started implementing notifications. + * + * Revision 1.7 2001/02/17 16:52:05 mpowers + * Changes in imports to support building with jdk1.1 collections. + * + * Revision 1.6 2001/02/05 18:45:45 mpowers + * Reduced access back to private for utility methods. + * + * Revision 1.5 2001/02/05 18:42:32 mpowers + * Updated documentation throughout project. + * + * Revision 1.4 2001/01/18 16:57:47 mpowers + * Added debug facility. + * + * Revision 1.3 2001/01/10 16:28:53 mpowers + * Implemented a compare-by-reference weak hashtable + * because WeakHashMap compared by value and we can't + * assume that display groups won't contain objects + * whose values are equivalent. + * + * Revision 1.2 2001/01/09 20:10:19 mpowers + * Now using weak references to track observables and their observers. + * + * Revision 1.1.1.1 2000/12/21 15:46:44 mpowers + * Contributing wotonomy. + * + * Revision 1.8 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java new file mode 100644 index 0000000..e616a33 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverProxy.java @@ -0,0 +1,104 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import net.wotonomy.foundation.NSSelector; + +/** +* A convenience observer for objects that do not or cannot +* subclass EODelayedObserver. EOObserverProxy will invoke +* an NSSelector on an object when it receives a subjectChanged +* message. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOObserverProxy + extends EODelayedObserver +{ + protected Object target; + protected NSSelector selector; + protected int priority; + + /** + * Constructs an EODelayedObserver that will invoke the specified selector + * on the specified object, and will run at the specified priority. + */ + public EOObserverProxy ( + Object anObject, NSSelector aSelector, int aPriority ) + { + target = anObject; + selector = aSelector; + priority = aPriority; + } + + /** + * Constructs an EODelayedObserver that will invoke the specified selector + * on the specified object, and will run at ObserverPriorityThird priority, + * which is the default. + */ + public EOObserverProxy ( + Object anObject, NSSelector aSelector ) + { + this( anObject, aSelector, ObserverPriorityThird ); + } + + /** + * Returns the priority of this observer in the queue. + * This implementation returns the priority specified + * in the constructor. + */ + public int priority () + { + return priority; + } + + /** + * Notifies observer that one or more objects that + * it is observing have changed. The observer should + * check all objects it is observing for changes. + */ + public void subjectChanged () + { + try + { + selector.invoke( target ); + } + catch ( Exception exc ) + { + System.out.println( "Error notifying observer: " ); + exc.printStackTrace(); + } + } + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/10/22 21:55:32 mpowers + * This turns out to be a really useful class. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java new file mode 100644 index 0000000..8ec6e5c --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserving.java @@ -0,0 +1,51 @@ +/* +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.control; + +/** +* A pure java implementation of EOObserving. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOObserving +{ + /** + * Called when the specified object is about to change. + */ + void objectWillChange ( Object anObject ); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:46:44 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java new file mode 100644 index 0000000..7f4b1c6 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOOrQualifier.java @@ -0,0 +1,169 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOOrQualifier contains other EOQualifiers, +* evaluating as true if any of the contained +* qualifiers evaluate as true. +* +* @author michael@mpowers.net +* @author yjcheung@intersectsoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOOrQualifier extends EOQualifier + implements EOKeyValueArchiving, EOQualifierEvaluation +{ + private List qualifiers; + + public EOOrQualifier( + List aQualifierList ) + { + qualifiers = new LinkedList( aQualifierList ); + } + + /** + * Returns a List of qualifiers contained by this qualifier. + */ + public NSArray qualifiers() + { + return new NSArray( qualifiers ); + } + + /** + * Add a new qualifier to the list. + */ + public void addQualifier(EOQualifier qualifier) + { + qualifiers.add(qualifier); + } + + /** + * Evaluates this qualifier for the specified object, + * and returns whether the object is qualified. + * selector() is invoked on the value for key() on the + * specified object, with value() as the parameter. + * + * Note: this has a lazy "or" implementation. Ex. Qal1 or Qal2. + * If Qal1 is evaluated to be true, then it returns true without + * further evaluate Qal2. + */ + public boolean evaluateWithObject( Object anObject ) + { + Iterator it = qualifiers.iterator(); + while( it.hasNext() ) + { + if ( ((EOQualifier) it.next()).evaluateWithObject(anObject) ) + { + return true; + } + } + return false; + } + + /** + * Returns a string representation of this qualifier. + */ + public String toString() + { + StringBuffer myBuf = new StringBuffer("("); + Iterator it = qualifiers.iterator(); + while (it.hasNext()) + { + myBuf = myBuf.append(((EOQualifier) it.next()).toString()).append(" or "); + } + String myStr = myBuf.toString(); + myStr = myStr.substring(0, myStr.lastIndexOf(" or ")).concat(")"); + return myStr; + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { + NSArray a = (NSArray)arch.decodeObjectForKey("qualifiers"); + if (a == null) + return null; + NSMutableArray l = new NSMutableArray(); + for (int i = 0; i < a.count(); i++) { + NSDictionary d = (NSDictionary)a.objectAtIndex(i); + EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d); + EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua); + if (q != null) + l.addObject(q); + } + return new EOAndQualifier(l); + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOOrQualifier", "class"); + NSMutableArray arr = new NSMutableArray(qualifiers.size()); + for (int i = 0; i < qualifiers.size(); i++) { + EOQualifier q = (EOQualifier)qualifiers.get(i); + if (q instanceof EOKeyValueArchiving) { + EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); + ((EOKeyValueArchiving)q).encodeWithKeyValueArchiver(ar2); + arr.addObject(ar2.dictionary()); + } else + throw new WotonomyException("Cannot archive instance of " + q.getClass().getName()); + } + arch.encodeObject(arr, "qualifiers"); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.6 2003/08/12 01:43:04 chochos + * formally implement EOQualifierEvaluation + * + * Revision 1.5 2003/08/09 01:22:51 chochos + * qualifiers implement EOKeyValueArchiving + * + * Revision 1.4 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.3 2001/10/31 15:25:14 mpowers + * Cleanup of qualifiers. + * + * Revision 1.2 2001/10/30 22:57:28 mpowers + * EOQualifier framework is now working. + * + * Revision 1.1 2001/09/13 15:25:56 mpowers + * Started implementation of the EOQualifier framework. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java new file mode 100644 index 0000000..6fffea4 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifier.java @@ -0,0 +1,680 @@ +/* +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.control; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.NSSelector; +import net.wotonomy.foundation.NSSet; +import net.wotonomy.foundation.internal.ValueConverter; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* EOQualifiers are used to perform property-based +* qualifications on objects: for a set of criteria, +* a qualifier either qualifies or disqualifies an +* given object. EOKeyValueQualifiers can be joined +* by EOAndQualifier and EOOrQualifier, and so can +* form a tree of qualifications. <br><br> +* +* Certain qualifiers +* can accept a variable in place of a value; variable +* names are marked by a "$", as in "$name". Variables +* are resolved with the qualifierWithBindings() method. +* +* @author michael@mpowers.net +* @author yjcheung@intersectsoft.com +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public abstract class EOQualifier +{ + public static final NSSelector + QualifierOperatorCaseInsensitiveLike = new OperatorCaseInsensitiveLike(); + public static final NSSelector + QualifierOperatorContains = new OperatorContains(); + public static final NSSelector + QualifierOperatorEqual = new OperatorEqual(); + public static final NSSelector + QualifierOperatorGreaterThan = new OperatorGreaterThan(); + public static final NSSelector + QualifierOperatorGreaterThanOrEqualTo = new OperatorGreaterThanOrEqualTo(); + public static final NSSelector + QualifierOperatorLessThan = new OperatorLessThan(); + public static final NSSelector + QualifierOperatorLessThanOrEqualTo = new OperatorLessThanOrEqualTo(); + public static final NSSelector + QualifierOperatorLike = new OperatorLike(); + public static final NSSelector + QualifierOperatorNotEqual = new OperatorNotEqual(); + + /** + * Default constructor. + */ + public EOQualifier () + { + } + + /** + * Adds all qualifier keys in this qualifier to + * the specified Set, which is expected to be + * mutable. The tree of qualifiers is traversed + * and the left-hand-side of each expression is + * added to the set. + */ + public void addQualifierKeysToSet ( Set aSet ) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns a Set of all property names used for + * comparisons by this qualifier. The tree of + * qualifiers is traversed and the left-hand-side + * of each expression is added to the set. + */ + public NSSet allQualifierKeys () + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns a List containing the variables used + * at compare-time by this qualifier. Each variable + * will appear only once in the list. + */ + public NSArray bindingKeys () + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns whether the specified object meets the + * criteria defined by this qualifier. + */ + public boolean evaluateWithObject ( Object anObject ) + { + return true; + } + + /** + * Returns the key (which can be a key path) that + * is tested against the specified binding variable. + * The tree is traversed looking for the first instance + * of the specified variable, and the corresponding + * left-hand-side of the expression is returned. + */ + public String keyPathForBindingKey ( String aVariable ) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns a qualifier that is like this qualifier, + * except all variables will be replaced with values + * from the specified Map whose keys match the variable + * names. If requireAll is true, an exception will be + * thrown if there is no key that matches on of the + * variables in the tree; otherwise, the qualifier + * containing the unmatched variable is removed. + */ + public EOQualifier qualifierWithBindings ( + Map aMap, + boolean requireAll ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + + /** + * Tests whether all the keys in this qualifier can + * be applied to an object of the specified class. + * @return A Throwable if the validation fails, + * otherwise returns null if the class can be used + * with this qualifier. + */ + public Throwable validateKeysWithRootClassDescription ( + Class aClass ) + { + throw new WotonomyException( "Not implemented yet." ); + } + + // statics + + /** + * Convenience to retain only those objects from the specified + * List that meet the specified qualifier's requirements. + */ + public static void filterArrayWithQualifier ( + List anObjectList, EOQualifier aQualifier ) + { + ListIterator iterator = anObjectList.listIterator(); + while ( iterator.hasNext() ) + { + if ( ! aQualifier.evaluateWithObject( iterator.next() ) ) + { + iterator.remove(); + } + } + } + + /** + * Convenience to return a List consisting only + * of those objects in the specified List that meet + * the specified qualifier's requirements. + */ + public static NSArray filteredArrayWithQualifier ( + List anObjectList, EOQualifier aQualifier ) + { + Object o; + List result = new LinkedList(); + Iterator iterator = anObjectList.iterator(); + while ( iterator.hasNext() ) + { + o = iterator.next(); + if ( aQualifier.evaluateWithObject( o ) ) + { + result.add( o ); + } + } + return new NSArray( (Collection) result ); + } + + /** + * Convenience to create a set of EOKeyValueQualifiers + * joined by an EOAndQualifier. Each pair of keys and + * values are used to create EOKeyValueQualifiers. + */ + public static EOQualifier + qualifierToMatchAllValues ( Map aMap ) + { + Object key, value; + List qualifierList = new LinkedList(); + Iterator iterator = aMap.keySet().iterator(); + while ( iterator.hasNext() ) + { + key = iterator.next(); + value = aMap.get( key ); + qualifierList.add( new EOKeyValueQualifier( + key.toString(), QualifierOperatorEqual, value ) ); + } + return new EOAndQualifier( qualifierList ); + } + + /** + * Convenience to create a set of EOKeyValueQualifiers + * joined by an EOOrQualifier. Each pair of keys and + * values are used to create EOKeyValueQualifiers. + */ + public static EOQualifier + qualifierToMatchAnyValue ( Map aMap ) + { + Object key, value; + List qualifierList = new LinkedList(); + Iterator iterator = aMap.keySet().iterator(); + while ( iterator.hasNext() ) + { + key = iterator.next(); + value = aMap.get( key ); + qualifierList.add( new EOKeyValueQualifier( + key.toString(), QualifierOperatorEqual, value ) ); + } + return new EOOrQualifier( qualifierList ); + } + + /** + * Returns an EOQualifier that meets the criteria + * represented by the specified string and variable + * length argument list. This method parses the string + * and returns a tree of qualifiers. Each token beginning + * with "%" is replaced with the corresponding object + * from the argument list. The parser recognizes the + * operation tokens returned from the allQualifierOperators() + * method. + */ + public static EOQualifier qualifierWithQualifierFormat ( + String aString, List anArgumentList ) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns a List of operators that are supported for + * relational operations. This excludes string comparison + * operators. + */ + public static NSArray relationalQualifierOperators () + { + NSMutableArray result = new NSMutableArray(); + BaseSelector selector; + Iterator iterator = allQualifierOperators().iterator(); + while ( iterator.hasNext() ) + { + selector = (BaseSelector) iterator.next(); + if ( selector.isRelationalOperator() ) + { + result.addObject( selector ); + } + } + return result; + } + + /** + * Returns a List of valid operators. + */ + public static NSArray allQualifierOperators () + { + return operators.allKeys(); + } + + /** + * Returns a selector the corresponds to the operation + * represented by the specified string. For example, + * ">" represents QualifierOperatorGreaterThan. + */ + public static NSSelector operatorSelectorForString ( + String anOperatorString ) + { + return (NSSelector) + operators.objectForKey( anOperatorString ); + } + + /** + * Returns a string the corresponds to the operation + * represented by the specified selector. For example, + * QualifierOperatorGreaterThan is represented with ">". + */ + public static String stringForOperatorSelector ( + NSSelector aSelector ) + { + return (String) + operators.allKeysForObject( aSelector ).lastObject(); + } + + /** + * Returns a string representation of this qualifier. + */ + public String toString() + { + //TODO: implement this + return super.toString(); + } + + // built-in qualifiers + + private static NSMutableDictionary operators; + static + { + operators = new NSMutableDictionary(); + operators.setObjectForKey( + QualifierOperatorCaseInsensitiveLike, + QualifierOperatorCaseInsensitiveLike.toString() ); + operators.setObjectForKey( + QualifierOperatorContains, + QualifierOperatorContains.toString() ); + operators.setObjectForKey( + QualifierOperatorEqual, + QualifierOperatorEqual.toString() ); + operators.setObjectForKey( + QualifierOperatorGreaterThan, + QualifierOperatorGreaterThan.toString() ); + operators.setObjectForKey( + QualifierOperatorGreaterThanOrEqualTo, + QualifierOperatorGreaterThanOrEqualTo.toString() ); + operators.setObjectForKey( + QualifierOperatorLessThan, + QualifierOperatorLessThan.toString() ); + operators.setObjectForKey( + QualifierOperatorLessThanOrEqualTo, + QualifierOperatorLessThanOrEqualTo.toString() ); + operators.setObjectForKey( + QualifierOperatorLike, + QualifierOperatorLike.toString() ); + operators.setObjectForKey( + QualifierOperatorNotEqual, + QualifierOperatorNotEqual.toString() ); + } + + static private abstract class BaseSelector extends NSSelector + { + public String name () + { + return "BaseSelector"; + } + + public Class[] parameterTypes () + { + return new Class[] { Object.class, Object.class }; + } + + public Method methodOnClass (Class aClass) + throws NoSuchMethodException + { + throw new NoSuchMethodException(); + } + + public Method methodOnObject (Object anObject) + throws NoSuchMethodException + { + throw new NoSuchMethodException(); + } + + public boolean implementedByClass (Class aClass) + { + return true; + } + + public boolean implementedByObject (Object anObject) + { + return true; + } + + public boolean isRelationalOperator() + { + return true; + } + + public Object invoke (Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return qualify( anObject, parameters[0] ); + } + + abstract protected Boolean qualify( Object target, Object parameter ); + + protected int doCompare(Object o1, Object o2) + { + Class firstClass = o1.getClass(); + Class secondClass = o2.getClass(); + + if ( ! ( secondClass.equals( firstClass ) ) ) + { + Object converted = null; + if ( o2 instanceof Comparable ) + { + converted = ValueConverter.convertObjectToClass( o1, secondClass ); + if (converted != null) + { + o1 = converted; + } + } + + if (converted == null && (o1 instanceof Comparable)) + { + converted = ValueConverter.convertObjectToClass( o2, firstClass ); + if ( converted != null ) + { + o2 = converted; + } + } + + if (converted == null) + { + throw new WotonomyException("Qualifier: Not Comparable Objects"); + // no way to compare + } + } + + return ((Comparable)o2).compareTo( o1 ); + } + + } + + + static class OperatorCaseInsensitiveLike extends BaseSelector { + + public boolean isRelationalOperator() + { + return false; + } + + protected Boolean qualify( Object o1, Object o2 ) + { + String myString1 = o1.toString(); + String myString2 = o2.toString(); + myString1 = myString1.toLowerCase(); + myString2 = myString2.toLowerCase(); + StringTokenizer st = new StringTokenizer(myString1, "%"); + + while (st.hasMoreTokens()) { + String part = st.nextToken(); + int index = myString2.indexOf(part); + if (index > -1) + { + myString2 = myString2.substring(index + part.length()); + } + else + { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + + } + + public String toString() + { + return "caseInsensitiveLike"; + } + } + + static class OperatorContains extends BaseSelector { + + public boolean isRelationalOperator() + { + return false; + } + + protected Boolean qualify( Object o1, Object o2 ) + { + String myString1 = o1.toString(); + String myString2 = o2.toString(); + return new Boolean( + myString2.indexOf(myString1) > -1 ); + } + + public String toString() + { + return "contains"; + } + } + + static class OperatorEqual extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean( doCompare(o1, o2) == 0 ); + } + + public String toString() + { + return "="; + } + } + + static class OperatorGreaterThan extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean( doCompare(o1, o2) > 0 ); + } + + public String toString() + { + return ">"; + } + } + + static class OperatorGreaterThanOrEqualTo extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean( doCompare(o1, o2) >= 0 ); + } + + public String toString() + { + return new String(" >= "); + } + } + + static class OperatorLessThan extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean( doCompare(o1, o2) < 0 ); + } + + public String toString() + { + return ">"; + } + } + + static class OperatorLessThanOrEqualTo extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean (doCompare(o1, o2) <= 0); + } + + public String toString() + { + return "<="; + } + } + + static class OperatorLike extends BaseSelector { + + public boolean isRelationalOperator() + { + return false; + } + + protected Boolean qualify( Object o1, Object o2 ) + { + String myString1 = o1.toString(); + String myString2 = o2.toString(); + StringTokenizer st = new StringTokenizer(myString1, "%"); + while (st.hasMoreTokens()) { + String part = st.nextToken(); + int index = myString2.indexOf(part); + if (index > -1) + { + myString2 = myString2.substring(index + part.length()); + } + else + { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + } + + public String toString() + { + return "like"; + } + } + + static class OperatorNotEqual extends BaseSelector { + + protected Boolean qualify( Object o1, Object o2 ) + { + return new Boolean(!(o1.equals(o2))); + } + + public String toString() + { + return "!="; + } + } + + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver ua) { + String cname = (String)ua.decodeObjectForKey("class"); + if (cname.equals("EOKeyValueQualifier")) + return (EOQualifier)EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua); + if (cname.equals("EOAndQualifier")) + return (EOQualifier)EOAndQualifier.decodeWithKeyValueUnarchiver(ua); + if (cname.equals("EOOrQualifier")) + return (EOQualifier)EOOrQualifier.decodeWithKeyValueUnarchiver(ua); + if (cname.equals("EONotQualifier")) + return (EOQualifier)EONotQualifier.decodeWithKeyValueUnarchiver(ua); + return null; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.10 2003/08/09 01:22:51 chochos + * qualifiers implement EOKeyValueArchiving + * + * Revision 1.9 2001/11/04 18:29:11 mpowers + * Better handling for non-string types used with non-relational operators. + * + * Revision 1.8 2001/10/31 15:25:14 mpowers + * Cleanup of qualifiers. + * + * Revision 1.7 2001/10/30 22:57:28 mpowers + * EOQualifier framework is now working. + * + * Revision 1.6 2001/10/30 22:16:37 mpowers + * Implemented operators as selectors. + * + * Revision 1.5 2001/09/14 14:21:28 mpowers + * Updated javadoc. + * + * Revision 1.3 2001/09/13 15:25:56 mpowers + * Started implementation of the EOQualifier framework. + * + * Revision 1.2 2001/02/27 03:33:04 mpowers + * Initial draft of the key-value qualifier. + * + * Revision 1.1.1.1 2000/12/21 15:46:47 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java new file mode 100644 index 0000000..87769b8 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOQualifierEvaluation.java @@ -0,0 +1,42 @@ +/* +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.control; + +/** +* EOQualifiers that want to perform in-memory +* evaluation should implement this interface. <br><br> +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOQualifierEvaluation { + + public boolean evaluateWithObject(Object eo); + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/08/12 01:42:17 chochos + * formally declare this interface + * + */ +
\ No newline at end of file diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java new file mode 100644 index 0000000..568b555 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EORelationshipManipulation.java @@ -0,0 +1,76 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** +* EORelationshipManipulation provides methods for generically +* adding and removing relationships between objects, handling +* both one-way and reciprocal relationships. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EORelationshipManipulation +{ + /** + * Adds the specified object to the relationship on this + * object specified by the key. For to-one relationships, + * this operation is the same as valueForKey. + */ + void addObjectToPropertyWithKey( + Object anObject, String aKey ); + + /** + * Removes the specified object from the relationship on + * this object specified by the key. For to-one relationships, + * this operation is the same as takeValueForKey with a null + * value. + */ + void removeObjectFromPropertyWithKey( + Object anObject, String aKey ); + + /** + * As addObjectToProperty with key, but also performs the + * reciprocal operation on the other side of the relationship. + */ + void addObjectToBothSidesOfRelationshipWithKey( + EORelationshipManipulation anObject, String aKey ); + + /** + * As removeObjectFromPropertyWithKey with key, but also performs the + * reciprocal operation on the other side of the relationship. + */ + void removeObjectFromBothSidesOfRelationshipWithKey( + EORelationshipManipulation anObject, String aKey ); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java new file mode 100644 index 0000000..789b6da --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOSortOrdering.java @@ -0,0 +1,406 @@ +/* +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.control; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSSelector; + +/** +* EOSortOrdering defines a sort key and operation. +* DisplayGroups use lists of EOSortOrdering to determine +* how to order their items. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOSortOrdering implements Serializable, EOKeyValueArchiving +{ + /** + * Sorts items in ascending order. + */ + public static final + NSSelector CompareAscending = new CompareAscendingComparator(); + + /** + * Sorts items in descending order. + */ + public static final + NSSelector CompareDescending = new CompareDescendingComparator(); + + /** + * Sorts items' string representations in ascending order + * in a case insensitive manner. + */ + public static final + NSSelector CompareCaseInsensitiveAscending = + new CompareCaseInsensitiveAscendingComparator(); + + /** + * Sorts items' string representations in descending order + * in a case insensitive manner. + */ + public static final + NSSelector CompareCaseInsensitiveDescending = + new CompareCaseInsensitiveDescendingComparator(); + + protected String key; + protected NSSelector selector; + + /** + * Factory-style constructor returns a new EOSortOrdering instance + * with the specified key and selector. Neither may be null. + */ + public static EOSortOrdering sortOrderingWithKey(String key, NSSelector selector) + { + return new EOSortOrdering( key, selector ); + } + + /** + * Constructor creates an EOSortOrdering that uses the + * specified key and selector. Neither may be null. + */ + public EOSortOrdering( String aKey, NSSelector aSelector ) + { + key = aKey; + selector = aSelector; + } + + /** + * Constructor creates an EOSortOrdering that uses the + * specified key and comparator. Neither may be null. + * Not in the spec. + */ + public EOSortOrdering( String aKey, Comparator aComparator ) + { + key = aKey; + selector = new NSSelector( aKey, aComparator ); + } + + /** + * Returns the property key. + */ + public String key() + { + return key; + } + + /** + * Returns the selector. + */ + public NSSelector selector() + { + return selector; + } + + public String toString() + { + return "[EOSortOrdering: key='"+key+"' selector='"+selector+"']"; + } + + public boolean equals( Object anObject ) + { + if ( anObject instanceof EOSortOrdering ) + { + EOSortOrdering x = (EOSortOrdering) anObject; + if ( selector().equals( x.selector() ) ) + { + if ( key().equals( x.key() ) ) + { + return true; + } + } + } + return false; + } + + /** + * Sorts the specified list in place according to the specified + * list of EOSortOrderings. The items will be sorted first by the + * first ordering, and items with equal values for that property + * will be sorted by the next ordering, and so on. + */ + public static void sortArrayUsingKeyOrderArray( + List anObjectList, List aSortOrderingList ) + { + List keys = new ArrayList( aSortOrderingList ); + Collections.reverse( keys ); + Iterator it = keys.iterator(); + EOSortOrdering sortOrdering; + while ( it.hasNext() ) + { + sortOrdering = (EOSortOrdering) it.next(); + Collections.sort( anObjectList, + new DelegatingComparator( + sortOrdering.key(), sortOrdering.selector() ) ); + } + } + + /** + * Sorts the specified list in place according to the specified + * list of EOSortOrderings. The items will be sorted first by the + * first ordering, and items with equal values for that property + * will be sorted by the next ordering, and so on. + */ + public static NSArray sortedArrayUsingKeyOrderArray( + List anObjectList, List aSortOrderingList ) + { + NSArray result = new NSMutableArray(); + result.addAll( anObjectList ); + sortArrayUsingKeyOrderArray( result, aSortOrderingList ); + return result; + } + + public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { + String k = (String)arch.decodeObjectForKey("key"); + String sname = (String)arch.decodeObjectForKey("selectorName"); + NSSelector sel = null; + if (sname.equals("compareAscending:")) + sel = CompareAscending; + else if (sname.equals("compareDescending:")) + sel = CompareDescending; + else if (sname.equals("compareCaseInsensitiveAscending:")) + sel = CompareCaseInsensitiveAscending; + else if (sname.equals("compareCaseInsensitiveDescending:")) + sel = CompareCaseInsensitiveAscending; + else { + if (sname.endsWith(":")) + sname = sname.substring(0, sname.length()-1); + sel = new NSSelector(sname, new Class[]{ Object.class }); + } + return new EOSortOrdering(k, sel); + } + + public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { + arch.encodeObject("EOSortOrdering", "class"); + arch.encodeObject(key(), "key"); + if (selector.equals(CompareAscending)) + arch.encodeObject("compareAscending:", "selectorName"); + else if (selector.equals(CompareDescending)) + arch.encodeObject("compareDescending:", "selectorName"); + else if (selector.equals(CompareCaseInsensitiveAscending)) + arch.encodeObject("compareCaseInsensitiveAscending:", "selectorName"); + else if (selector.equals(CompareCaseInsensitiveAscending)) + arch.encodeObject("compareCaseInsensitiveDescending:", "selectorName"); + else + arch.encodeObject(selector.name() + ":", "selectorName"); + } + + private static class CompareAscendingComparator + extends NSSelector + { + public int compare(Object o1, Object o2) + { + if ( o1 instanceof Comparable ) + { + if ( o2 instanceof Comparable ) + { + return ((Comparable)o1).compareTo( o2 ); + } + } + + // null handling: null is less than any object + + if ( o1 == null ) + { + if ( o2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else // o1 != null + if ( o2 == null ) + { + return 1; + } + + // fall back on string representation comparison + + return o1.toString().compareTo( o2.toString() ); + } + + public boolean equals(Object obj) + { + return ( this == obj ); + } + } + + private static class CompareDescendingComparator + extends CompareAscendingComparator + { + public int compare(Object o1, Object o2) + { + return -1 * super.compare( o1, o2 ); + } + } + + private static class CompareCaseInsensitiveAscendingComparator + extends NSSelector + { + public int compare(Object o1, Object o2) + { + // null handling: null is less than any object + + if ( o1 == null ) + { + if ( o2 == null ) + { + return 0; + } + else + { + return -1; + } + } + else // o1 != null + if ( o2 == null ) + { + return 1; + } + + return o1.toString().toLowerCase().compareTo( + o2.toString().toLowerCase() ); + } + + public boolean equals(Object obj) + { + return ( this == obj ); + } + } + + private static class CompareCaseInsensitiveDescendingComparator + extends CompareCaseInsensitiveAscendingComparator + { + public int compare(Object o1, Object o2) + { + return -1 * super.compare( o1, o2 ); + } + } + + private static class DelegatingComparator implements Comparator + { + private String key; + private Comparator comparator; + + public DelegatingComparator( String aKey, Comparator aComparator ) + { + key = aKey; + comparator = aComparator; + } + + public int compare(Object o1, Object o2) + { + Object v1, v2; + if ( o1 instanceof EOKeyValueCoding ) + { + v1 = ((EOKeyValueCoding)o1).valueForKey( key ); + } + else + { + v1 = EOKeyValueCodingSupport.valueForKey( o1, key ); + } + if ( o2 instanceof EOKeyValueCoding ) + { + v2 = ((EOKeyValueCoding)o2).valueForKey( key ); + } + else + { + v2 = EOKeyValueCodingSupport.valueForKey( o2, key ); + } + return comparator.compare( v1, v2 ); + } + + public boolean equals(Object obj) + { + return ( this == obj ); + } + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.14 2003/08/11 19:39:52 chochos + * now encodes/decodes correctly. + * + * Revision 1.13 2003/08/09 01:29:56 chochos + * implements EOKeyValueArchiving + * + * Revision 1.12 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.11 2003/02/07 20:23:23 mpowers + * Provided backwards compatibility for comparators. + * + * Revision 1.10 2003/01/18 23:46:58 mpowers + * EOSortOrdering is now correctly using NSSelectors. + * + * Revision 1.9 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.8 2002/03/01 20:23:27 mpowers + * Implemented equals. + * + * Revision 1.7 2002/02/06 21:15:04 mpowers + * Added null handling to CompareCaseInsensitiveAscendingComparator. + * + * Revision 1.6 2001/12/01 23:51:24 mpowers + * Made serializable. + * + * Revision 1.5 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.4 2001/01/24 22:15:52 mpowers + * Fixed npe when comparing nulls. + * + * Revision 1.3 2001/01/12 19:11:56 mpowers + * Fixed table column click sorting. + * + * Revision 1.2 2001/01/12 17:23:46 mpowers + * Inner classes are now private. + * + * Revision 1.1 2001/01/11 20:34:26 mpowers + * Implemented EOSortOrdering and added support in framework. + * Added header-click to sort table columns. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java new file mode 100644 index 0000000..44ba855 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOTemporaryGlobalID.java @@ -0,0 +1,219 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.net.InetAddress; + +/** +* EOTemporaryGlobalID is a network-wide unique key. +* This is used by EOEditingContext to construct temporary +* ids when new objects are created. <br><br> +* +* The specified format of the key is a byte array: +* < Sequence [2], ProcessID [2], Time [4], IP Addr [4] >, +* but because java does not allow access to the process id, +* the timestamp of when this class is first loaded is used +* to simulate a process id. +* +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOTemporaryGlobalID + extends EOGlobalID +{ + /** + * Holds the length in bytes of the key that is generated. + */ + public static final int UniqueBinaryKeyLength = 12; + + private static int sequence; + private static byte[] processid; // 2 bytes + private static byte[] ipaddr; // 4 bytes + + static // static initializer + { + // init sequence + sequence = 0; + + // init processid + processid = new byte[2]; + long time = System.currentTimeMillis(); + processid[1] = (byte) time; + time = time >> 8; + processid[0] = (byte) time; + + // init ipaddr + ipaddr = new byte[4]; + try + { + ipaddr = InetAddress.getLocalHost().getAddress(); + } + catch ( Exception exc ) + { + // could not obtain ip address - use pid twice + ipaddr[0] = processid[0]; + ipaddr[1] = processid[1]; + ipaddr[2] = processid[0]; + ipaddr[3] = processid[1]; + } + } + + private byte[] key; + private int hashCode; + + /** + * Generates a new id with a unique key. + */ + public EOTemporaryGlobalID() + { + key = new byte[UniqueBinaryKeyLength]; + + // init sequence (important byte first) + key[0] = (byte) ( sequence ); + key[1] = (byte) ( sequence >> 8 ); + sequence++; + + // populate pid (important byte first) + key[2] = processid[1]; + key[3] = processid[0]; + + // init time (important byte first) + long time = System.currentTimeMillis(); + key[4] = (byte) time; + time = time >> 8; + key[5] = (byte) time; + time = time >> 8; + key[6] = (byte) time; + time = time >> 8; + key[7] = (byte) time; + + // populate ipaddr + key[8] = ipaddr[0]; + key[9] = ipaddr[1]; + key[10] = ipaddr[2]; + key[11] = ipaddr[3]; + + // use string's hash code + hashCode = new String( key ).hashCode(); + } + + /** + * Private constructor for cloning. + */ + private EOTemporaryGlobalID( byte[] aKey ) + { +// key = aKey; // this might be faster + + // make copy of key - might be safer + key = new byte[ UniqueBinaryKeyLength ]; + for ( int i = 0; i < UniqueBinaryKeyLength; i++ ) + { + key[i] = aKey[i]; + } + + // use string's hash code + hashCode = new String( aKey ).hashCode(); + } + + /** + * Returns true. + */ + public boolean isTemporary() + { + return true; + } + + /** + * Returns whether the keys are equal. + */ + public boolean equals( Object anObject ) + { + if ( ! ( anObject instanceof EOTemporaryGlobalID ) ) + return false; + + byte[] otherKey = ((EOTemporaryGlobalID)anObject).key; + + for ( int i = 0; i < UniqueBinaryKeyLength; i++ ) + { + if ( key[i] != otherKey[i] ) return false; + } + return true; + } + + /** + * Returns a copy of this object. + */ + public Object clone() + { + // faster than super.clone() + return new EOTemporaryGlobalID( key ); + } + + public int hashCode() + { + return hashCode; + } + + /** + * Returns a string representation of this key. + * This is a 24-character string with each pair + * of characters holding a hexadecimal value that + * is 128 more than the value of the corresponding + * byte (to account for two's complement). + */ + public String toString() + { + String hex; + StringBuffer buffer = new StringBuffer(); + for ( int i = 0; i < key.length; i++ ) + { + // get string: adjust for two's complement + hex = Integer.toHexString( key[i]+128 ); + // pad with zero so we take two characters + if ( hex.length() == 1 ) hex = "0" + hex; + // append hex code + buffer.append( hex ); + } + return buffer.toString(); + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2001/04/29 22:02:45 mpowers + * Work on id transposing between editing contexts. + * + * Revision 1.3 2001/02/15 21:13:30 mpowers + * First draft implementation is complete. Now on to debugging. + * + * Revision 1.2 2001/02/14 23:03:02 mpowers + * A near-complete first draft of EOEditingContext. + * + * Revision 1.1 2001/02/13 23:24:29 mpowers + * Implementing more of editing context. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java new file mode 100644 index 0000000..a0aa4db --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOValidation.java @@ -0,0 +1,72 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +/** +* EOValidation provides methods for validating a operation +* on an object as a whole, rather than on an individual property. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface EOValidation +{ + /** + * Validates this object for delete. + * Throws an exception if this object cannot be deleted. + */ + void validateForDelete(); + + /** + * Validates this object for insertion into the external store. + * Throws an exception if this object cannot be inserted. + * Validations here should be specific to insertion. + * Implementations may call validateForSave(). + */ + void validateForInsert(); + + /** + * Validates this object for a commit to the external store. + * Throws an exception if this object cannot be committed. + * Validations here are not specific to either inserts or updates. + */ + void validateForSave(); + + /** + * Validates this object for update to the external store. + * Throws an exception if this object cannot be updated. + * Validations here should be specific to updates. + * Implementations may call validateForSave(). + */ + void validateForUpdate(); +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2001/11/13 04:13:59 mpowers + * Added interfaces needed to begin work on EOCustomObject. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java new file mode 100644 index 0000000..cc91fd7 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOVectorKeyGlobalID.java @@ -0,0 +1,79 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2001 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.control; + +/** +* +* @author ezamudio@nasoft.com +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class EOVectorKeyGlobalID extends EOKeyGlobalID { + + protected Object[] _keyValues; + + public EOVectorKeyGlobalID(String entityName, Object[] values) { + super(entityName, 0); + _keyValues = new Object[values.length]; + for (int i = 0; i < values.length; i++) { + _keyValues[i] = values[i]; + } + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#keyValuesNoCopy() + */ + public Object[] _keyValuesNoCopy() { + return _keyValues; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#_keyValues() + */ + public Object[] keyValues() { + Object[] v = new Object[_keyValues.length]; + for (int i = 0; i < _keyValues.length; i++) { + v[i] = _keyValues[i]; + } + return v; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOKeyGlobalID#keyCount() + */ + public int keyCount() { + return _keyValues.length; + } + + /* (non-Javadoc) + * @see net.wotonomy.control.EOGlobalID#isTemporary() + */ + public boolean isTemporary() { + return false; + } + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/19 01:59:01 chochos + * Added the wotonomy headers + * + */ diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java new file mode 100644 index 0000000..ee40f23 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EditingContext.java @@ -0,0 +1,283 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.util.List; +import java.util.Map; + +// swing dependency for undo manager +//import javax.swing.undo.UndoManager; + +/** +* EditingContext provides transactional support for +* fetching, editing, and committing changes made on a +* collection of objects to a parent object store. +* This subclasses EOEditingContext to provide +* java-friendly conveniences. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EditingContext extends EOEditingContext +{ + /** + * Default constructor creates a new editing context + * that uses the default object store. If the default + * object store has not been set, an exception is thrown. + */ + public EditingContext() + { + this( defaultParentObjectStore() ); + } + + /** + * Creates a new editing context that uses the specified + * object store as its parent object store. + */ + public EditingContext( EOObjectStore anObjectStore ) + { + super( anObjectStore ); + } + + /** + * Returns a List of objects associated with the object + * with the specified id for the specified property + * relationship, or may return a placeholder array that + * will defer the fetch until needed (aka an array fault). + * All objects must be registered in the specified editing context. + * This implementation calls to its parent object store's + * implementation if the requested source object is not + * registered in this editing context. + * The specified relationship key must produce a result of + * type Collection for the source object or an exception is thrown. + */ + public List getArrayFaultWithSourceGlobalID ( + EOGlobalID aGlobalID, + String aRelationshipKey, + EOEditingContext aContext ) + { + return arrayFaultWithSourceGlobalID( + aGlobalID, aRelationshipKey, aContext ); + } + + /** + * Returns a snapshot of the specified object as it + * existed when it was last read or committed to the + * parent object store. + */ + public Map getCommittedSnapshotForObject ( + Object anObject ) + { + return committedSnapshotForObject( anObject ); + } + + /** + * Returns a snapshot of the specified object as it + * existed before the edits triggered by the current + * event loop were processed. + */ + public Map getCurrentEventSnapshotForObject ( + Object anObject ) + { + return currentEventSnapshotForObject( anObject ); + } + + /** + * Returns the delegate for this editing context, + * or null if no delegate has been set. + */ + public Object getDelegate () + { + return delegate(); + } + + /** + * Returns a List of all objects marked as deleted + * in this editing context. + */ + public List getDeletedObjects () + { + return deletedObjects(); + } + + /** + * Returns a List of registered editors of this + * editing context. + */ + public List getEditors() + { + return editors(); + } + + /** + * Returns the object for the specified id. + * If the object's data has not been fetched, + * it will be fetched when needed. + */ + public Object getFaultForGlobalID ( + EOGlobalID aGlobalID ) + { + return faultForGlobalID( aGlobalID, this ); + } + + /** + * Returns a fault representing an object of + * the specified entity type with values from + * the specified dictionary. + */ + public Object getFaultForRawRow ( + Map aDictionary, + String anEntityName ) + { + return faultForRawRow( aDictionary, anEntityName ); + } + + /** + * Returns the fetch timestamp for this editing context. + */ + public double getFetchTimestamp() + { + return fetchTimestamp(); + } + + /** + * Returns the id for the specified object, or null + * if the object is not registered in this context. + */ + public EOGlobalID getGlobalIDForObject ( + Object anObject ) + { + return globalIDForObject( anObject ); + } + + /** + * Returns a List of the objects that have been + * inserted into this editing context. + */ + public List getInsertedObjects () + { + return insertedObjects(); + } + + /** + * Returns the message handler for this editing context, + * or null if no message handler has been set. + */ + public Object getMessageHandler () + { + return messageHandler(); + } + + /** + * Returns the object registered in this editing context + * for the specified id, or null if that id is not + * registered. + */ + public Object getObjectForGlobalID ( + EOGlobalID aGlobalID ) + { + return objectForGlobalID( aGlobalID ); + } + + /** + * Returns a List of objects the meet the criteria of + * the supplied specification. + */ + public List getObjectsWithFetchSpecification ( + EOFetchSpecification aFetchSpec ) + { + return objectsWithFetchSpecification( aFetchSpec ); + } + + /** + * Returns the parent object store for this editing context. + * The result will not be null. + */ + public EOObjectStore getParentObjectStore () + { + return parentObjectStore(); + } + + /** + * Returns a List of all objects registered in this + * editing context. + */ + public List geRegisteredObjects () + { + return registeredObjects(); + } + + /** + * Returns the root object store, which is the parent + * of all parent object stores of this editing context. + */ + public EOObjectStore getRootObjectStore () + { + return rootObjectStore(); + } + + /** + * Returns a list of all objects marked as modified, + * but not inserted or deleted, in this editing context. + */ + public List getUpdatedObjects () + { + return updatedObjects(); + } + + // static methods + + public static double getDefaultFetchTimestampLag() + { + return defaultFetchTimestampLag(); + } + + /** + * Returns the default parent object store for all + * object stores created with the parameterless + * constructor. + */ + public static EOObjectStore getDefaultParentObjectStore() + { + return defaultParentObjectStore(); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2002/03/26 21:46:36 mpowers + * Contributing EditingContext as a java-friendly convenience. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java new file mode 100644 index 0000000..1aa2147 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/KeyValueCodingUtilities.java @@ -0,0 +1,740 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import net.wotonomy.foundation.NSDictionary; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.internal.Duplicator; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* KeyValueCodingUtilities implements what +* EOKeyValueCodingSupport leaves out. Importantly, +* this class implements the deep clone and deep copy +* operations that are essential to the functioning of +* nested editing contexts. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 900 $ +*/ +public class KeyValueCodingUtilities +{ + /** + * Returns a Map of the specified keys to their values, + * each of which is obtained by calling valueForKey + * on the specified object if it implements EOKeyValueCoding, + * and otherwise falling back on EOKeyValueCodingSupport. + * Null values must be represented by NSNull.nullValue(). + */ + static public NSDictionary valuesForKeys( + Object anObject, List aKeyList ) + { + return valuesForKeys( anObject, aKeyList, false ); + } + + /** + * Returns a Map of the specified keys to their values, + * each of which is obtained by calling storedValueForKey + * on the specified object if it implements EOKeyValueCoding, + * and otherwise falling back on EOKeyValueCodingSupport. + * Null values must be represented by NSNull.nullValue(). + */ + static public NSDictionary storedValuesForKeys( + Object anObject, List aKeyList ) + { + return valuesForKeys( anObject, aKeyList, true ); + } + + /** + * Called by valuesForKeys and storedValuesForKeys. + * This uses storedValueForKey if isStored is true, + * otherwise uses valueForKey. + */ + static private NSDictionary valuesForKeys( + Object anObject, List aKeyList, boolean isStored ) + { + EOKeyValueCoding coding; + if ( anObject instanceof EOKeyValueCoding ) + { + coding = (EOKeyValueCoding) anObject; + } + else + { + coding = null; + } + + String key; + Object value; + NSMutableDictionary result = new NSMutableDictionary(); + Iterator it = aKeyList.iterator(); + while ( it.hasNext() ) + { + //TODO: get rid of this try/catch - exceptions should be fatal (?) + try + { + key = it.next().toString(); + if ( coding != null ) + { + if ( isStored ) + value = coding.storedValueForKey( key ); + else + value = coding.valueForKey( key ); + } + else + { + if ( isStored ) + value = EOKeyValueCodingSupport.storedValueForKey( anObject, key ); + else + value = EOKeyValueCodingSupport.valueForKey( anObject, key ); + } + if ( value == null ) + { + value = EONullValue.nullValue(); + } + result.setObjectForKey( value, key ); + } + catch ( RuntimeException exc ) + { + System.out.println( + "KeyValueCodingUtilities.valuesForKeys: " + + isStored + " : " + exc ); + } + } + return result; + } + + /** + * Takes the keys from the specified Map as properties + * and applies the corresponding values, each of which + * might be set by calling takeValueForKey on the + * specified object if it implements EOKeyValueCoding, + * and otherwise falling back on EOKeyValueCodingSupport. + * Null values must be represented by NSNull.nullValue(). + */ + static public void takeValuesFromDictionary( + Object anObject, Map aMap ) + { + takeStoredValuesFromDictionary( anObject, aMap, false ); + } + + /** + * Takes the keys from the specified Map as properties + * and applies the corresponding values, each of which + * might be set by calling takeStoredValueForKey on the + * specified object if it implements EOKeyValueCoding, + * and otherwise falling back on EOKeyValueCodingSupport. + * Null values must be represented by NSNull.nullValue(). + */ + static public void takeStoredValuesFromDictionary( + Object anObject, Map aMap ) + { + takeStoredValuesFromDictionary( anObject, aMap, true ); + } + + /** + * Called by takeValuesFromDictionary and takeStoredValuesFromDictionary. + * This uses takeStoredValueForKey if isStored is true, + * otherwise uses takeValueForKey. + */ + static private void takeStoredValuesFromDictionary( + Object anObject, Map aMap, boolean isStored ) + { + EOKeyValueCoding coding; + if ( anObject instanceof EOKeyValueCoding ) + { + coding = (EOKeyValueCoding) anObject; + } + else + { + coding = null; + } + + String key; + Object value; + NSMutableDictionary result = new NSMutableDictionary(); + Iterator it = aMap.keySet().iterator(); + while ( it.hasNext() ) + { + //TODO: get rid of this try/catch - exceptions should be fatal (?) + try + { + key = it.next().toString(); + value = aMap.get( key ); + if ( value instanceof EONullValue ) + // can't use == nullValue() because of cloning/serialization + { + value = null; + } + if ( coding != null ) + { + if ( isStored ) + coding.takeStoredValueForKey( value, key ); + else + coding.takeValueForKey( value, key ); + } + else + { + if ( isStored ) + EOKeyValueCodingSupport.takeStoredValueForKey( + anObject, value, key ); + else + EOKeyValueCodingSupport.takeValueForKey( + anObject, value, key ); + } + } + catch ( WotonomyException exc ) + { + System.out.println( + "KeyValueCodingUtilities.takeStoredValuesFromDictionary: " + + isStored + " : " + exc ); + } + } + } + + /** + * Creates a deep clone of the specified object. + * (Object.clone() only creates a shallow clone.) + * Returns null if operation fails. + */ + static public Object clone( Object aSource ) + { + return Duplicator.deepClone( aSource ); + } + + /** + * Creates a deep clone of the specified object, + * registered in the specified source editing context, + * transposing it into the specified destination + * editing context. + * Returns null if operation fails. + */ + static public Object clone( + EOEditingContext aSourceContext, Object aSource, + EOEditingContext aDestinationContext ) + { + return clone( aSourceContext, aSource, aDestinationContext, aSource ); + } + + /** + * Called by clone and copy. + * The specified root object will not be replaced + * by an object in the destination editing context: + * this should be the same as the source object for + * cloning, but should be null for copying. + * Returns null if operation fails. + */ + static private Object clone( + EOEditingContext aSourceContext, Object aSource, + EOEditingContext aDestinationContext, + Object aRootObject ) + { + +//System.out.println(); +//System.out.println( "clone: " + aSourceContext ); +//System.out.println( " : " + aSource ); +//System.out.println( " : " + aDestinationContext ); +//System.out.println(); + + // the only known way to deep copy in + // java without native code is serialization + + return thaw( + freeze( aSource, aSourceContext, aRootObject, true ), + aDestinationContext, true ); + } + + /** + * Serializes an object to a byte array containing + * GlobalIDMarkers in place of references to other objects + * registered in the specified context. + * The specified root object will be serialized, + * even if it is registered in the specified context: + * this is typically the root object you're trying to + * serialize. + * Package access, as this method is used by editing + * context for snapshots. + */ + static public byte[] freeze( + Object anObject, EOEditingContext aContext, Object aRootObject, boolean transpose ) + { + try + { +//long t = System.currentTimeMillis(); + ByteArrayOutputStream byteOutput = + new ByteArrayOutputStream();// CloneBufferSize ); + ObjectOutputStream objectOutput; + if ( transpose ) + { + objectOutput = + new TransposingContextObjectOutputStream( + byteOutput, aContext, aRootObject ); + } + else + { + objectOutput = + new ContextObjectOutputStream( + byteOutput, aContext ); + } + + objectOutput.writeObject( anObject ); + objectOutput.flush(); + objectOutput.close(); + + return byteOutput.toByteArray(); + +// profiling +/* +byte[] result = byteOutput.toByteArray(); +long size = result.length; +long time = ( System.currentTimeMillis() - t ); +maxSize = Math.max( size, maxSize ); +minSize = Math.min( size, minSize ); +totSize += size; +maxTime = Math.max( time, maxTime ); +minTime = Math.min( time, minTime ); +totTime += time; +nTime++; +System.out.println( "freeze: size = [ " + size + " : " + minSize + " : " + ( (float)totSize / (float)nTime ) + " : " + maxSize ++ " ] time = [ " + time + " : " + minTime + " : " + ( (float)totTime / (float)nTime ) + " : " + maxTime + " ]" ); +return result; +*/ +// end profiling + + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + } + +//static long maxTime, minTime, totTime, nTime, maxSize, minSize, totSize; +//static long maxTimeThaw, minTimeThaw, totTimeThaw, nTimeThaw; + + /** + * De-serializes an object from the specified byte + * array, replacing GlobalIDMarkers with reference + * to objects registered in the specified editing + * context. + * Package access, as this method is used by editing + * context for snapshots. + */ + static public Object thaw( + byte[] aByteArray, EOEditingContext aContext, boolean transpose ) + { + return thaw( aByteArray, aContext, null, transpose ); + } + + /** + * De-serializes an object from the specified byte + * array, replacing GlobalIDMarkers with reference + * to objects registered in the specified editing + * context. + * Package access, as this method is used by editing + * context for snapshots. + */ + static public Object thaw( + byte[] aByteArray, EOEditingContext aContext, ClassLoader aLoader, boolean transpose ) + { + try + { +//long t = System.currentTimeMillis(); + ByteArrayInputStream byteInput = + new ByteArrayInputStream( aByteArray ); + ObjectInputStream objectInput; + if ( transpose ) + { + objectInput = + new TransposingContextObjectInputStream( + byteInput, aContext, aLoader ); + } + else + { + objectInput = + new ContextObjectInputStream( + byteInput, aContext, aLoader ); + } + + return objectInput.readObject(); +// profiling +/* +Object result = objectInput.readObject(); +long timeThaw = ( System.currentTimeMillis() - t ); +maxTimeThaw = Math.max( timeThaw, maxTimeThaw ); +minTimeThaw = Math.min( timeThaw, minTimeThaw ); +totTimeThaw += timeThaw; +nTimeThaw++; +System.out.println( "thaw: size = " + aByteArray.length + ", time = [ " + timeThaw + " : " + minTimeThaw + " : " + ( (float)totTimeThaw / (float)nTimeThaw ) + " : " + maxTimeThaw + " ]" ); +return result; +*/ +// end profiling + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + } + + /** + * Copies values from one object registered in the + * specified origin context to the specified destination + * object + * The values themselves are cloned, so this is a deep copy. + * Returns the destination object, or throws exception + * if operation fails. + */ + static public Object copy( Object aSource, Object aDestination ) + { + NSDictionary values = (NSDictionary) + clone( valuesForKeys( aSource, + EOClassDescription.classDescriptionForClass( + aSource.getClass() ).attributeKeys() ) ); + + takeStoredValuesFromDictionary( aDestination, values ); + return aDestination; + } + + /** + * Copies values from one object registered in the + * specified origin context to the specified destination + * object + * The values themselves are cloned, so this is a deep copy. + * Returns the destination object, or throws exception + * if operation fails. + */ + static public Object copy( + EOEditingContext aSourceContext, Object aSource, + EOEditingContext aDestinationContext, Object aDestination ) + { + // get all keys for this object + EOClassDescription classDesc = + EOClassDescription.classDescriptionForClass( aSource.getClass() ); + List keys = new LinkedList(); + keys.addAll( classDesc.attributeKeys() ); + keys.addAll( classDesc.toOneRelationshipKeys() ); + keys.addAll( classDesc.toManyRelationshipKeys() ); + + // transpose all objects registered in source context + NSDictionary values = storedValuesForKeys( aSource, keys ); + values = (NSDictionary) + clone( aSourceContext, values, aDestinationContext, null ); + + // apply to destination object + takeStoredValuesFromDictionary( aDestination, values ); + return aDestination; + } + + // inner classes + + /** + * An ObjectOutputStream that serializes objects with references + * to an editing context. The specified context will not be + * serialized but referenced, so that a ContextObjectInputStream + * can replace the reference with another editing context. + */ + static private class ContextObjectOutputStream extends ObjectOutputStream + { + private EditingContextMarker marker = new EditingContextMarker(); + protected EOEditingContext editingContext; + + /** + * Specifies the output stream to wrap, + * and the source context that should be + * referenced but not serialized. + */ + public ContextObjectOutputStream( + OutputStream anOutputStream, + EOEditingContext aContext ) + throws IOException + { + super( anOutputStream ); + editingContext = aContext; + try + { + enableReplaceObject(true); + } + catch ( Exception exc ) + { + exc.printStackTrace(); + } + } + + protected Object replaceObject(Object anObject) throws IOException + { +// if ( anObject == editingContext ) return marker; +//FIXME: this should be more strict as above + if ( anObject instanceof EOEditingContext ) return marker; + return anObject; + } + + } + + /** + * A ContextObjectOutputStream that replaces any objects registered + * in the source editing context with markers to be used in + * ContextObjectInputStream. + */ + static private class TransposingContextObjectOutputStream + extends ContextObjectOutputStream + { + protected Object rootObject; + + /** + * Specifies the output stream to wrap, + * the source context containing objects that + * should be replaced if found, + * and the object which should not be re-registered, + * which is typically the object being cloned, but + * may be null. + */ + public TransposingContextObjectOutputStream( + OutputStream anOutputStream, + EOEditingContext aContext, + Object anObject ) + throws IOException + { + super( anOutputStream, aContext ); + rootObject = anObject; + } + + protected Object replaceObject(Object anObject) throws IOException + { + if ( anObject == rootObject ) return anObject; + if ( editingContext != null ) + { + EOGlobalID id = editingContext.globalIDForObject( anObject ); + if ( id != null ) + { + Object result = new GlobalIDMarker( id ); + //System.out.println( "KeyValueCodingUtilities.replaceObject: returning: " + result ); + return result; + } + } + return super.replaceObject( anObject ); + } + + } + + /** + * A marker class so references to objects registered in editing + * contexts get transposed rather than cloned. + */ + static private class GlobalIDMarker implements Serializable + { + private EOGlobalID id; + + public GlobalIDMarker( EOGlobalID anID ) + { + id = anID; + } + + public EOGlobalID getID() + { + return id; + } + + public String toString() + { + return "[GlobalIDMarker:"+id+"]"; + } + } + + /** + * A marker class so references an object's editing context + * gets transposed rather than cloned. + */ + static private class EditingContextMarker implements Serializable + { + // just a marker class - no implementation necessary + } + + /** + * An ObjectInputStream that replaces any markers from + * ContextObjectOutputStream with objects registered + * in the destination editing context. + */ + static private class ContextObjectInputStream extends ObjectInputStream + { + protected EOEditingContext editingContext; + protected ClassLoader classLoader; + + /** + * Specifies the output stream to wrap, + * the source context containing objects that + * should be to replace any markers. + * The class loader may be null. + */ + public ContextObjectInputStream( + InputStream anInputStream, + EOEditingContext aContext, + ClassLoader aClassLoader ) + throws IOException + { + super( anInputStream ); + editingContext = aContext; + classLoader = aClassLoader; + if ( classLoader == null ) + { + classLoader = + KeyValueCodingUtilities.class.getClassLoader(); + } + try + { + enableResolveObject(true); + } + catch ( Exception exc ) + { + exc.printStackTrace(); + } + } + + protected Object resolveObject(Object anObject) throws IOException + { + if ( anObject instanceof EditingContextMarker ) + { + return editingContext; + } + return anObject; + } + + protected Class resolveClass(ObjectStreamClass v) + throws IOException, ClassNotFoundException + { + return classLoader.loadClass( v.getName() ); + } + } + + /** + * A ContextObjectInputStream that replaces any markers from + * TransposingContextObjectOutputStream with objects registered + * in the destination editing context. + */ + static private class TransposingContextObjectInputStream + extends ContextObjectInputStream + { + /** + * Specifies the output stream to wrap, + * the source context containing objects that + * should be to replace any markers. + */ + public TransposingContextObjectInputStream( + InputStream anInputStream, + EOEditingContext aContext, + ClassLoader aClassLoader ) + throws IOException + { + super( anInputStream, aContext, aClassLoader ); + } + + protected Object resolveObject(Object anObject) throws IOException + { + if ( anObject instanceof GlobalIDMarker ) + { + return editingContext.faultForGlobalID( + ((GlobalIDMarker)anObject).getID(), editingContext ); + } + return super.resolveObject( anObject ); + } + } + +} + +/* + * $Log$ + * Revision 1.3 2006/02/18 22:46:44 cgruber + * Add Surrogate map from .util into control's internal package, and fix imports. + * + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.15 2003/01/21 22:30:10 mpowers + * thaw() now allows you to pass in a class loader. + * + * Revision 1.14 2002/05/15 13:46:35 mpowers + * Exposed freeze and thaw as public. + * + * Revision 1.13 2001/08/22 19:25:13 mpowers + * Added (and commented out) profiling code for freeze. + * + * Revision 1.12 2001/05/06 18:27:10 mpowers + * More broadly catching editing contexts for now. + * + * Revision 1.11 2001/05/05 13:18:49 mpowers + * Fixed: transposing output stream was not returning the object to replace. + * + * Revision 1.10 2001/05/04 16:57:56 mpowers + * Now correctly transposing references to editing contexts when + * cloning/copying between editing contexts. + * + * Revision 1.9 2001/05/04 14:42:58 mpowers + * Now getting stored values in KeyValueCoding. + * MasterDetail now marks dirty based on whether it's an attribute + * or relation. + * Implemented editing context marker. + * + * Revision 1.8 2001/05/02 15:47:40 mpowers + * Fixed the pernicious problem with reverts: recordObject was recording + * a snapshot of the clone before the transposition-copy happened, + * so the revert object would lose all of its transposed relationships. + * + * Revision 1.7 2001/04/30 12:33:17 mpowers + * Fixed problem with use of EONullValue.nullValue(), which can't be used + * when we're serializably duplicating objects. + * + * Revision 1.6 2001/04/30 02:14:25 mpowers + * Copying should call takeStoredValueForKeys. + * + * Revision 1.5 2001/04/29 22:02:45 mpowers + * Work on id transposing between editing contexts. + * + * Revision 1.4 2001/04/29 02:29:31 mpowers + * Debugging relationship faulting. + * + * Revision 1.3 2001/04/28 16:18:44 mpowers + * Implementing relationships. + * + * Revision 1.2 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.1 2001/04/27 23:41:12 mpowers + * Contributing file for KeyValueCodingUtilities. + * + * + */ + + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java new file mode 100644 index 0000000..19d39ff --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/ObservableArray.java @@ -0,0 +1,346 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 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.control; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.NSRange; + +/** +* A package class that extends NSMutableArray but makes use +* of the fact that wotonomy's implementation extends ArrayList +* to intercept insertions and deletion and register and +* unregister objects for change notifications as appropriate. +* Since we can't be sure of ArrayList's implementation, we're +* forced to override each and every add and remove method, +* some of which probably call each other. However, +* EOObserverCenter will only register us once per object. +*/ +class ObservableArray extends NSMutableArray +{ + EOObserving observer; + + ObservableArray( EOObserving anObserver ) + { + observer = anObserver; + } + + /** + * Removes the last object from the array. + */ + public void removeLastObject () + { + remove( count() - 1 ); + } + + /** + * Removes the object at the specified index. + */ + public void removeObjectAtIndex (int index) + { + remove( index ); + } + + /** + * Adds all objects in the specified collection. + */ + public void addObjectsFromArray (Collection aCollection) + { + addAll( aCollection ); + } + + /** + * Removes all objects from the array. + */ + public void removeAllObjects () + { + clear(); + } + + /** + * Removes all objects equivalent to the specified object + * within the range of specified indices. + */ + public void removeObject (Object anObject, NSRange aRange) + { + if ( ( anObject == null ) || ( aRange == null ) ) return; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject.equals( get( i ) ) ) + { + remove( i ); + i = i - 1; + max = max - 1; + } + } + } + + /** + * Removes all instances of the specified object within the + * range of specified indices, comparing by reference. + */ + public void removeIdenticalObject (Object anObject, NSRange aRange) + { + if ( ( anObject == null ) || ( aRange == null ) ) return; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject == get( i ) ) + { + remove( i ); + i = i - 1; + max = max - 1; + } + } + } + + /** + * Removes all objects in the specified collection from the array. + */ + public void removeObjectsInArray (Collection aCollection) + { + removeAll( aCollection ); + } + + /** + * Removes all objects in the indices within the specified range + * from the array. + */ + public void removeObjectsInRange (NSRange aRange) + { + if ( aRange == null ) return; + + for ( int i = 0; i < aRange.length(); i++ ) + { + remove( aRange.location() ); + } + } + + /** + * Replaces objects in the current range with objects from + * the specified range of the specified array. If currentRange + * is larger than otherRange, the extra objects are removed. + * If otherRange is larger than currentRange, the extra objects + * are added. + */ + public void replaceObjectsInRange (NSRange currentRange, + List otherArray, NSRange otherRange) + { + if ( ( currentRange == null ) || ( otherArray == null ) || + ( otherRange == null ) ) return; + + // transform otherRange if out of bounds for array + if ( otherRange.maxRange() > otherArray.size() ) + { + // TODO: Test this logic. + int loc = Math.min( otherRange.location(), otherArray.size() - 1 ); + otherRange = new NSRange( loc, otherArray.size() - loc ); + } + + Object o; + List subList = subList( + currentRange.location(), currentRange.maxRange() ); + int otherIndex = otherRange.location(); + // TODO: Test this logic. + for ( int i = 0; i < subList.size(); i++ ) + { + if ( otherIndex < otherRange.maxRange() ) + { // set object + subList.set( i, otherArray.get( otherIndex ) ); + } + else + { // remove extra elements from currentRange + subList.remove( i ); + i--; + } + otherIndex++; + } + // TODO: Test this logic. + for ( int i = otherIndex; i < otherRange.maxRange(); i++ ) + { + add( otherArray.get( i ) ); + } + } + + /** + * Clears the current array and then populates it with the + * contents of the specified collection. + */ + public void setArray (Collection aCollection) + { + clear(); + addAll( aCollection ); + } + + /** + * Removes all objects equivalent to the specified object. + */ + public void removeObject (Object anObject) + { + remove( anObject ); + } + + /** + * Removes all occurences of the specified object, + * comparing by reference. + */ + public void removeIdenticalObject (Object anObject) + { + EOObserverCenter.removeObserver( observer, anObject ); + super.removeIdenticalObject( anObject ); + } + + /** + * Inserts the specified object into this array at the + * specified index. + */ + public void insertObjectAtIndex (Object anObject, int anIndex) + { + add( anIndex, anObject ); + } + + /** + * Replaces the object at the specified index with the + * specified object. + */ + public void replaceObjectAtIndex (int anIndex, Object anObject) + { + set( anIndex, anObject ); + } + + /** + * Adds the specified object to the end of this array. + */ + public void addObject (Object anObject) + { + add( anObject ); + } + + // interface List: mutators + + public void add(int index, Object element) + { + EOObserverCenter.addObserver( observer, element ); + super.add( index, element ); + } + + public boolean add(Object o) + { + EOObserverCenter.addObserver( observer, o ); + return super.add(o); + } + + public boolean addAll(Collection coll) + { + Iterator it = coll.iterator(); + while ( it.hasNext() ) + { + EOObserverCenter.addObserver( observer, it.next() ); + } + return super.addAll(coll); + } + + public boolean addAll(int index, Collection c) + { + Iterator it = c.iterator(); + while ( it.hasNext() ) + { + EOObserverCenter.addObserver( observer, it.next() ); + } + return super.addAll( index, c ); + } + + public void clear() + { + Iterator it = iterator(); + while ( it.hasNext() ) + { + EOObserverCenter.removeObserver( observer, it.next() ); + } + super.clear(); + } + + public Object remove(int index) + { + EOObserverCenter.removeObserver( observer, get(index) ); + return super.remove( index ); + } + + public boolean remove(Object o) + { + EOObserverCenter.removeObserver( observer, o ); + return super.remove(o); + } + + public boolean removeAll(Collection coll) + { + Iterator it = coll.iterator(); + while ( it.hasNext() ) + { + EOObserverCenter.removeObserver( observer, it.next() ); + } + return super.removeAll(coll); + } + + public boolean retainAll(Collection coll) + { + throw new UnsupportedOperationException(); + } + + public Object set(int index, Object element) + { + EOObserverCenter.removeObserver( observer, get(index) ); + EOObserverCenter.addObserver( observer, element ); + return super.set( index, element ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/10/24 21:15:35 mpowers + * New implementations of NSArray and subclasses. + * + * Revision 1.1 2001/02/20 16:38:55 mpowers + * MasterDetailAssociations now observe their controlled display group's + * objects for changes to that the parent object will be marked as updated. + * Before, only inserts and deletes to an object's items are registered. + * Also, moved ObservableArray to package access. + * + * Revision 1.1 2001/01/24 14:37:24 mpowers + * Contributing a delegate useful for debugging. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java new file mode 100644 index 0000000..8b88615 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/OrderedDataSource.java @@ -0,0 +1,57 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +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.control; + +/** +* A simple extension of EODataSource that +* allows for indexed insertion. The wotonomy +* implementation of EODisplayGroup supports +* this and will use it if possible. This is +* useful for classes like the PropertyDataSource, +* where the ordering of items in an indexed +* property may be important. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public abstract class OrderedDataSource extends EODataSource +{ + /** + * Inserts the specified object into this data source, + * at the specified index. + */ + public abstract void insertObjectAtIndex ( + Object anObject, int anIndex ); +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2001/01/24 14:10:53 mpowers + * Contributing OrderedDataSource, and PropertyDataSource extends it. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java new file mode 100644 index 0000000..76f7219 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/PropertyDataSource.java @@ -0,0 +1,549 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +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.control; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.TreeSet; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* A data source that reads and writes to an indexed +* property of a java object. This class is used by +* MasterDetailAssociation to retreive objects from +* the master display group. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class PropertyDataSource extends OrderedDataSource +{ + protected Object source; + protected String key; + protected Class lastKnownType; // for best-guessing + protected EOClassDescription classDesc; + protected EOEditingContext context; + + /** + * Creates a new PropertyDataSource with no editing context + * and will try to guess the appropriate class description + * when trying to create objects. + */ + public PropertyDataSource() + { + this( null, (EOClassDescription) null ); + } + + /** + * Creates a new PropertyDataSource that uses the specified + * editing context, but will try to guess the appropriate + * class description when trying to create objects. + */ + public PropertyDataSource( EOEditingContext aContext ) + { + this( aContext, (EOClassDescription) null ); + } + + /** + * Creates a new PropertyDataSource that uses the specified + * editing context and vends objects of the specified class. + */ + public PropertyDataSource( + EOEditingContext aContext, Class aClass ) + { + this( aContext, EOClassDescription.classDescriptionForClass( aClass ) ); + } + + /** + * Creates a new PropertyDataSource that uses the specified + * editing context and vends objects of the specified + * class description. + */ + public PropertyDataSource( + EOEditingContext aContext, EOClassDescription aClassDesc ) + { + source = null; + key = null; + lastKnownType = null; + classDesc = aClassDesc; + context = aContext; + } + + /** + * Provides the master object for detail display groups. + */ + public Object source() + { + return source; + } + + /** + * Allows a detail display group to set the master object. + */ + public void setSource( Object anObject ) + { + source = anObject; + } + + /** + * Provides the detail key for detail display groups. + */ + public String key() + { + return key; + } + + /** + * Allows a detail display group to set the detail key. + */ + public void setKey( String aKey ) + { + key = aKey; + } + + /** + * Inserts the specified object into this data source. + * Calls insertObjectAtIndex and appends to the end + * of the list. + */ + public void insertObject ( Object anObject ) + { + insertObjectAtIndex( anObject, -1 ); // trick to force to end + } + + /** + * Inserts the specified object into this data source, + * at the specified index. + */ + public void insertObjectAtIndex ( + Object anObject, int anIndex ) + { + if ( source == null ) return; + List list = readAsList(); + if ( anIndex == -1 ) anIndex = list.size(); // force to end + if ( anIndex > list.size() ) anIndex = list.size(); // force to end + list.add( anIndex, anObject ); + writeAsList( list ); + } + + /** + * Deletes the specified object from this data source. + */ + public void deleteObject ( Object anObject ) + { + if ( source == null ) return; + List list = readAsList(); + list.remove( anObject ); + writeAsList( list ); + } + + public EOEditingContext editingContext () + { + return context; + } + + /** + * Returns a List containing the objects in this + * data source. + */ + public NSArray fetchObjects () + { + if ( source == null ) return NSArray.EmptyArray; + return readAsList(); + } + + /** + * Returns a new instance of this class. + */ + public EODataSource + dataSourceQualifiedByKey ( String aKey ) + { + // determine the target class desc if possible + EOClassDescription keyClassDesc = null; + if ( classDesc != null ) + { + keyClassDesc = classDesc.classDescriptionForDestinationKey( aKey ); + } + return new PropertyDataSource( editingContext(), keyClassDesc ); + } + + /** + * Restricts this data source to vend those + * objects that are associated with the specified + * key on the specified object. + */ + public void + qualifyWithRelationshipKey ( + String aKey, Object anObject ) + { + source = anObject; + key = aKey; + } + + /** + * Returns the class description passed to the + * constructor, if any. If no class description and + * if the bound property is an indexed property, + * the type of the array is returned, otherwise + * this method returns null. This method is called + * by createObject(). + */ + public EOClassDescription + classDescriptionForObjects () + { + // just return the class description if we have one + if ( classDesc != null ) return classDesc; + + // otherwise, try to do some guesswork + EOClassDescription result = null; + + // lastKnownType is not updated here + Class type = lastKnownType; + + // if no last known type + if ( type == null ) + { + // if source and key were specified + if ( ( source != null ) && ( key != null ) ) + { + // try to get an array type + Method m = Introspector.getPropertyReadMethod( + source.getClass(), key, new Class[0] ); + if ( m != null ) + { + Class returnType = m.getReturnType(); + if ( returnType.isArray() ) + { + type = returnType.getComponentType(); + } + } + else + { + throw new WotonomyException( "Key does not exist for object: " + key + " : " + source ); + } + } + + // does not update lastKnownType because + // we prefer to get that info from a fetch. + } + + // if type has been determined + if ( type != null ) + { + result = + EOClassDescription.classDescriptionForClass( type ); + } + + return result; + } + + /** + * Calls getValue() and returns the result as a List. + * Sets lastKnownType to the retrieved type. + */ + protected NSMutableArray readAsList() + { + Object value = getValue(); + if ( value == null ) + { + return new NSMutableArray(); + } + + Object o; + NSMutableArray result = new NSMutableArray(); + boolean hasReadType = false; + lastKnownType = null; + + // if instance of array, convert to list + if ( value.getClass().isArray() ) + { + int count = Array.getLength( value ); + for ( int i = 0; i < count; i++ ) + { + o = Array.get( value, i ); + if ( o != null ) + { + // we've already found a type + if ( hasReadType ) + { + // check that this matches the last known type + if ( o.getClass() != lastKnownType ) + { + // not all of the same type: set to null + lastKnownType = null; + } + } + else // this is the first type we've found + { + // remember it + hasReadType = true; + lastKnownType = o.getClass(); + } + } + result.add( o ); + } + } + else + if ( value instanceof Collection ) + { + // convert to list so we handle sets, etc. + Iterator i = ((Collection)value).iterator(); + while ( i.hasNext() ) + { + o = i.next(); + if ( o != null ) + { + // we've already found a type + if ( hasReadType ) + { + // check that this matches the last known type + if ( o.getClass() != lastKnownType ) + { + // not all of the same type: set to null + lastKnownType = null; + } + } + else // this is the first type we've found + { + // remember it + hasReadType = true; + lastKnownType = o.getClass(); + } + } + result.add( o ); + } + } + else + { + lastKnownType = null; + throw new WotonomyException( "PropertyDataSource: " + + "bound property was not an indexed property: " + key ); + } + + return result; + } + + /** + * Converts the specified List to lastKnownType + * and calls setValue(). + */ + protected void writeAsList( List anObjectList ) + { + if ( source == null ) + { + throw new WotonomyException( "PropertyDataSource: " + + "no source object: " + key ); + } + + Class c = source.getClass(); + Method m = Introspector.getPropertyReadMethod( c, key, new Class[0] ); + if ( m == null ) + { + throw new WotonomyException( "Could not read property for object: " + + key + " : " + source + " (" + c + ")" ); + } + + Class returnType = m.getReturnType(); + + int count = anObjectList.size(); + Object result = null; + + if ( returnType.isArray() ) + { + Class type = returnType.getComponentType(); + result = Array.newInstance( type, count ); + for ( int i = 0; i < count; i++ ) + { + Array.set( result, i, anObjectList.get( i ) ); + } + } + else + { + Collection collection = null; + + if ( ! returnType.isInterface() ) + { + try + { + collection = (Collection) returnType.newInstance(); + } + catch ( Exception exc ) + { + // no default constructor, leave null + } + } + + // try to find an acceptable collections type + if ( collection == null ) + { + if ( returnType.isAssignableFrom( NSMutableArray.class ) ) + { + collection = new NSMutableArray(); + } + else + if ( returnType.isAssignableFrom( LinkedList.class ) ) + { + collection = new LinkedList(); + } + else + if ( returnType.isAssignableFrom( ArrayList.class ) ) + { + collection = new ArrayList(); + } + else + if ( returnType.isAssignableFrom( HashSet.class ) ) + { + collection = new HashSet(); + } + else + if ( returnType.isAssignableFrom( TreeSet.class ) ) + { + collection = new TreeSet(); + } + } + + if ( collection == null ) + { + throw new WotonomyException( "Could not create a collection of type: " + returnType ); + } + + collection.addAll( anObjectList ); + result = collection; + } + + setValue( result ); + } + + /** + * Returns the value of the indexed property + * specified by qualifyWithRelationshipKey. + */ + protected Object getValue() + { + if ( source instanceof EOKeyValueCoding ) + { + return ((EOKeyValueCoding)source).valueForKey( key ); + } + return EOKeyValueCodingSupport.valueForKey( source, key ); + } + + /** + * Sets the value of the indexed property + * specified by qualifyWithRelationshipKey. + * The argument is assumed to be of appropriate + * type for the property. EOObserverCenter is + * notified that the object will change. + */ + protected void setValue( Object aValue ) + { + EOClassDescription sourceDesc = + EOClassDescription.classDescriptionForClass( source.getClass() ); + + + // if we're not editing a relationship (?) +// if ( ! sourceDesc.toManyRelationshipKeys().containsObject( key ) ) + { + // mark the parent as changed + EOObserverCenter.notifyObserversObjectWillChange( source ); + } + + + if ( source instanceof EOKeyValueCoding ) + { + ((EOKeyValueCoding)source).takeValueForKey( aValue, key ); + } + else + { + EOKeyValueCodingSupport.takeValueForKey( source, aValue, key ); + } + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.14 2003/01/18 23:30:42 mpowers + * WODisplayGroup now compiles. + * + * Revision 1.13 2002/10/24 21:15:36 mpowers + * New implementations of NSArray and subclasses. + * + * Revision 1.12 2002/10/24 18:18:12 mpowers + * NSArray's are now considered read-only, so we can return our internal + * representation to reduce unnecessary object allocation. + * + * Revision 1.11 2002/04/15 21:55:33 mpowers + * Catching a condition where the get may not return the value passed to set. + * + * Revision 1.10 2002/03/08 23:20:37 mpowers + * insertObject now calls insertObjectAtIndex. + * + * Revision 1.9 2001/06/05 19:10:41 mpowers + * Better handling of null properties. + * + * Revision 1.8 2001/05/21 14:03:35 mpowers + * Added a convenience constructor for java classes. + * + * Revision 1.7 2001/04/30 13:15:24 mpowers + * Child contexts re-initializing objects invalidated in parent now + * propery transpose relationships. + * + * Revision 1.6 2001/04/29 02:29:31 mpowers + * Debugging relationship faulting. + * + * Revision 1.5 2001/04/28 22:17:51 mpowers + * Revised PropertyDataSource to be EOClassDescription-aware. + * + * Revision 1.4 2001/04/27 23:37:20 mpowers + * Now using EOClassDescription in the EODataSource class, as we should. + * + * Revision 1.3 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.2 2001/01/24 14:10:53 mpowers + * Contributing OrderedDataSource, and PropertyDataSource extends it. + * + * Revision 1.1.1.1 2000/12/21 15:46:50 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java new file mode 100644 index 0000000..e12fda0 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/internal/Surrogate.java @@ -0,0 +1,259 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +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.control.internal; + +import net.wotonomy.control.EOObserverCenter; +import net.wotonomy.foundation.NSMutableDictionary; +import net.wotonomy.foundation.internal.Introspector; + +/** +* A Surrogate is a special object that can be used in a display +* group when you wish to emulate other objects or modify their +* behaviors. Because it is a Map, it makes use of Introspector's +* ability to treat keys in a map as if they were properties to +* implement the following features. +* <ul> +* <li>By default, Surrogate works like a Map, and reading and +* writing properties to a Surrogate gets and puts values in the +* Map.</li> +* <li>If one or more delegate objects are specified, property keys +* that do not exist in the map are read from and written to the +* delegate object.</li> +* <li>If a default value is specified, that value will be returned +* for all property reads that do not exist in the map or in the +* delegate object. (Subsequent writes to those properties will +* create a key in the map and then subsequent reads will read not +* read the default object.)</li> +* <li>Subclasses can override the get(Object) method to further +* customize the behavior of a Surrogate. +* </ul> +* +* @author michael@mpowers.net +* @date $Date: 2006-02-18 17:46:44 -0500 (Sat, 18 Feb 2006) $ +* @revision $Revision: 900 $ +*/ +public class Surrogate extends NSMutableDictionary +{ + protected Object[] delegates; + protected Object defaultValue; + + /** + * Default constructor with no delegate object and no default value. + */ + public Surrogate() + { + delegates = null; + defaultValue = null; + } + + /** + * Constructor specifying a delegate object. + */ + public Surrogate( Object[] aDelegateArray ) + { + setDelegates( aDelegateArray ); + } + + /** + * Constructor specifying a default value. + */ + public Surrogate( Object aDefault ) + { + setDefaultValue( aDefault ); + } + + /** + * Constructor specifying a delegate object and a default value. + */ + public Surrogate( Object[] aDelegateArray, Object aDefault ) + { + setDelegates( aDelegateArray ); + setDefaultValue( aDefault ); + } + + /** + * Returns the first delegate object, or null if no delegates exist. + */ + public Object getDelegate() + { + if ( delegates == null ) return null; + if ( delegates.length == 0 ) return null; + return delegates[0]; + } + + /** + * Sets the delegate object list to contain only the + * specified object. + */ + public void setDelegate( Object aDelegate ) + { + setDelegates( new Object[] { aDelegate } ); + } + + /** + * Returns the list of delegates in the order in which + * they are consulted. + */ + public Object[] getDelegates() + { + if ( delegates == null ) delegates = new Object[0]; + return delegates; + } + + /** + * Sets the list of delegates in the order in which they + * will be consulted. + */ + public void setDelegates( Object[] aDelegateArray ) + { + delegates = aDelegateArray; + } + + /** + * Returns the current default value, or null if no default exists. + */ + public Object getDefaultValue() + { + return defaultValue; + } + + /** + * Sets the default value. + */ + public void setDefaultValue( Object aDefault ) + { + defaultValue = aDefault; + } + + /** + * Called by get to retrieve a value from the internal map. + * This implementation simply calls super.get(). + */ + public Object directGet( Object aKey ) + { + return super.get( aKey ); + } + + /** + * Called by put to retrieve a value from the internal map. + * This implementation simply calls super.put(). + */ + public Object directPut( Object aKey, Object aValue ) + { + return super.put( aKey, aValue ); + } + + /** + * Overridden to consult each delegate before + * checking the internal list of keys. No matching + * key is found, returns the default object, or + * null if no default object exists. + */ + public Object get( Object aKey ) + { + // check all delegates in order + int i, j; + Object[] list = getDelegates(); + String[] properties; + for ( i = 0; i < list.length; i++ ) + { + // for each delegate + properties = + Introspector.getReadPropertiesForObject( list[i] ); + for ( j = 0; j < properties.length; j++ ) + { + // if delegate has property + if ( properties[j].equals( aKey ) ) + { + // use this delegate + return Introspector.get( list[i], aKey.toString() ); + } + } + } + + // return from internal map + Object result = directGet( aKey ); + if ( result == null ) + { + // if not in map, return default object + result = getDefaultValue(); + } + return result; + } + + /** + * Overridden to attempt to write each delegate, writing to + * only the first successful delegate, before storing the + * value in the internal map. + */ + public Object put( Object aKey, Object aValue ) + { + // check all delegates in order + int i, j; + Object[] list = getDelegates(); + String[] properties; + for ( i = 0; i < list.length; i++ ) + { + // for each delegate + properties = + Introspector.getWritePropertiesForObject( list[i] ); + for ( j = 0; j < properties.length; j++ ) + { + // if delegate has property + if ( properties[j].equals( aKey ) ) + { + // use this delegate + EOObserverCenter.notifyObserversObjectWillChange( list[i] ); + return Introspector.set( list[i], aKey.toString(), aValue ); + } + } + } + + // set on internal map + EOObserverCenter.notifyObserversObjectWillChange( this ); + return directPut( aKey, aValue ); + } + + /** + * Overridden to compare by reference. + */ + public boolean equals( Object anObject ) + { + return ( this == anObject ); + } + +} + +/* + * $Log$ + * Revision 1.1 2006/02/18 22:46:44 cgruber + * Add Surrogate map from .util into control's internal package, and fix imports. + * + * Revision 1.1 2006/02/16 13:22:22 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:52:21 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:48 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html new file mode 100644 index 0000000..c32d3c7 --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/package.html @@ -0,0 +1,6 @@ +<body> +<p> +Support classes needed by the ui and web packages. +This package roughly corresponds to the eocontrol package. +</p> +</body> |
