From 40a9d99496e098562f090fb7ffce9e749011b131 Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Mon, 20 May 2024 17:58:16 -0400 Subject: Formatting pass --- .../java/net/wotonomy/datastore/DataIndex.java | 150 +- .../main/java/net/wotonomy/datastore/DataKey.java | 188 +- .../main/java/net/wotonomy/datastore/DataSoup.java | 397 +- .../java/net/wotonomy/datastore/DataStore.java | 103 +- .../main/java/net/wotonomy/datastore/DataView.java | 106 +- .../net/wotonomy/datastore/DefaultComparator.java | 195 +- .../net/wotonomy/datastore/DefaultDataIndex.java | 348 +- .../net/wotonomy/datastore/DefaultDataView.java | 846 ++- .../java/net/wotonomy/datastore/DuplicateList.java | 27 +- .../main/java/net/wotonomy/datastore/FileSoup.java | 711 +- .../net/wotonomy/datastore/SerializedFileSoup.java | 129 +- .../wotonomy/datastore/UniquelyIdentifiable.java | 20 +- .../java/net/wotonomy/datastore/XMLFileSoup.java | 134 +- .../main/java/net/wotonomy/foundation/NSArray.java | 1122 ++-- .../java/net/wotonomy/foundation/NSBundle.java | 101 +- .../main/java/net/wotonomy/foundation/NSCoder.java | 94 +- .../java/net/wotonomy/foundation/NSCoding.java | 78 +- .../java/net/wotonomy/foundation/NSComparator.java | 101 +- .../main/java/net/wotonomy/foundation/NSData.java | 336 +- .../main/java/net/wotonomy/foundation/NSDate.java | 319 +- .../java/net/wotonomy/foundation/NSDictionary.java | 509 +- .../java/net/wotonomy/foundation/NSDisposable.java | 14 +- .../wotonomy/foundation/NSForwardException.java | 172 +- .../net/wotonomy/foundation/NSKeyValueCoding.java | 672 +- .../foundation/NSKeyValueCodingAdditions.java | 321 +- .../foundation/NSKeyValueCodingSupport.java | 328 +- .../main/java/net/wotonomy/foundation/NSLock.java | 123 +- .../java/net/wotonomy/foundation/NSLocking.java | 41 +- .../main/java/net/wotonomy/foundation/NSLog.java | 845 ++- .../net/wotonomy/foundation/NSMultiReaderLock.java | 228 +- .../net/wotonomy/foundation/NSMutableArray.java | 601 +- .../net/wotonomy/foundation/NSMutableData.java | 208 +- .../wotonomy/foundation/NSMutableDictionary.java | 229 +- .../net/wotonomy/foundation/NSMutableRange.java | 148 +- .../net/wotonomy/foundation/NSNotification.java | 233 +- .../wotonomy/foundation/NSNotificationCenter.java | 1086 ++-- .../wotonomy/foundation/NSNotificationQueue.java | 553 +- .../main/java/net/wotonomy/foundation/NSNull.java | 128 +- .../net/wotonomy/foundation/NSNumberFormatter.java | 41 +- .../foundation/NSPropertyListSerialization.java | 514 +- .../main/java/net/wotonomy/foundation/NSRange.java | 581 +- .../net/wotonomy/foundation/NSRecursiveLock.java | 212 +- .../java/net/wotonomy/foundation/NSRunLoop.java | 864 ++- .../java/net/wotonomy/foundation/NSSelector.java | 538 +- .../main/java/net/wotonomy/foundation/NSSet.java | 319 +- .../java/net/wotonomy/foundation/NSTimeZone.java | 272 +- .../java/net/wotonomy/foundation/NSTimestamp.java | 299 +- .../wotonomy/foundation/NSTimestampFormatter.java | 51 +- .../wotonomy/foundation/internal/Duplicator.java | 439 +- .../wotonomy/foundation/internal/Introspector.java | 1544 ++--- .../foundation/internal/IntrospectorException.java | 17 +- .../internal/MissingPropertyException.java | 17 +- .../foundation/internal/NetworkClassLoader.java | 574 +- .../internal/NullPrimitiveException.java | 17 +- .../foundation/internal/PropertyComparator.java | 91 +- .../foundation/internal/PropertyListParser.java | 926 ++- .../net/wotonomy/foundation/internal/QueueMap.java | 996 ++- .../foundation/internal/URLResourceReader.java | 267 +- .../foundation/internal/ValueConverter.java | 1215 ++-- .../foundation/internal/WotonomyException.java | 135 +- .../net/wotonomy/foundation/xml/XMLDecoder.java | 61 +- .../net/wotonomy/foundation/xml/XMLEncoder.java | 50 +- .../src/test/java/AllTests.java | 4 +- .../src/test/java/TestBundle.java | 10 +- .../java/net/wotonomy/foundation/AllTests.java | 4 +- .../java/net/wotonomy/foundation/NSArrayTest.java | 569 +- .../java/net/wotonomy/foundation/NSBundleTest.java | 219 +- .../java/net/wotonomy/jdbcadaptor/JDBCAdaptor.java | 56 +- .../wotonomy/jdbcadaptor/JDBCAdaptorException.java | 28 +- .../java/net/wotonomy/jdbcadaptor/JDBCChannel.java | 230 +- .../java/net/wotonomy/jdbcadaptor/JDBCContext.java | 56 +- .../net/wotonomy/jdbcadaptor/JDBCExpression.java | 38 +- .../jdbcadaptor/JDBCExpressionFactory.java | 29 +- .../wotonomy/access/EOAccessArrayFaultHandler.java | 40 +- .../net/wotonomy/access/EOAccessFaultHandler.java | 39 +- .../access/EOAccessGenericFaultHandler.java | 33 +- .../java/net/wotonomy/access/EOAccessLock.java | 19 +- .../main/java/net/wotonomy/access/EOAdaptor.java | 81 +- .../java/net/wotonomy/access/EOAdaptorChannel.java | 110 +- .../java/net/wotonomy/access/EOAdaptorContext.java | 39 +- .../net/wotonomy/access/EOAdaptorOperation.java | 37 +- .../main/java/net/wotonomy/access/EOAttribute.java | 114 +- .../main/java/net/wotonomy/access/EODatabase.java | 60 +- .../net/wotonomy/access/EODatabaseChannel.java | 37 +- .../net/wotonomy/access/EODatabaseContext.java | 338 +- .../net/wotonomy/access/EODatabaseOperation.java | 19 +- .../main/java/net/wotonomy/access/EOEntity.java | 202 +- .../wotonomy/access/EOEntityClassDescription.java | 55 +- .../wotonomy/access/EOGeneralAdaptorException.java | 25 +- .../src/main/java/net/wotonomy/access/EOJoin.java | 22 +- .../src/main/java/net/wotonomy/access/EOModel.java | 131 +- .../java/net/wotonomy/access/EOModelGroup.java | 79 +- .../main/java/net/wotonomy/access/EOProperty.java | 21 +- .../wotonomy/access/EOPropertyListEncoding.java | 34 +- .../wotonomy/access/EOQualifierSQLGeneration.java | 77 +- .../java/net/wotonomy/access/EORelationship.java | 113 +- .../java/net/wotonomy/access/EOSQLExpression.java | 170 +- .../wotonomy/access/EOSQLExpressionFactory.java | 44 +- .../net/wotonomy/access/EOStoredProcedure.java | 61 +- .../net/wotonomy/control/AbstractObjectStore.java | 1350 ++-- .../main/java/net/wotonomy/control/ArrayFault.java | 322 +- .../java/net/wotonomy/control/ChildDataSource.java | 282 +- .../java/net/wotonomy/control/EOAndQualifier.java | 180 +- .../net/wotonomy/control/EOClassDescription.java | 990 ++- .../wotonomy/control/EOCooperatingObjectStore.java | 63 +- .../java/net/wotonomy/control/EOCustomObject.java | 1158 ++-- .../java/net/wotonomy/control/EODataSource.java | 230 +- .../net/wotonomy/control/EODatabaseDataSource.java | 551 +- .../net/wotonomy/control/EODeferredFaulting.java | 36 +- .../net/wotonomy/control/EODelayedObserver.java | 209 +- .../wotonomy/control/EODelayedObserverQueue.java | 446 +- .../net/wotonomy/control/EOEditingContext.java | 5547 +++++++--------- .../net/wotonomy/control/EOEnterpriseObject.java | 352 +- .../java/net/wotonomy/control/EOFaultHandler.java | 40 +- .../main/java/net/wotonomy/control/EOFaulting.java | 97 +- .../net/wotonomy/control/EOFetchSpecification.java | 847 ++- .../java/net/wotonomy/control/EOGenericRecord.java | 214 +- .../main/java/net/wotonomy/control/EOGlobalID.java | 88 +- .../wotonomy/control/EOIntegralKeyGlobalID.java | 38 +- .../wotonomy/control/EOKeyComparisonQualifier.java | 59 +- .../java/net/wotonomy/control/EOKeyGlobalID.java | 160 +- .../net/wotonomy/control/EOKeyValueArchiver.java | 32 +- .../net/wotonomy/control/EOKeyValueArchiving.java | 21 +- .../net/wotonomy/control/EOKeyValueCoding.java | 166 +- .../control/EOKeyValueCodingAdditions.java | 245 +- .../wotonomy/control/EOKeyValueCodingSupport.java | 338 +- .../net/wotonomy/control/EOKeyValueQualifier.java | 224 +- .../net/wotonomy/control/EOKeyValueUnarchiver.java | 46 +- .../java/net/wotonomy/control/EONotQualifier.java | 124 +- .../java/net/wotonomy/control/EONullValue.java | 143 +- .../java/net/wotonomy/control/EOObjectStore.java | 465 +- .../wotonomy/control/EOObjectStoreCoordinator.java | 238 +- .../net/wotonomy/control/EOObserverCenter.java | 727 +-- .../java/net/wotonomy/control/EOObserverProxy.java | 125 +- .../java/net/wotonomy/control/EOObserving.java | 36 +- .../java/net/wotonomy/control/EOOrQualifier.java | 183 +- .../java/net/wotonomy/control/EOQualifier.java | 1089 ++-- .../wotonomy/control/EOQualifierEvaluation.java | 24 +- .../control/EORelationshipManipulation.java | 80 +- .../java/net/wotonomy/control/EOSortOrdering.java | 562 +- .../net/wotonomy/control/EOTemporaryGlobalID.java | 350 +- .../java/net/wotonomy/control/EOValidation.java | 82 +- .../net/wotonomy/control/EOVectorKeyGlobalID.java | 34 +- .../java/net/wotonomy/control/EditingContext.java | 441 +- .../wotonomy/control/KeyValueCodingUtilities.java | 1140 ++-- .../java/net/wotonomy/control/ObservableArray.java | 485 +- .../net/wotonomy/control/OrderedDataSource.java | 48 +- .../net/wotonomy/control/PropertyDataSource.java | 863 ++- .../net/wotonomy/control/internal/Surrogate.java | 279 +- .../java/net/wotonomy/test/BindingController.java | 30 +- .../main/java/net/wotonomy/test/BindingPanel.java | 239 +- .../src/main/java/net/wotonomy/test/DataKeyID.java | 126 +- .../java/net/wotonomy/test/DataObjectStore.java | 774 +-- .../java/net/wotonomy/test/EditController.java | 248 +- .../src/main/java/net/wotonomy/test/EditPanel.java | 59 +- .../net/wotonomy/test/InspectorController.java | 142 +- .../src/main/java/net/wotonomy/test/Test.java | 150 +- .../java/net/wotonomy/test/TestController.java | 490 +- .../java/net/wotonomy/test/TestDataSource.java | 158 +- .../src/main/java/net/wotonomy/test/TestMap.java | 238 +- .../main/java/net/wotonomy/test/TestObject.java | 643 +- .../net/wotonomy/test/TestObjectClassDesc.java | 38 +- .../java/net/wotonomy/test/TestObjectStore.java | 409 +- .../src/main/java/net/wotonomy/test/TestPanel.java | 119 +- .../java/net/wotonomy/test/TreeController.java | 372 +- .../net/wotonomy/test/TreeInspectorController.java | 214 +- .../src/main/java/net/wotonomy/test/TreePanel.java | 42 +- .../net/wotonomy/ui/swing/ActionAssociation.java | 463 +- .../wotonomy/ui/swing/AdjustableAssociation.java | 443 +- .../net/wotonomy/ui/swing/ButtonAssociation.java | 684 +- .../net/wotonomy/ui/swing/ComboBoxAssociation.java | 1137 ++-- .../net/wotonomy/ui/swing/DateAssociation.java | 856 +-- .../ui/swing/DisplayGroupActionAssociation.java | 148 +- .../wotonomy/ui/swing/DisplayGroupInspector.java | 135 +- .../net/wotonomy/ui/swing/DisplayGroupNode.java | 2452 ++++--- .../net/wotonomy/ui/swing/ListAssociation.java | 491 +- .../wotonomy/ui/swing/MutableDisplayGroupNode.java | 296 +- .../wotonomy/ui/swing/NotificationInspector.java | 497 +- .../wotonomy/ui/swing/RadioPanelAssociation.java | 593 +- .../net/wotonomy/ui/swing/ReferenceInspector.java | 417 +- .../net/wotonomy/ui/swing/SliderAssociation.java | 587 +- .../net/wotonomy/ui/swing/TableAssociation.java | 1370 ++-- .../wotonomy/ui/swing/TableColumnAssociation.java | 1085 ++-- .../net/wotonomy/ui/swing/TextAssociation.java | 1980 +++--- .../wotonomy/ui/swing/TimedTextAssociation.java | 1430 ++-- .../net/wotonomy/ui/swing/TreeAssociation.java | 876 ++- .../wotonomy/ui/swing/TreeColumnAssociation.java | 488 +- .../wotonomy/ui/swing/TreeModelAssociation.java | 2930 ++++----- .../ui/swing/components/AbsoluteLayout.java | 61 +- .../ui/swing/components/AlphaTextField.java | 593 +- .../components/AlternatingRowCellRenderer.java | 157 +- .../ui/swing/components/BetterFlowLayout.java | 952 +-- .../ui/swing/components/BetterRootLayout.java | 268 +- .../ui/swing/components/BetterTableUI.java | 176 +- .../wotonomy/ui/swing/components/ButtonPanel.java | 1066 ++- .../ui/swing/components/CheckButtonPanel.java | 455 +- .../ui/swing/components/ColorCellEditor.java | 88 +- .../ui/swing/components/ColorCellRenderer.java | 79 +- .../ui/swing/components/ComboBoxCellRenderer.java | 44 +- .../ui/swing/components/DateTextField.java | 1098 ++-- .../ui/swing/components/FormattedCellRenderer.java | 460 +- .../ui/swing/components/IconCellRenderer.java | 1272 ++-- .../wotonomy/ui/swing/components/ImagePanel.java | 91 +- .../wotonomy/ui/swing/components/InfoPanel.java | 2908 ++++----- .../ui/swing/components/KeyDelayTimer.java | 301 +- .../ui/swing/components/KeyableCellEditor.java | 566 +- .../ui/swing/components/LineWrappingRenderer.java | 225 +- .../ui/swing/components/MultiLineLabel.java | 163 +- .../ui/swing/components/NumericTextField.java | 754 +-- .../ui/swing/components/PropertyEditorTable.java | 927 ++- .../swing/components/PropertyEditorTableModel.java | 538 +- .../ui/swing/components/RadioButtonPanel.java | 278 +- .../ui/swing/components/SmartPasswordField.java | 456 +- .../ui/swing/components/SmartTextField.java | 398 +- .../ui/swing/components/StatusButtonPanel.java | 435 +- .../ui/swing/components/TintedImageFilter.java | 122 +- .../wotonomy/ui/swing/components/TreeChooser.java | 1134 ++-- .../ui/swing/components/TreeTableCellRenderer.java | 305 +- .../net/wotonomy/ui/swing/util/ClassGrabber.java | 108 +- .../ui/swing/util/ComponentHighlighter.java | 212 +- .../net/wotonomy/ui/swing/util/GIFEncoder.java | 890 ++- .../wotonomy/ui/swing/util/ObjectInspector.java | 300 +- .../wotonomy/ui/swing/util/PositionComparator.java | 84 +- .../ui/swing/util/StackTraceInspector.java | 686 +- .../ui/swing/util/TextInputRangeChecker.java | 609 +- .../net/wotonomy/ui/swing/util/WindowGrabber.java | 229 +- .../wotonomy/ui/swing/util/WindowUtilities.java | 890 ++- .../java/net/wotonomy/ui/DebuggingDelegate.java | 488 +- .../main/java/net/wotonomy/ui/DelegateAdapter.java | 380 +- .../main/java/net/wotonomy/ui/DisplayGroup.java | 490 +- .../main/java/net/wotonomy/ui/EOAssociation.java | 880 ++- .../main/java/net/wotonomy/ui/EODisplayGroup.java | 4040 ++++++------ .../java/net/wotonomy/ui/GenericAssociation.java | 586 +- .../net/wotonomy/ui/MasterDetailAssociation.java | 605 +- .../net/wotonomy/ui/MirrorDetailAssociation.java | 134 +- .../main/java/net/wotonomy/ui/ObservableArray.java | 486 +- .../java/net/wotonomy/web/ObservableArray.java | 488 +- .../src/main/java/net/wotonomy/web/URI.java | 6836 ++++++++++---------- .../java/net/wotonomy/web/WOActionResults.java | 38 +- .../main/java/net/wotonomy/web/WOActionURL.java | 39 +- .../main/java/net/wotonomy/web/WOActiveImage.java | 115 +- .../main/java/net/wotonomy/web/WOApplication.java | 2021 +++--- .../main/java/net/wotonomy/web/WOAssociation.java | 236 +- .../src/main/java/net/wotonomy/web/WOBody.java | 150 +- .../src/main/java/net/wotonomy/web/WOCheckBox.java | 124 +- .../main/java/net/wotonomy/web/WOComponent.java | 2118 +++--- .../java/net/wotonomy/web/WOComponentContent.java | 27 +- .../wotonomy/web/WOComponentRequestHandler.java | 340 +- .../main/java/net/wotonomy/web/WOConditional.java | 131 +- .../src/main/java/net/wotonomy/web/WOContext.java | 929 ++- .../src/main/java/net/wotonomy/web/WOCookie.java | 314 +- .../main/java/net/wotonomy/web/WODirectAction.java | 440 +- .../wotonomy/web/WODirectActionRequestHandler.java | 322 +- .../main/java/net/wotonomy/web/WODisplayGroup.java | 4219 ++++++------ .../java/net/wotonomy/web/WODynamicElement.java | 339 +- .../src/main/java/net/wotonomy/web/WOElement.java | 115 +- .../src/main/java/net/wotonomy/web/WOForm.java | 181 +- .../src/main/java/net/wotonomy/web/WOFrame.java | 99 +- .../java/net/wotonomy/web/WOGenericContainer.java | 61 +- .../java/net/wotonomy/web/WOGenericElement.java | 114 +- .../main/java/net/wotonomy/web/WOHiddenField.java | 19 +- .../main/java/net/wotonomy/web/WOHyperlink.java | 408 +- .../src/main/java/net/wotonomy/web/WOImage.java | 277 +- .../main/java/net/wotonomy/web/WOImageButton.java | 78 +- .../src/main/java/net/wotonomy/web/WOInput.java | 135 +- .../main/java/net/wotonomy/web/WOMailDelivery.java | 52 +- .../src/main/java/net/wotonomy/web/WOMessage.java | 354 +- .../java/net/wotonomy/web/WOParentElement.java | 258 +- .../java/net/wotonomy/web/WOPasswordField.java | 18 +- .../main/java/net/wotonomy/web/WOPopUpButton.java | 251 +- .../main/java/net/wotonomy/web/WORadioButton.java | 18 +- .../main/java/net/wotonomy/web/WORepetition.java | 295 +- .../src/main/java/net/wotonomy/web/WORequest.java | 1003 ++- .../java/net/wotonomy/web/WORequestHandler.java | 59 +- .../main/java/net/wotonomy/web/WOResetButton.java | 33 +- .../java/net/wotonomy/web/WOResourceManager.java | 776 +-- .../net/wotonomy/web/WOResourceRequestHandler.java | 150 +- .../main/java/net/wotonomy/web/WOResourceURL.java | 38 +- .../src/main/java/net/wotonomy/web/WOResponse.java | 235 +- .../net/wotonomy/web/WOServletSessionStore.java | 262 +- .../src/main/java/net/wotonomy/web/WOSession.java | 918 ++- .../main/java/net/wotonomy/web/WOSessionStore.java | 118 +- .../java/net/wotonomy/web/WOStaticElement.java | 68 +- .../src/main/java/net/wotonomy/web/WOString.java | 173 +- .../main/java/net/wotonomy/web/WOSubmitButton.java | 77 +- .../java/net/wotonomy/web/WOSwitchComponent.java | 160 +- .../src/main/java/net/wotonomy/web/WOText.java | 52 +- .../main/java/net/wotonomy/web/WOTextField.java | 63 +- .../net/wotonomy/web/util/BrowserLauncher.java | 1242 ++-- .../java/net/wotonomy/web/xml/XMLRPCDecoder.java | 146 +- .../net/wotonomy/web/xml/XMLRPCDecoderHelper.java | 663 +- .../java/net/wotonomy/web/xml/XMLRPCEncoder.java | 840 ++- .../java/net/wotonomy/web/xml/XMLRPCReceiver.java | 77 +- .../java/net/wotonomy/web/xml/XMLRPCSelector.java | 320 +- .../java/net/wotonomy/web/xml/XMLRPCServlet.java | 423 +- .../net/wotonomy/web/xml/XMLRPCSelectorTest.java | 71 +- 296 files changed, 59328 insertions(+), 69990 deletions(-) diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataIndex.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataIndex.java index 89fa1eb..8fc681e 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataIndex.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataIndex.java @@ -22,90 +22,88 @@ import java.io.Serializable; import java.util.List; /** -* A DataIndex maintains a list of objects associated with values. -* The objects can then be retrieved based on the values. This class -* should not be much more complex than a simple map or list because -* the DataSoup is responsible for populating it. -*/ -public interface DataIndex extends Serializable -{ - /** - * Gets the name of this index. The DataSoup uses this to - * uniquely refer to this index. - * @return The name of this index. - */ - public String getName(); + * A DataIndex maintains a list of objects associated with values. The objects + * can then be retrieved based on the values. This class should not be much more + * complex than a simple map or list because the DataSoup is responsible for + * populating it. + */ +public interface DataIndex extends Serializable { + /** + * Gets the name of this index. The DataSoup uses this to uniquely refer to this + * index. + * + * @return The name of this index. + */ + public String getName(); + + /** + * The property managed by this index. This is the property used when the + * DataSoup builds and rebuilds this index. + * + * @return The property managed by this index. + */ + public String getProperty(); + + /** + * Adds an object to be associated with the specified value. + * + * @param anObject A data object, usually but not always a DataKey. + * @param newValue The property value to be associated with the data object. + * @return The data object that was inserted, or null if an error occurred. + */ + public Object addObject(Object anObject, Object newValue); - /** - * The property managed by this index. This is the property - * used when the DataSoup builds and rebuilds this index. - * @return The property managed by this index. - */ - public String getProperty(); - - /** - * Adds an object to be associated with the specified value. - * @param anObject A data object, usually but not always a DataKey. - * @param newValue The property value to be associated with the data object. - * @return The data object that was inserted, or null if an error occurred. - */ - public Object addObject( Object anObject, Object newValue ); - - /** - * Updates an object previously associated with the specified - * value to be associated with the specified new value. - * @param anObject A data object, usually but not always a DataKey. - * @param oldValue The value currently associated with the data object. - * @param newValue The value to be associated with the data object. - * @return The data object that was updated, or null if an error occurred. - */ - public Object updateObject( Object anObject, - Object oldValue, Object newValue ); - - /** - * Removes an object from the index. - * @param anObject A data object, usually but not always a DataKey. - * @param oldValue The value currently associated with the data object. - * @return The data object that was removed, or null if not found or error. - */ - public Object removeObject( Object anObject, Object oldValue ); + /** + * Updates an object previously associated with the specified value to be + * associated with the specified new value. + * + * @param anObject A data object, usually but not always a DataKey. + * @param oldValue The value currently associated with the data object. + * @param newValue The value to be associated with the data object. + * @return The data object that was updated, or null if an error occurred. + */ + public Object updateObject(Object anObject, Object oldValue, Object newValue); + + /** + * Removes an object from the index. + * + * @param anObject A data object, usually but not always a DataKey. + * @param oldValue The value currently associated with the data object. + * @return The data object that was removed, or null if not found or error. + */ + public Object removeObject(Object anObject, Object oldValue); + + /** + * Removes all objects from the index. Usually called before rebuilding the + * index. + */ + public void clear(); + + /** + * Returns all objects in the index whose associated values fall between the two + * specified values, inclusive. + * + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A List of the matching objects, ordered in increasing value, or null + * for invalid query parameters or other error. + */ + public List query(Object beginValue, Object endValue); - /** - * Removes all objects from the index. Usually called before - * rebuilding the index. - */ - public void clear(); - - /** - * Returns all objects in the index whose associated values fall - * between the two specified values, inclusive. - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A List of the matching objects, ordered in increasing - * value, or null for invalid query parameters or other error. - */ - public List query( Object beginValue, Object endValue ); - } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:46:50 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:46:50 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:35 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:35 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataKey.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataKey.java index 6fe574f..4ac8b50 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataKey.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataKey.java @@ -22,121 +22,101 @@ import java.io.Serializable; import net.wotonomy.foundation.internal.ValueConverter; -public class DataKey implements Comparable, Serializable, Cloneable -{ +public class DataKey implements Comparable, Serializable, Cloneable { static final long serialVersionUID = 8421127539579065705L; - Long key; - - public DataKey() - { - key = new Long( 0 ); - } - - /** - * Converts string representation to new object. - */ - public DataKey( String aString ) - { - this(); - setKeyString( aString ); - } - - public int hashCode() - { - return key.intValue(); - } - - public void increment() - { - key = new Long( key.longValue() + 1 ); - } - - public Object clone() - { - return new DataKey( this.toString() ); - } - - public String toString() - { - return key.toString(); - } - - public String getKeyString() - { - return key.toString(); - } - - public void setKeyString( String aString ) - { - Long parsed = ValueConverter.getLong( aString ); - if ( parsed != null ) key = parsed; - } - - public boolean equals( Object anObject ) - { - if ( anObject instanceof String ) - { - if ( toString().equals( anObject ) ) - { - return true; - } - } - if ( ! ( anObject instanceof DataKey ) ) return false; - return key.equals( ((DataKey)anObject).key ); - } - - public int compareTo( Object anObject ) - { - if ( anObject instanceof String ) - { - if ( toString().equals( anObject ) ) - { - return 0; - } - } - if ( ! ( anObject instanceof DataKey ) ) - { - Long converted = (Long) ValueConverter.getLong( anObject ); - if ( converted != null ) - { - return (int) ( key.longValue() - converted.longValue() ); - } - return 0; - }; - return (int) ( key.longValue() - ((DataKey)anObject).key.longValue() ); - } + Long key; + + public DataKey() { + key = new Long(0); + } + + /** + * Converts string representation to new object. + */ + public DataKey(String aString) { + this(); + setKeyString(aString); + } + + public int hashCode() { + return key.intValue(); + } + + public void increment() { + key = new Long(key.longValue() + 1); + } + + public Object clone() { + return new DataKey(this.toString()); + } + + public String toString() { + return key.toString(); + } + + public String getKeyString() { + return key.toString(); + } + + public void setKeyString(String aString) { + Long parsed = ValueConverter.getLong(aString); + if (parsed != null) + key = parsed; + } + + public boolean equals(Object anObject) { + if (anObject instanceof String) { + if (toString().equals(anObject)) { + return true; + } + } + if (!(anObject instanceof DataKey)) + return false; + return key.equals(((DataKey) anObject).key); + } + + public int compareTo(Object anObject) { + if (anObject instanceof String) { + if (toString().equals(anObject)) { + return 0; + } + } + if (!(anObject instanceof DataKey)) { + Long converted = (Long) ValueConverter.getLong(anObject); + if (converted != null) { + return (int) (key.longValue() - converted.longValue()); + } + return 0; + } + ; + return (int) (key.longValue() - ((DataKey) anObject).key.longValue()); + } } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2003/08/14 19:29:38 chochos - * minor cleanup (imports, static method calls, etc) + * Revision 1.4 2003/08/14 19:29:38 chochos minor cleanup (imports, static + * method calls, etc) * - * Revision 1.3 2001/02/23 23:44:44 mpowers - * Fixes for hashcode to ensure proper key comparison. + * Revision 1.3 2001/02/23 23:44:44 mpowers Fixes for hashcode to ensure proper + * key comparison. * - * Revision 1.2 2001/02/15 21:12:41 mpowers - * Added accessors for key throughout the api. This breaks compatibility. - * insertObject now returns the permanent key for the newly created object. - * The old way returned a copy of the object which was an additional read - * that was often ignored. Now you can read it only if you need it. - * Furthermore, there was not other way of getting the permanent key. + * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout + * the api. This breaks compatibility. insertObject now returns the permanent + * key for the newly created object. The old way returned a copy of the object + * which was an additional read that was often ignored. Now you can read it only + * if you need it. Furthermore, there was not other way of getting the permanent + * key. * - * Revision 1.1.1.1 2000/12/21 15:47:04 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:04 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataSoup.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataSoup.java index 3c37d43..c7523b3 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataSoup.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataSoup.java @@ -20,197 +20,207 @@ package net.wotonomy.datastore; import java.util.Collection; -public interface DataSoup -{ - /** - * Adds the specified object to the soup and returns the key - * for the new object by which it may be subsequently retrieved. - * Null indicates an error, probably due to serialization. - * @param anObject Object to be added to soup. - * @return The unique identifier used for the new object. - */ - public DataKey addObject( Object anObject ); - - /** - * Removes the specified object from the soup and returns - * the removed object as read from the soup (which is the - * original copy of the object). Null indicates object not found. - * @param aKey A key for an object to be removed. - * @return The object that was removed, or null if not found or error. - */ - public Object removeObject( DataKey aKey ); - - /** - * Updates the specified object and returns the object - * as updated. Null indicates an error writing the object. - * @param aKey A key for an object to be updated. - * @param aKey The new object for that key. - * @return A copy of the updated object, possibly updated, - * or null if not found or error. - */ - public Object updateObject( DataKey aKey, Object updatedObject ); - - /** - * Gets object from data store whose identifier is equal - * to the specified object. - * @param aKey A key for an object to be retrieved. - * @return The corresponding object from the soup. - */ - public Object getObjectByKey( DataKey aKey ); - +public interface DataSoup { /** - * Registers an object that may or may not be created - * later, returning a temporary but uniquely identifiable - * key. The key will be replaced with a permanent key when - * the object is created with addObject(). - * @param anObject An object to be registered. - * @return A temporary key for this object. - */ - public DataKey registerTemporaryObject( Object anObject ); + * Adds the specified object to the soup and returns the key for the new object + * by which it may be subsequently retrieved. Null indicates an error, probably + * due to serialization. + * + * @param anObject Object to be added to soup. + * @return The unique identifier used for the new object. + */ + public DataKey addObject(Object anObject); - // index management - /** - * Adds an index to the soup. - * @param aName The string identifier for this index. - * @param aProperty The property on which this index will be based. - */ - public void addIndex( String aName, String aProperty ); - + * Removes the specified object from the soup and returns the removed object as + * read from the soup (which is the original copy of the object). Null indicates + * object not found. + * + * @param aKey A key for an object to be removed. + * @return The object that was removed, or null if not found or error. + */ + public Object removeObject(DataKey aKey); + + /** + * Updates the specified object and returns the object as updated. Null + * indicates an error writing the object. + * + * @param aKey A key for an object to be updated. + * @param aKey The new object for that key. + * @return A copy of the updated object, possibly updated, or null if not found + * or error. + */ + public Object updateObject(DataKey aKey, Object updatedObject); + + /** + * Gets object from data store whose identifier is equal to the specified + * object. + * + * @param aKey A key for an object to be retrieved. + * @return The corresponding object from the soup. + */ + public Object getObjectByKey(DataKey aKey); + /** - * Deletes the specified index from the soup. - * @param aName The string identifier for the index to be removed. - */ - public void removeIndex( String aName ); - + * Registers an object that may or may not be created later, returning a + * temporary but uniquely identifiable key. The key will be replaced with a + * permanent key when the object is created with addObject(). + * + * @param anObject An object to be registered. + * @return A temporary key for this object. + */ + public DataKey registerTemporaryObject(Object anObject); + + // index management + /** - * Gets a collection of all indices in this soup. - * @return A collection of all indices in this soup. - */ + * Adds an index to the soup. + * + * @param aName The string identifier for this index. + * @param aProperty The property on which this index will be based. + */ + public void addIndex(String aName, String aProperty); + + /** + * Deletes the specified index from the soup. + * + * @param aName The string identifier for the index to be removed. + */ + public void removeIndex(String aName); + + /** + * Gets a collection of all indices in this soup. + * + * @return A collection of all indices in this soup. + */ public Collection getAllIndices(); - - // relationship management - + + // relationship management + /** - * Adds a relation to entries in another soup. - * @param aProperty The property on which this relation will be based. - * @param aSoup The name of the soup to be related in this store. - */ + * Adds a relation to entries in another soup. + * + * @param aProperty The property on which this relation will be based. + * @param aSoup The name of the soup to be related in this store. + */ // public void addRelation( String aProperty, String aSoup ); - + /** - * Deletes the specified relation to entries in another soup. - * @param aProperty The property on which this relation will be based. - * @param aSoup The name of the soup to be related in this store. - */ + * Deletes the specified relation to entries in another soup. + * + * @param aProperty The property on which this relation will be based. + * @param aSoup The name of the soup to be related in this store. + */ // public void removeRelation( String aProperty, String aSoup ); - + /** - * Gets a collection of all relations in this soup. - * @return A collection of all relation in this soup. - */ + * Gets a collection of all relations in this soup. + * + * @return A collection of all relation in this soup. + */ // public Collection getAllRelations(); - - // queries + + // queries /** - * Returns an empty data view, suitable for creating - * new entries in the soup. - * @return A DataView containing no entries. - */ + * Returns an empty data view, suitable for creating new entries in the soup. + * + * @return A DataView containing no entries. + */ public DataView createView(); - - /** - * Queries by the specified pre-generated index, if it exists. - * Will return objects whose values for the indexed property - * fall between the two values inclusive. - * Otherwise, falls through to queryByProperty. - * @param anIndexName The index to be queried. - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView queryByIndex( - String anIndexName, Object beginKey, Object endKey ); - - /** - * Generates an index based on the specified property - * and then executes the query. - * Will return objects whose values for the specified property - * fall between the two values inclusive. - * @param aPropertyName The property to be queried. If null, - * will query the objects directly with queryObjects(). - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView queryByProperty( - String aPropertyName, Object beginKey, Object endKey ); - /** - * Generates an index based on the values of the objects themselves - * and then executes the query. - * Will return objects whose values fall between the two values inclusive. - * @param beginValue The beginning value, or null for all values - * up to and including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView queryObjects( Object beginKey, Object endKey ); + /** + * Queries by the specified pre-generated index, if it exists. Will return + * objects whose values for the indexed property fall between the two values + * inclusive. Otherwise, falls through to queryByProperty. + * + * @param anIndexName The index to be queried. + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView queryByIndex(String anIndexName, Object beginKey, Object endKey); + + /** + * Generates an index based on the specified property and then executes the + * query. Will return objects whose values for the specified property fall + * between the two values inclusive. + * + * @param aPropertyName The property to be queried. If null, will query the + * objects directly with queryObjects(). + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView queryByProperty(String aPropertyName, Object beginKey, Object endKey); + + /** + * Generates an index based on the values of the objects themselves and then + * executes the query. Will return objects whose values fall between the two + * values inclusive. + * + * @param beginValue The beginning value, or null for all values up to and + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView queryObjects(Object beginKey, Object endKey); + + /** + * Returns a view containing the objects for the specified keys. + * + * @param aKeyList A collection of keys to be placed in the view. + * @return A DataView containing the objects for the corresponding keys, in the + * order in which the keys are returned from the collection. + */ + public DataView queryByKeys(Collection aKeyList); - /** - * Returns a view containing the objects for the specified keys. - * @param aKeyList A collection of keys to be placed in the view. - * @return A DataView containing the objects for the corresponding - * keys, in the order in which the keys are returned from the collection. - */ - public DataView queryByKeys( Collection aKeyList ); - - /** - * As queryByIndex, but with objects returned in reverse order. - * @param anIndexName The index to be queried. - * @param beginValue The beginning value, or null for all values - * up to and including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryByIndex( - String anIndexName, Object beginKey, Object endKey ); - - /** - * As queryByProperty, but with objects returned in reverse order. - * @param aPropertyName The property to be queried. If null, - * will query the objects directly with queryObjects(). - * @param beginValue The beginning value, or null for all values - * up to and including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryByProperty( - String aPropertyName, Object beginKey, Object endKey ); + /** + * As queryByIndex, but with objects returned in reverse order. + * + * @param anIndexName The index to be queried. + * @param beginValue The beginning value, or null for all values up to and + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryByIndex(String anIndexName, Object beginKey, Object endKey); - /** - * As queryObjects, but with objects returned in reverse order. - * @param beginValue The beginning value, or null for all values - * up to and including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryObjects( Object beginKey, Object endKey ); + /** + * As queryByProperty, but with objects returned in reverse order. + * + * @param aPropertyName The property to be queried. If null, will query the + * objects directly with queryObjects(). + * @param beginValue The beginning value, or null for all values up to and + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryByProperty(String aPropertyName, Object beginKey, Object endKey); + + /** + * As queryObjects, but with objects returned in reverse order. + * + * @param beginValue The beginning value, or null for all values up to and + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryObjects(Object beginKey, Object endKey); // public void addIndex( String aName, DataIndex anIndex ) {} // public void removeIndex( String aName ) {} @@ -219,39 +229,32 @@ public interface DataSoup // public void removeTaggedObject( String aKey ); // public void setMetaData( // String aMetaProperty, Serializable aValue, Serializable anObject ); - + } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2003/08/14 19:29:38 chochos - * minor cleanup (imports, static method calls, etc) + * Revision 1.4 2003/08/14 19:29:38 chochos minor cleanup (imports, static + * method calls, etc) * - * Revision 1.3 2001/03/05 22:12:11 mpowers - * Created the control package for a datastore-specific implementation - * of EOObjectStore. + * Revision 1.3 2001/03/05 22:12:11 mpowers Created the control package for a + * datastore-specific implementation of EOObjectStore. * - * Revision 1.2 2001/02/15 21:12:41 mpowers - * Added accessors for key throughout the api. This breaks compatibility. - * insertObject now returns the permanent key for the newly created object. - * The old way returned a copy of the object which was an additional read - * that was often ignored. Now you can read it only if you need it. - * Furthermore, there was not other way of getting the permanent key. + * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout + * the api. This breaks compatibility. insertObject now returns the permanent + * key for the newly created object. The old way returned a copy of the object + * which was an additional read that was often ignored. Now you can read it only + * if you need it. Furthermore, there was not other way of getting the permanent + * key. * - * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataStore.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataStore.java index 28fbc90..2924ca3 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataStore.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataStore.java @@ -22,73 +22,58 @@ import java.io.File; import java.io.Serializable; import java.util.Iterator; -public class DataStore implements Serializable -{ - protected File homeDirectory; - - public DataStore( String aPath ) - { - homeDirectory = new File( aPath ); - - // if specified directory does not exist - if ( ! homeDirectory.exists() ) - { - homeDirectory.mkdirs(); - } - - // if existing path is a file, exit with error - if ( homeDirectory.isDirectory() ) - { - new RuntimeException( "DataStore: Specified directory is a file." ); +public class DataStore implements Serializable { + protected File homeDirectory; + + public DataStore(String aPath) { + homeDirectory = new File(aPath); + + // if specified directory does not exist + if (!homeDirectory.exists()) { + homeDirectory.mkdirs(); + } + + // if existing path is a file, exit with error + if (homeDirectory.isDirectory()) { + new RuntimeException("DataStore: Specified directory is a file."); + } + } + + public File getHomeDirectory() { + return homeDirectory; + } + + public DataSoup getSoupForName(String aName) { + return null; } - } - - public File getHomeDirectory() - { - return homeDirectory; - } - - - public DataSoup getSoupForName( String aName ) - { - return null; - } - public void removeSoup( DataSoup aSoup ) - { - // FIXME - } - public Iterator getAllSoups() - { - return null; - } - - public static void main( String[] argv ) - { - new DataStore( "/Local/Users/michael/Projects/test/data" ); - } - + + public void removeSoup(DataSoup aSoup) { + // FIXME + } + + public Iterator getAllSoups() { + return null; + } + + public static void main(String[] argv) { + new DataStore("/Local/Users/michael/Projects/test/data"); + } + } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/03/05 22:12:11 mpowers - * Created the control package for a datastore-specific implementation - * of EOObjectStore. + * Revision 1.2 2001/03/05 22:12:11 mpowers Created the control package for a + * datastore-specific implementation of EOObjectStore. * - * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataView.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataView.java index fec9cfc..a0e7819 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataView.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DataView.java @@ -21,91 +21,81 @@ package net.wotonomy.datastore; import java.util.List; import java.util.Observer; -public interface DataView extends List -{ +public interface DataView extends List { // public void newQuery( String aProperty, Object beginKey, Object endKey ); // public void addQuery( String aProperty, Object beginKey, Object endKey ); // public void removeQuery( String aProperty, Object beginKey, Object endKey ); // public void retainQuery( String aProperty, Object beginKey, Object endKey ); /** - * This method is called to notify the DataView - * that one of its objects has been modified and - * should be updated when the view is committed. - */ + * This method is called to notify the DataView that one of its objects has been + * modified and should be updated when the view is committed. + */ public void update(Object o); /** - * This method is called commit all changes to - * the DataView to its data store. The list - * elements may be refreshed from the datastore, - * although the list itself should remain unchanged. - * @return True if the commit was successful, - * otherwise false. - */ + * This method is called commit all changes to the DataView to its data store. + * The list elements may be refreshed from the datastore, although the list + * itself should remain unchanged. + * + * @return True if the commit was successful, otherwise false. + */ public boolean commit(); /** - * Called to add the specified observer to the - * list of observers that should receive notifications - * when the view if modified. DataViews notify - * when objects are added, updated, or deleted, - * passing the affected object as the parameter - * to the Observer's notify method. - * @param o The observer to add. - */ - public void addObserver(Observer o); + * Called to add the specified observer to the list of observers that should + * receive notifications when the view if modified. DataViews notify when + * objects are added, updated, or deleted, passing the affected object as the + * parameter to the Observer's notify method. + * + * @param o The observer to add. + */ + public void addObserver(Observer o); /** - * Called to remove the specified observer from the - * list of observers that should receive notifications - * when the view if modified. - * @param o The observer to delete. - */ - public void deleteObserver(Observer o); + * Called to remove the specified observer from the list of observers that + * should receive notifications when the view if modified. + * + * @param o The observer to delete. + */ + public void deleteObserver(Observer o); /** - * Called to clear the list of observers that should - * receive notifications when the view if modified. - */ - public void deleteObservers(); + * Called to clear the list of observers that should receive notifications when + * the view if modified. + */ + public void deleteObservers(); /** - * Returns the key for the specified object. - * If the object is not in the view, returns null. - */ - public DataKey getKeyForObject( Object anObject ); + * Returns the key for the specified object. If the object is not in the view, + * returns null. + */ + public DataKey getKeyForObject(Object anObject); /** - * Returns the object for the specified key. - * If the key is not in the view, returns null. - */ - public Object getObjectForKey( DataKey aKey ); + * Returns the object for the specified key. If the key is not in the view, + * returns null. + */ + public Object getObjectForKey(DataKey aKey); } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/15 21:12:41 mpowers - * Added accessors for key throughout the api. This breaks compatibility. - * insertObject now returns the permanent key for the newly created object. - * The old way returned a copy of the object which was an additional read - * that was often ignored. Now you can read it only if you need it. - * Furthermore, there was not other way of getting the permanent key. + * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout + * the api. This breaks compatibility. insertObject now returns the permanent + * key for the newly created object. The old way returned a copy of the object + * which was an additional read that was often ignored. Now you can read it only + * if you need it. Furthermore, there was not other way of getting the permanent + * key. * - * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:05 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultComparator.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultComparator.java index b97b8a1..dc49737 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultComparator.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultComparator.java @@ -24,122 +24,99 @@ import java.util.Comparator; import net.wotonomy.foundation.internal.ValueConverter; /** -* DefaultComparator exists to compare basic java -* primitive wrappers, since these classes don't -* implement Comparable in jdk 1.1.x. Also uses -* ValueConverter to try to match types for comparison. -*/ -public class DefaultComparator implements Comparator, Serializable -{ - public int compare(Object o1, Object o2) - { + * DefaultComparator exists to compare basic java primitive wrappers, since + * these classes don't implement Comparable in jdk 1.1.x. Also uses + * ValueConverter to try to match types for comparison. + */ +public class DefaultComparator implements Comparator, Serializable { + public int compare(Object o1, Object o2) { // System.out.println( "compare: " + o1 + " : " + o1.getClass() + " : " + o2 + " : " + o2.getClass() ); -/* - if ( ( o1 instanceof Comparable ) && ( o2 instanceof Comparable ) ) - { - return ((Comparable)o1).compareTo( o2 ); - } -*/ - if ( ( o1 instanceof Number ) && ( o2 instanceof Number ) ) - { - // TODO: special case for each type would be faster - return (int) - ( ((Number)o1).doubleValue() - ((Number)o2).doubleValue() ); - } - - if ( o1 instanceof StringBuffer ) - { - o1 = o1.toString(); - } - if ( o2 instanceof StringBuffer ) - { - o2 = o2.toString(); - } - - if ( ( o1 instanceof String ) && ( o2 instanceof String ) ) - { - return ((String)o1).compareTo( ((String)o2) ); - } - - if ( ( o1 instanceof Character ) && ( o2 instanceof Character ) ) - { - return (int) - ((Character)o1).charValue() - ((Character)o2).charValue(); - } - - if ( ( o1 instanceof Byte ) && ( o2 instanceof Byte ) ) - { - return (int) - ((Byte)o1).byteValue() - ((Byte)o2).byteValue(); - } - - if ( ( o1 instanceof Boolean ) && ( o2 instanceof Boolean ) ) - { - if ( o1.equals( o2 ) ) return 0; - - // presumably TRUE is greater than FALSE - if ( o1.equals( Boolean.TRUE ) ) return 1; - return -1; - } - - // handle all NULL cases: NULL is less than anything else. - if ( ( o1 == null ) && ( o2 == null ) ) return 0; - if ( ( o1 == null ) && ( o2 != null ) ) return -1; - if ( ( o2 == null ) && ( o1 != null ) ) return 1; - - if ( o1.getClass() != o2.getClass() ) - { - Object convertedValue; - - if ( ! ( o2 instanceof String ) ) - // (string should be lowest common demoninator, if possible) - { - // convert first to second's type - convertedValue = - ValueConverter.convertObjectToClass( o1, o2.getClass() ); - if ( convertedValue != null ) - { - return compare( convertedValue, o2 ); - } - } - - // convert second to first's type - convertedValue = - ValueConverter.convertObjectToClass( o2, o1.getClass() ); - if ( convertedValue != null ) - { - return -1 * compare( convertedValue, o1 ); // reverse result - } - } - - // we tried really hard, but these values are incomparable: - // we'll consider them equal. - return 0; - } - - public boolean equals(Object obj) - { - return (obj == this); - } + /* + * if ( ( o1 instanceof Comparable ) && ( o2 instanceof Comparable ) ) { return + * ((Comparable)o1).compareTo( o2 ); } + */ + if ((o1 instanceof Number) && (o2 instanceof Number)) { + // TODO: special case for each type would be faster + return (int) (((Number) o1).doubleValue() - ((Number) o2).doubleValue()); + } + + if (o1 instanceof StringBuffer) { + o1 = o1.toString(); + } + if (o2 instanceof StringBuffer) { + o2 = o2.toString(); + } + + if ((o1 instanceof String) && (o2 instanceof String)) { + return ((String) o1).compareTo(((String) o2)); + } + + if ((o1 instanceof Character) && (o2 instanceof Character)) { + return (int) ((Character) o1).charValue() - ((Character) o2).charValue(); + } + + if ((o1 instanceof Byte) && (o2 instanceof Byte)) { + return (int) ((Byte) o1).byteValue() - ((Byte) o2).byteValue(); + } + + if ((o1 instanceof Boolean) && (o2 instanceof Boolean)) { + if (o1.equals(o2)) + return 0; + + // presumably TRUE is greater than FALSE + if (o1.equals(Boolean.TRUE)) + return 1; + return -1; + } + + // handle all NULL cases: NULL is less than anything else. + if ((o1 == null) && (o2 == null)) + return 0; + if ((o1 == null) && (o2 != null)) + return -1; + if ((o2 == null) && (o1 != null)) + return 1; + + if (o1.getClass() != o2.getClass()) { + Object convertedValue; + + if (!(o2 instanceof String)) + // (string should be lowest common demoninator, if possible) + { + // convert first to second's type + convertedValue = ValueConverter.convertObjectToClass(o1, o2.getClass()); + if (convertedValue != null) { + return compare(convertedValue, o2); + } + } + + // convert second to first's type + convertedValue = ValueConverter.convertObjectToClass(o2, o1.getClass()); + if (convertedValue != null) { + return -1 * compare(convertedValue, o1); // reverse result + } + } + + // we tried really hard, but these values are incomparable: + // we'll consider them equal. + return 0; + } + + public boolean equals(Object obj) { + return (obj == this); + } } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:08 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:08 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:36 michael Added log to all files. * * */ - - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataIndex.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataIndex.java index a8ede78..218c1d4 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataIndex.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataIndex.java @@ -25,215 +25,193 @@ import java.util.List; import java.util.TreeMap; /** -* This implementation of DataIndex wraps a TreeMap and -* adds the ability to contain objects with duplicate keys. -*/ -public class DefaultDataIndex implements DataIndex -{ + * This implementation of DataIndex wraps a TreeMap and adds the ability to + * contain objects with duplicate keys. + */ +public class DefaultDataIndex implements DataIndex { static final long serialVersionUID = -3759982714240822885L; - protected String name; - protected String property; - private TreeMap treeMap; - private Comparator comparator; - - public DefaultDataIndex() - { - comparator = new DefaultComparator(); - setTreeMap( new TreeMap( new DefaultComparator() ) ); - } - - public DefaultDataIndex( String aName, String aProperty ) - { - this(); - setName( aName ); - setProperty( aProperty ); - } - - // included for xml serialization - public Comparator getComparator() { return comparator; } - public void setComparator( Comparator aComparator ) - { - comparator = aComparator; - // set comparator and copy contents - TreeMap map = getTreeMap(); - setTreeMap( new TreeMap( comparator ) ); - getTreeMap().putAll( map ); - - } - - public String getName() { return name; }; - public void setName( String aName ) { name = aName; } - public String getProperty() { return property; }; - public void setProperty( String aProperty ) { property = aProperty; } - public TreeMap getTreeMap() { return treeMap; } - public void setTreeMap( TreeMap aMap ) { treeMap = aMap; } - - public List query( Object beginValue, Object endValue ) - { + protected String name; + protected String property; + private TreeMap treeMap; + private Comparator comparator; + + public DefaultDataIndex() { + comparator = new DefaultComparator(); + setTreeMap(new TreeMap(new DefaultComparator())); + } + + public DefaultDataIndex(String aName, String aProperty) { + this(); + setName(aName); + setProperty(aProperty); + } + + // included for xml serialization + public Comparator getComparator() { + return comparator; + } + + public void setComparator(Comparator aComparator) { + comparator = aComparator; + // set comparator and copy contents + TreeMap map = getTreeMap(); + setTreeMap(new TreeMap(comparator)); + getTreeMap().putAll(map); + + } + + public String getName() { + return name; + }; + + public void setName(String aName) { + name = aName; + } + + public String getProperty() { + return property; + }; + + public void setProperty(String aProperty) { + property = aProperty; + } + + public TreeMap getTreeMap() { + return treeMap; + } + + public void setTreeMap(TreeMap aMap) { + treeMap = aMap; + } + + public List query(Object beginValue, Object endValue) { //System.out.println( "DefaultDataIndex.query: " + beginValue + " : " + endValue ); List result = new LinkedList(); - if ( endValue == null ) - { - if ( beginValue == null ) - { - // begin and end are null, return entire set - populateListFromIterator( result, treeMap.values().iterator() ); - return result; - } - - // only end is null, return all starting from beginValue - populateListFromIterator( result, - treeMap.tailMap( beginValue ).values().iterator() ); + if (endValue == null) { + if (beginValue == null) { + // begin and end are null, return entire set + populateListFromIterator(result, treeMap.values().iterator()); + return result; + } + + // only end is null, return all starting from beginValue + populateListFromIterator(result, treeMap.tailMap(beginValue).values().iterator()); return result; - } - else - if ( beginValue == null ) - { - // only begin is null, return all ending with endValue - populateListFromIterator( result, - treeMap.headMap( endValue ).values().iterator() ); - } - else - { - // begin and end are specified, return all inclusive - populateListFromIterator( result, - treeMap.subMap( beginValue, endValue ).values().iterator() ); + } else if (beginValue == null) { + // only begin is null, return all ending with endValue + populateListFromIterator(result, treeMap.headMap(endValue).values().iterator()); + } else { + // begin and end are specified, return all inclusive + populateListFromIterator(result, treeMap.subMap(beginValue, endValue).values().iterator()); } - + // append endValue results, so it's inclusive - Object o = treeMap.get( endValue ); - if ( o != null ) - { - if ( o instanceof DuplicateList ) - { - populateListFromIterator( result, - ((DuplicateList)o).iterator() ); - } - else - { - result.add( o ); - } + Object o = treeMap.get(endValue); + if (o != null) { + if (o instanceof DuplicateList) { + populateListFromIterator(result, ((DuplicateList) o).iterator()); + } else { + result.add(o); + } } - + // return complete result return result; } - - protected void populateListFromIterator( List aList, Iterator it ) - { - Object o; - while ( it.hasNext() ) - { - o = it.next(); - if ( o instanceof DuplicateList ) - { - populateListFromIterator( - aList, ((DuplicateList)o).iterator() ); - } - else - { - aList.add( o ); - } - } - } - - public Object addObject( Object anObject, Object newValue ) - { - Object o = treeMap.get( newValue ); - if ( o != null ) - { - if ( o instanceof DuplicateList ) - { - ((DuplicateList)o).add( anObject ); - return anObject; - } - - DuplicateList list = new DuplicateList(); - list.add( o ); - list.add( anObject ); - anObject = list; - - } -if ( anObject == null ) new RuntimeException().printStackTrace(); - - treeMap.put( newValue, anObject ); - return anObject; - } - - public Object updateObject( Object anObject, - Object oldValue, Object newValue ) - { - removeObject( anObject, oldValue ); - return addObject( anObject, newValue ); - } - - public Object removeObject( Object anObject, Object oldValue ) - { - Object o = treeMap.get( oldValue ); - if ( o != null ) - { - if ( o instanceof DuplicateList ) - { - // remove this item from the list - DuplicateList list = (DuplicateList) o; - list.remove( anObject ); - + + protected void populateListFromIterator(List aList, Iterator it) { + Object o; + while (it.hasNext()) { + o = it.next(); + if (o instanceof DuplicateList) { + populateListFromIterator(aList, ((DuplicateList) o).iterator()); + } else { + aList.add(o); + } + } + } + + public Object addObject(Object anObject, Object newValue) { + Object o = treeMap.get(newValue); + if (o != null) { + if (o instanceof DuplicateList) { + ((DuplicateList) o).add(anObject); + return anObject; + } + + DuplicateList list = new DuplicateList(); + list.add(o); + list.add(anObject); + anObject = list; + + } + if (anObject == null) + new RuntimeException().printStackTrace(); + + treeMap.put(newValue, anObject); + return anObject; + } + + public Object updateObject(Object anObject, Object oldValue, Object newValue) { + removeObject(anObject, oldValue); + return addObject(anObject, newValue); + } + + public Object removeObject(Object anObject, Object oldValue) { + Object o = treeMap.get(oldValue); + if (o != null) { + if (o instanceof DuplicateList) { + // remove this item from the list + DuplicateList list = (DuplicateList) o; + list.remove(anObject); + // if there are still duplicates, return - if ( list.size() > 1 ) - return anObject; - - // else, list size must be one - if ( list.size() == 0 ) - { - System.out.println( "DefaultDataIndex.deleteObject: " + oldValue - + " : list size is 1 : this should never happen." ); + if (list.size() > 1) + return anObject; + + // else, list size must be one + if (list.size() == 0) { + System.out.println("DefaultDataIndex.deleteObject: " + oldValue + + " : list size is 1 : this should never happen."); return null; - } + } - // replace existing list with remaining item from list - treeMap.remove( oldValue ); - treeMap.put( oldValue, list.getFirst() ); - return anObject; - } + // replace existing list with remaining item from list + treeMap.remove(oldValue); + treeMap.put(oldValue, list.getFirst()); + return anObject; + } // otherwise, proceed normally - treeMap.remove( oldValue ); - } + treeMap.remove(oldValue); + } return anObject; - } - - public void clear() - { - treeMap.clear(); - } - - public String toString() - { - return "DefaultDataIndex: " + name + " : " + property + " : " + treeMap.toString(); - } - + } + + public void clear() { + treeMap.clear(); + } + + public String toString() { + return "DefaultDataIndex: " + name + " : " + property + " : " + treeMap.toString(); + } + } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/14 19:29:38 chochos - * minor cleanup (imports, static method calls, etc) + * Revision 1.2 2003/08/14 19:29:38 chochos minor cleanup (imports, static + * method calls, etc) * - * Revision 1.1.1.1 2000/12/21 15:47:11 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:11 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataView.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataView.java index ca76252..1864cac 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataView.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DefaultDataView.java @@ -26,21 +26,18 @@ import java.util.List; import java.util.ListIterator; import java.util.Observable; -public class DefaultDataView extends Observable - implements DataView -{ - protected DataSoup backingSoup; - protected List objectList; - protected List keyList; - protected List addedObjectList; - protected List removedObjectList; - protected List addedKeyList; - protected List removedKeyList; - protected Collection updatedObjects; - protected boolean fullyLoaded; - - DefaultDataView( DataSoup aSoup, Collection aKeyList ) - { +public class DefaultDataView extends Observable implements DataView { + protected DataSoup backingSoup; + protected List objectList; + protected List keyList; + protected List addedObjectList; + protected List removedObjectList; + protected List addedKeyList; + protected List removedKeyList; + protected Collection updatedObjects; + protected boolean fullyLoaded; + + DefaultDataView(DataSoup aSoup, Collection aKeyList) { backingSoup = aSoup; objectList = new ArrayList(); keyList = new ArrayList(); @@ -51,11 +48,10 @@ public class DefaultDataView extends Observable updatedObjects = new LinkedList(); fullyLoaded = false; - setKeyList( aKeyList ); + setKeyList(aKeyList); } - - void setKeyList( Collection aCollection ) - { + + void setKeyList(Collection aCollection) { fullyLoaded = false; addedObjectList.clear(); removedObjectList.clear(); @@ -64,494 +60,434 @@ public class DefaultDataView extends Observable updatedObjects.clear(); keyList.clear(); objectList.clear(); - if ( ( aCollection == null ) || ( aCollection.size() == 0 ) ) - { + if ((aCollection == null) || (aCollection.size() == 0)) { return; } - keyList.addAll( aCollection ); - for ( int i = 0; i < keyList.size(); i++ ) - { - objectList.add( null ); + keyList.addAll(aCollection); + for (int i = 0; i < keyList.size(); i++) { + objectList.add(null); } } - public Object get( int i ) - { - if ( i > keyList.size() ) return null; + public Object get(int i) { + if (i > keyList.size()) + return null; - Object o = objectList.get( i ); - if ( o == null ) - { - Object key = keyList.get( i ); - if ( key == null ) return null; // FIXME!! + Object o = objectList.get(i); + if (o == null) { + Object key = keyList.get(i); + if (key == null) + return null; // FIXME!! - //NOTE: this is the gateway for getting object from the soup - o = backingSoup.getObjectByKey( (DataKey) key ); + // NOTE: this is the gateway for getting object from the soup + o = backingSoup.getObjectByKey((DataKey) key); - objectList.set( i, o ); + objectList.set(i, o); } return o; } - public int indexOf( Object o ) - { - if ( o == null ) return -1; - for ( int i = 0; i < size(); i++ ) - { - if ( o.equals( get( i ) ) ) - { - return i; - } - } - return -1; - } - - private int indexOfIdenticalObject( Object o ) - { - if ( o == null ) return -1; - for ( int i = 0; i < size(); i++ ) - { - if ( o == get( i ) ) - { - return i; - } - } - return -1; - } - - public int lastIndexOf( Object o ) - { - if ( o == null ) return -1; - int lastIndex = -1; - for ( int i = 0; i < size(); i++ ) - { - if ( o.equals( get( i ) ) ) - { - lastIndex = i; - } - } - return lastIndex; - } - - protected void loadAllObjects() - { - if ( fullyLoaded ) return; - for ( int i = 0; i < keyList.size(); i++ ) - { - get( i ); + public int indexOf(Object o) { + if (o == null) + return -1; + for (int i = 0; i < size(); i++) { + if (o.equals(get(i))) { + return i; + } + } + return -1; + } + + private int indexOfIdenticalObject(Object o) { + if (o == null) + return -1; + for (int i = 0; i < size(); i++) { + if (o == get(i)) { + return i; + } + } + return -1; + } + + public int lastIndexOf(Object o) { + if (o == null) + return -1; + int lastIndex = -1; + for (int i = 0; i < size(); i++) { + if (o.equals(get(i))) { + lastIndex = i; + } + } + return lastIndex; + } + + protected void loadAllObjects() { + if (fullyLoaded) + return; + for (int i = 0; i < keyList.size(); i++) { + get(i); } fullyLoaded = true; } - - // convenience to return the first object, or null. - public Object getObject() - { - return get( 0 ); - } - + + // convenience to return the first object, or null. + public Object getObject() { + return get(0); + } + // marked object as updated - public void update( Object anObject ) - { - if ( contains( anObject ) ) - { - if ( ! addedObjectList.contains( anObject ) ) - { - updatedObjects.add( anObject ); - } + public void update(Object anObject) { + if (contains(anObject)) { + if (!addedObjectList.contains(anObject)) { + updatedObjects.add(anObject); + } } - // notification - setChanged(); - notifyObservers( anObject ); - } - - // DefaultDataViews know their parent soup to perform the query - // and take the subset - public DataView query( - String aProperty, Object beginKey, Object endKey ) { return this; } - - public boolean commit() - { - int index; - Object o; - DataKey key; - for ( int i = 0; i < addedObjectList.size(); i++ ) - { - o = addedObjectList.get(i); - key = backingSoup.addObject( o ); - index = indexOfIdenticalObject( o ); - keyList.set( index, key ); + // notification + setChanged(); + notifyObservers(anObject); + } + + // DefaultDataViews know their parent soup to perform the query + // and take the subset + public DataView query(String aProperty, Object beginKey, Object endKey) { + return this; + } + + public boolean commit() { + int index; + Object o; + DataKey key; + for (int i = 0; i < addedObjectList.size(); i++) { + o = addedObjectList.get(i); + key = backingSoup.addObject(o); + index = indexOfIdenticalObject(o); + keyList.set(index, key); } addedObjectList.clear(); addedKeyList.clear(); - for ( int i = 0; i < removedObjectList.size(); i++ ) - { - backingSoup.removeObject( (DataKey) removedKeyList.get(i) ); + for (int i = 0; i < removedObjectList.size(); i++) { + backingSoup.removeObject((DataKey) removedKeyList.get(i)); } removedObjectList.clear(); removedKeyList.clear(); int i; Iterator it = updatedObjects.iterator(); - while ( it.hasNext() ) - { - i = objectList.indexOf( it.next() ); - backingSoup.updateObject( - (DataKey) keyList.get(i), objectList.get(i) ); + while (it.hasNext()) { + i = objectList.indexOf(it.next()); + backingSoup.updateObject((DataKey) keyList.get(i), objectList.get(i)); } updatedObjects.clear(); - // notification - setChanged(); - notifyObservers( this ); + // notification + setChanged(); + notifyObservers(this); return true; } - public DataKey getKeyForObject( Object anObject ) - { - int index = indexOfIdenticalObject( anObject ); - if ( index == -1 ) return null; - return (DataKey) keyList.get( index ); - } + public DataKey getKeyForObject(Object anObject) { + int index = indexOfIdenticalObject(anObject); + if (index == -1) + return null; + return (DataKey) keyList.get(index); + } - public Object getObjectForKey( DataKey aKey ) - { - int index = keyList.indexOf( aKey ); - if ( index == -1 ) return null; - return get( index ); - } + public Object getObjectForKey(DataKey aKey) { + int index = keyList.indexOf(aKey); + if (index == -1) + return null; + return get(index); + } // interface Collection - public int size () { return keyList.size(); } - public boolean isEmpty () { return keyList.isEmpty(); } - public void clear () { setKeyList( null ); }; - public int hashCode() { return keyList.hashCode(); }; + public int size() { + return keyList.size(); + } + + public boolean isEmpty() { + return keyList.isEmpty(); + } + + public void clear() { + setKeyList(null); + }; + + public int hashCode() { + return keyList.hashCode(); + }; - public boolean equals (Object o) - { - if ( ! ( o instanceof DefaultDataView ) ) return false; - return keyList.equals( ((DefaultDataView)o).keyList ); + public boolean equals(Object o) { + if (!(o instanceof DefaultDataView)) + return false; + return keyList.equals(((DefaultDataView) o).keyList); } - public boolean contains (Object o) - { + public boolean contains(Object o) { loadAllObjects(); return objectList.contains(o); } - public boolean containsAll (Collection c) - { - loadAllObjects(); - return objectList.containsAll( c ); - } - - public boolean add (Object o) - { - // if previously removed, restore to list - if ( removedObjectList.contains( o ) ) - { - int index = removedObjectList.indexOf( o ); - removedObjectList.remove( index ); - Object key = removedKeyList.remove( index ); - objectList.add( o ); - keyList.add( key ); - - // notification - setChanged(); - notifyObservers( o ); - return true; - } - - // register and add to lists - Object key = backingSoup.registerTemporaryObject( o ); - addedObjectList.add( o ); - addedKeyList.add( key ); - objectList.add( o ); - keyList.add( key ); - - // notification - setChanged(); - notifyObservers( o ); - return true; - } - - public Object remove( int index ) - { - Object result = get( index ); - if ( remove( result ) ) - { - return result; - } - return null; - } - - public boolean remove (Object o) - { - loadAllObjects(); - - int index = objectList.indexOf( o ); - if ( index == -1 ) return false; - - objectList.remove( index ); - Object key = keyList.remove( index ); - - if ( updatedObjects.contains( o ) ) - { - updatedObjects.remove( o ); - } - - // if not previously added, track removal - if ( ! ( removedObjectList.contains( o ) ) ) - { - removedObjectList.add( o ); - removedKeyList.add( key ); - } - - // notification - setChanged(); - notifyObservers( o ); - - return true; - } - - /** - * Set completely replaces the object at the specified - * index with the specified object. The new object is not - * marked as inserted, and the old object is not marked - * as deleted: the new object will be stored in the soup - * with the same key. The old object is returned. - */ - public Object set( int index, Object element ) - { - Object result = objectList.set( index, element ); - update( element ); - return result; - } - - public void add( int index, Object o ) - { - // if previously removed, restore to list - if ( removedObjectList.contains( o ) ) - { - int i = removedObjectList.indexOf( o ); - removedObjectList.remove( i ); - Object key = removedKeyList.remove( i ); - objectList.add( index, o ); - keyList.add( index, key ); - - // notification - setChanged(); - notifyObservers( o ); - return; - } - - // register and add to lists - Object key = backingSoup.registerTemporaryObject( o ); - addedObjectList.add( o ); - addedKeyList.add( key ); - objectList.add( index, o ); - keyList.add( index, key ); - - // notification - setChanged(); - notifyObservers( o ); - } - - public boolean addAll (Collection c) - { - boolean result = true; - Iterator it = c.iterator(); - while ( it.hasNext() ) - { - result = result && add( it.next() ); - } - return result; - } - - public boolean addAll (int index, Collection c) - { - int originalSize = size(); - boolean result = true; - Iterator it = c.iterator(); - while ( it.hasNext() ) - { - add( index, it.next() ); - } - return ( originalSize + c.size() == size() ); - } - - public boolean removeAll (Collection c) - { - boolean result = true; - Iterator it = c.iterator(); - while ( it.hasNext() ) - { - result = result && remove( it.next() ); - } - return result; - } - - public boolean retainAll (Collection c) - { - removeAll( new ArrayList( objectList ) ); - return addAll( c ); - } - - public List subList( int fromIndex, int toIndex ) - { - List result = new LinkedList(); - for ( int i = fromIndex; i < toIndex; i++ ) - { - result.add( get( i ) ); - } - return result; - } - - public Iterator iterator() - { - loadAllObjects(); - return objectList.iterator(); - -/* // uncomment to enable on-demand loading - return new Iterator() - { - int index = 0; - public boolean hasNext() { return ( index + 1 < keyList.size() ); } - public Object next() { - return get( index++ ); } - public void remove() - { - Object o = get( index ); - if ( o != null ) DefaultDataView.this.remove( o ); - } - }; -*/ - } - - public ListIterator listIterator() - { - return new DataViewIterator( this ); - } - - public ListIterator listIterator( int index ) - { - return new DataViewIterator( this, index ); - } - - public Object[] toArray () - { - loadAllObjects(); - return objectList.toArray(); - } - public java.lang.Object[] toArray (Object[] array) - { - loadAllObjects(); - return objectList.toArray( array ); - } - - protected class DataViewIterator implements ListIterator - { - DataView theView; - int currentIndex; - - //TODO: should track current object in addition to index - // to track external changes to the view. (or should be listener) - Object currentObject; - - public DataViewIterator( DataView aView ) - { - this( aView, 0 ); - } - - public DataViewIterator( DataView aView, int index ) - { - theView = aView; - if ( theView.size() > index ) - { - currentIndex = index; - currentObject = theView.get( currentIndex ); - } - else - { - index = -1; - currentObject = null; - } - } - - public void add( Object o ) - { - currentIndex++; - theView.add( currentIndex, o ); - } - - public boolean hasNext() - { - return ( theView.size() > currentIndex + 1 ); - } - - public boolean hasPrevious() - { - return ( currentIndex > -1 ); - } - - public Object next() - { - return theView.get( ++currentIndex ); - } - - public int nextIndex() - { - return currentIndex + 1; - } - - public Object previous() - { - return theView.get( currentIndex-- ); - } - - public int previousIndex() - { - return currentIndex; - } - - public void remove() - { - theView.remove( currentIndex-- ); - } - - public void set( Object o ) - { - theView.set( currentIndex, o ); - } - - } + public boolean containsAll(Collection c) { + loadAllObjects(); + return objectList.containsAll(c); + } + + public boolean add(Object o) { + // if previously removed, restore to list + if (removedObjectList.contains(o)) { + int index = removedObjectList.indexOf(o); + removedObjectList.remove(index); + Object key = removedKeyList.remove(index); + objectList.add(o); + keyList.add(key); + + // notification + setChanged(); + notifyObservers(o); + return true; + } + + // register and add to lists + Object key = backingSoup.registerTemporaryObject(o); + addedObjectList.add(o); + addedKeyList.add(key); + objectList.add(o); + keyList.add(key); + + // notification + setChanged(); + notifyObservers(o); + return true; + } + + public Object remove(int index) { + Object result = get(index); + if (remove(result)) { + return result; + } + return null; + } + + public boolean remove(Object o) { + loadAllObjects(); + + int index = objectList.indexOf(o); + if (index == -1) + return false; + + objectList.remove(index); + Object key = keyList.remove(index); + + if (updatedObjects.contains(o)) { + updatedObjects.remove(o); + } + + // if not previously added, track removal + if (!(removedObjectList.contains(o))) { + removedObjectList.add(o); + removedKeyList.add(key); + } + + // notification + setChanged(); + notifyObservers(o); + + return true; + } + + /** + * Set completely replaces the object at the specified index with the specified + * object. The new object is not marked as inserted, and the old object is not + * marked as deleted: the new object will be stored in the soup with the same + * key. The old object is returned. + */ + public Object set(int index, Object element) { + Object result = objectList.set(index, element); + update(element); + return result; + } + + public void add(int index, Object o) { + // if previously removed, restore to list + if (removedObjectList.contains(o)) { + int i = removedObjectList.indexOf(o); + removedObjectList.remove(i); + Object key = removedKeyList.remove(i); + objectList.add(index, o); + keyList.add(index, key); + + // notification + setChanged(); + notifyObservers(o); + return; + } + + // register and add to lists + Object key = backingSoup.registerTemporaryObject(o); + addedObjectList.add(o); + addedKeyList.add(key); + objectList.add(index, o); + keyList.add(index, key); + + // notification + setChanged(); + notifyObservers(o); + } + + public boolean addAll(Collection c) { + boolean result = true; + Iterator it = c.iterator(); + while (it.hasNext()) { + result = result && add(it.next()); + } + return result; + } + + public boolean addAll(int index, Collection c) { + int originalSize = size(); + boolean result = true; + Iterator it = c.iterator(); + while (it.hasNext()) { + add(index, it.next()); + } + return (originalSize + c.size() == size()); + } + + public boolean removeAll(Collection c) { + boolean result = true; + Iterator it = c.iterator(); + while (it.hasNext()) { + result = result && remove(it.next()); + } + return result; + } + + public boolean retainAll(Collection c) { + removeAll(new ArrayList(objectList)); + return addAll(c); + } + + public List subList(int fromIndex, int toIndex) { + List result = new LinkedList(); + for (int i = fromIndex; i < toIndex; i++) { + result.add(get(i)); + } + return result; + } + + public Iterator iterator() { + loadAllObjects(); + return objectList.iterator(); + + /* + * // uncomment to enable on-demand loading return new Iterator() { int index = + * 0; public boolean hasNext() { return ( index + 1 < keyList.size() ); } public + * Object next() { return get( index++ ); } public void remove() { Object o = + * get( index ); if ( o != null ) DefaultDataView.this.remove( o ); } }; + */ + } + + public ListIterator listIterator() { + return new DataViewIterator(this); + } + + public ListIterator listIterator(int index) { + return new DataViewIterator(this, index); + } + + public Object[] toArray() { + loadAllObjects(); + return objectList.toArray(); + } + + public java.lang.Object[] toArray(Object[] array) { + loadAllObjects(); + return objectList.toArray(array); + } + + protected class DataViewIterator implements ListIterator { + DataView theView; + int currentIndex; + + // TODO: should track current object in addition to index + // to track external changes to the view. (or should be listener) + Object currentObject; + + public DataViewIterator(DataView aView) { + this(aView, 0); + } + + public DataViewIterator(DataView aView, int index) { + theView = aView; + if (theView.size() > index) { + currentIndex = index; + currentObject = theView.get(currentIndex); + } else { + index = -1; + currentObject = null; + } + } + + public void add(Object o) { + currentIndex++; + theView.add(currentIndex, o); + } + + public boolean hasNext() { + return (theView.size() > currentIndex + 1); + } + + public boolean hasPrevious() { + return (currentIndex > -1); + } + + public Object next() { + return theView.get(++currentIndex); + } + + public int nextIndex() { + return currentIndex + 1; + } + + public Object previous() { + return theView.get(currentIndex--); + } + + public int previousIndex() { + return currentIndex; + } + + public void remove() { + theView.remove(currentIndex--); + } + + public void set(Object o) { + theView.set(currentIndex, o); + } + + } } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/15 21:12:41 mpowers - * Added accessors for key throughout the api. This breaks compatibility. - * insertObject now returns the permanent key for the newly created object. - * The old way returned a copy of the object which was an additional read - * that was often ignored. Now you can read it only if you need it. - * Furthermore, there was not other way of getting the permanent key. + * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout + * the api. This breaks compatibility. insertObject now returns the permanent + * key for the newly created object. The old way returned a copy of the object + * which was an additional read that was often ignored. Now you can read it only + * if you need it. Furthermore, there was not other way of getting the permanent + * key. * - * Revision 1.1.1.1 2000/12/21 15:47:14 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:14 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DuplicateList.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DuplicateList.java index ed47b24..9a5458f 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DuplicateList.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/DuplicateList.java @@ -21,29 +21,22 @@ package net.wotonomy.datastore; import java.util.LinkedList; /** -* DuplicateList is a marker class used to store values -* with duplicate keys in the DataIndex TreeMap. -*/ -public class DuplicateList extends LinkedList -{ + * DuplicateList is a marker class used to store values with duplicate keys in + * the DataIndex TreeMap. + */ +public class DuplicateList extends LinkedList { } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:14 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:14 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/FileSoup.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/FileSoup.java index 45cc9d8..d8b8df0 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/FileSoup.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/FileSoup.java @@ -29,82 +29,71 @@ import java.util.Map; import net.wotonomy.foundation.internal.Introspector; -abstract public class FileSoup implements DataSoup -{ - public static final String INDEX_SUFFIX = ".idx"; - public static final String MAP_SUFFIX = ".map"; - private static final String ID_ID = "id"; - private static final String IDENTITY_PROPERTY = "__SELF__"; - - protected DataKey nextUniqueIdentifier; - protected File homeDirectory; - protected Map indices; - - public FileSoup( String aPath ) - { - homeDirectory = new File( aPath ); - - // if specified directory does not exist - if ( ! homeDirectory.exists() ) - { - homeDirectory.mkdirs(); - } - - // if existing path is a file, exit with error - if ( homeDirectory.isDirectory() ) - { - new RuntimeException( "DataStore: Specified directory is a file." ); - } - - // read indices - DataIndex index; - indices = new HashMap(); - String[] files = getHomeDirectory().list(); - for ( int i = 0; i < files.length; i++ ) - { - if ( files[i].endsWith( INDEX_SUFFIX ) ) - { - index = (DataIndex) readFile( files[i] ); - if ( index != null ) - { - indices.put( index.getName(), index ); - } - } - } - - // read unique identifier - nextUniqueIdentifier = (DataKey) readFile( ID_ID ); - if ( nextUniqueIdentifier == null ) - nextUniqueIdentifier = new DataKey( "1" ); - - } - - public File getHomeDirectory() - { - return homeDirectory; - } - - // index management - - public void addIndex( String aName, String aProperty ) - { - DataIndex index = new DefaultDataIndex( aName, aProperty ); - indices.put( index.getName(), index ); - buildIndex( index ); +abstract public class FileSoup implements DataSoup { + public static final String INDEX_SUFFIX = ".idx"; + public static final String MAP_SUFFIX = ".map"; + private static final String ID_ID = "id"; + private static final String IDENTITY_PROPERTY = "__SELF__"; + + protected DataKey nextUniqueIdentifier; + protected File homeDirectory; + protected Map indices; + + public FileSoup(String aPath) { + homeDirectory = new File(aPath); + + // if specified directory does not exist + if (!homeDirectory.exists()) { + homeDirectory.mkdirs(); + } + + // if existing path is a file, exit with error + if (homeDirectory.isDirectory()) { + new RuntimeException("DataStore: Specified directory is a file."); + } + + // read indices + DataIndex index; + indices = new HashMap(); + String[] files = getHomeDirectory().list(); + for (int i = 0; i < files.length; i++) { + if (files[i].endsWith(INDEX_SUFFIX)) { + index = (DataIndex) readFile(files[i]); + if (index != null) { + indices.put(index.getName(), index); + } + } + } + + // read unique identifier + nextUniqueIdentifier = (DataKey) readFile(ID_ID); + if (nextUniqueIdentifier == null) + nextUniqueIdentifier = new DataKey("1"); + + } + + public File getHomeDirectory() { + return homeDirectory; + } + + // index management + + public void addIndex(String aName, String aProperty) { + DataIndex index = new DefaultDataIndex(aName, aProperty); + indices.put(index.getName(), index); + buildIndex(index); writeIndices(); - } - - public void removeIndex( String aName ) - { - indices.remove( aName ); + } + + public void removeIndex(String aName) { + indices.remove(aName); writeIndices(); - } + } + + public Collection getAllIndices() { + return indices.values(); + } - public Collection getAllIndices() - { - return indices.values(); - } - // public void addIndex( String aName, Index anIndex ) {} // public void removeIndex( String aName ) {} // public void addTaggedObject( String aKey, Serializable anObject ) @@ -112,357 +101,315 @@ abstract public class FileSoup implements DataSoup // public void removeTaggedObject( String aKey ); // public void setMetaData( // String aMetaProperty, Serializable aValue, Serializable anObject ); - - protected void buildIndex( DataIndex anIndex ) - { + + protected void buildIndex(DataIndex anIndex) { //System.out.print( "FileSoup.buildIndex: " + anIndex.getName() + " : " ); -long millis = System.currentTimeMillis(); + long millis = System.currentTimeMillis(); - anIndex.clear(); + anIndex.clear(); - int count = 0; + int count = 0; DataKey key; - Object o; - Object value; + Object o; + Object value; String property = anIndex.getProperty(); - - String[] files = getHomeDirectory().list(); - for ( int i = 0; i < files.length; i++ ) - { - if ( ( ! files[i].equals( ID_ID.toString() ) - && ( ! files[i].endsWith( INDEX_SUFFIX ) ) ) ) - { - key = new DataKey( files[i] ); - o = getObjectByKey( key ); - value = getValueFromObject( o, property ); - anIndex.addObject( key, value ); - count++; - } - } - + + String[] files = getHomeDirectory().list(); + for (int i = 0; i < files.length; i++) { + if ((!files[i].equals(ID_ID.toString()) && (!files[i].endsWith(INDEX_SUFFIX)))) { + key = new DataKey(files[i]); + o = getObjectByKey(key); + value = getValueFromObject(o, property); + anIndex.addObject(key, value); + count++; + } + } + //System.out.print( count + " objects: " ); //System.out.println( System.currentTimeMillis() - millis + " milliseconds" ); - } - protected void writeIndices() - { - DataIndex index; - Iterator it = getAllIndices().iterator(); - while ( it.hasNext() ) - { - index = (DataIndex) it.next(); - writeFile( index.getName() + INDEX_SUFFIX, index ); - } - } + } + + protected void writeIndices() { + DataIndex index; + Iterator it = getAllIndices().iterator(); + while (it.hasNext()) { + index = (DataIndex) it.next(); + writeFile(index.getName() + INDEX_SUFFIX, index); + } + } // object management // this implementation currently uses up a valid key increment - public DataKey registerTemporaryObject( Object anObject ) - { + public DataKey registerTemporaryObject(Object anObject) { DataKey id = getNextKey(); - - if ( anObject instanceof UniquelyIdentifiable ) - { - ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id ); + + if (anObject instanceof UniquelyIdentifiable) { + ((UniquelyIdentifiable) anObject).setUniqueIdentifier(id); } - + return id; } - - /** - * Adds the specified object to the soup and returns the key - * for the new object by which it may be subsequently retrieved. - * Null indicates an error, probably due to serialization. - */ - public DataKey addObject( Object anObject ) - { - DataKey id = getNextKey(); - - if ( anObject instanceof UniquelyIdentifiable ) - { // set id if necessary - ((UniquelyIdentifiable)anObject).setUniqueIdentifier( id ); - } - - writeFile( id.toString(), anObject ); - - // update indices + + /** + * Adds the specified object to the soup and returns the key for the new object + * by which it may be subsequently retrieved. Null indicates an error, probably + * due to serialization. + */ + public DataKey addObject(Object anObject) { + DataKey id = getNextKey(); + + if (anObject instanceof UniquelyIdentifiable) { // set id if necessary + ((UniquelyIdentifiable) anObject).setUniqueIdentifier(id); + } + + writeFile(id.toString(), anObject); + + // update indices DataIndex index; Iterator it = indices.values().iterator(); - while ( it.hasNext() ) - { - index = (DataIndex)it.next(); - index.addObject( id, - getValueFromObject( anObject, index.getProperty() ) ); + while (it.hasNext()) { + index = (DataIndex) it.next(); + index.addObject(id, getValueFromObject(anObject, index.getProperty())); } - + writeIndices(); return id; - } - - /** - * Removes the specified object from the soup and returns - * the removed object as read from the soup (which is the - * original copy of the object). Null indicates object not found. - */ - public Object removeObject( DataKey aKey ) - { - Object existing = getObjectByKey( aKey ); - if ( existing != null ) - { - if ( ! deleteFile( aKey.toString() ) ) - { - existing = null; // return error - } - else - { - // update indices - DataIndex index; - Iterator it = indices.values().iterator(); - while ( it.hasNext() ) - { - index = (DataIndex)it.next(); - index.removeObject( aKey, - getValueFromObject( existing, index.getProperty() ) ); - } - - writeIndices(); + } + + /** + * Removes the specified object from the soup and returns the removed object as + * read from the soup (which is the original copy of the object). Null indicates + * object not found. + */ + public Object removeObject(DataKey aKey) { + Object existing = getObjectByKey(aKey); + if (existing != null) { + if (!deleteFile(aKey.toString())) { + existing = null; // return error + } else { + // update indices + DataIndex index; + Iterator it = indices.values().iterator(); + while (it.hasNext()) { + index = (DataIndex) it.next(); + index.removeObject(aKey, getValueFromObject(existing, index.getProperty())); + } + + writeIndices(); } - } - - return existing; - } - - /** - * Updates the specified object and returns the object - * as updated. Null indicates an error writing the object. - */ - public Object updateObject( DataKey aKey, Object updatedObject ) - { - Object existing = getObjectByKey( aKey ); - if ( existing == null ) - { - System.err.println( "FileSoup.updateObject: " + - "existing object could not be found with id: " + aKey ); - return null; - } - - Object result = null; - if ( updatedObject instanceof UniquelyIdentifiable ) - { + } + + return existing; + } + + /** + * Updates the specified object and returns the object as updated. Null + * indicates an error writing the object. + */ + public Object updateObject(DataKey aKey, Object updatedObject) { + Object existing = getObjectByKey(aKey); + if (existing == null) { + System.err.println("FileSoup.updateObject: " + "existing object could not be found with id: " + aKey); + return null; + } + + Object result = null; + if (updatedObject instanceof UniquelyIdentifiable) { // update key if changed - ((UniquelyIdentifiable)updatedObject).setUniqueIdentifier( aKey ); - } - - if ( writeFile( aKey.toString(), updatedObject ) ) - { - result = updatedObject; - - // update indices - DataIndex index; - Iterator it = indices.values().iterator(); - while ( it.hasNext() ) - { - index = (DataIndex)it.next(); - index.updateObject( aKey, - getValueFromObject( existing, index.getProperty() ), - getValueFromObject( updatedObject, index.getProperty() ) ); - } - - writeIndices(); - } - + ((UniquelyIdentifiable) updatedObject).setUniqueIdentifier(aKey); + } + + if (writeFile(aKey.toString(), updatedObject)) { + result = updatedObject; + + // update indices + DataIndex index; + Iterator it = indices.values().iterator(); + while (it.hasNext()) { + index = (DataIndex) it.next(); + index.updateObject(aKey, getValueFromObject(existing, index.getProperty()), + getValueFromObject(updatedObject, index.getProperty())); + } + + writeIndices(); + } + //System.out.println( "FileSoup.updateObject: " + updatedObject + " -> " + result ); - return getObjectByKey( aKey ); - } - - protected DataKey getNextKey() - { + return getObjectByKey(aKey); + } + + protected DataKey getNextKey() { DataKey id = (DataKey) nextUniqueIdentifier.clone(); - // while ( id isn't yet in use by the soup ) increment(); + // while ( id isn't yet in use by the soup ) increment(); nextUniqueIdentifier.increment(); - writeFile( ID_ID.toString(), nextUniqueIdentifier ); - return id; - } - - protected DataKey getNextTempKey() - { - return getNextKey(); - } - - /** - * Gets object from data store whose identifier is equal - * to the specified object. - */ - public Object getObjectByKey( DataKey aKey ) - { - return readFile( aKey.toString() ); - } - - // queries - + writeFile(ID_ID.toString(), nextUniqueIdentifier); + return id; + } + + protected DataKey getNextTempKey() { + return getNextKey(); + } + /** - * Returns an empty data view, suitable for creating - * new entries in the soup. - * @return A DataView containing no entries. - */ - public DataView createView() - { - return new DefaultDataView( this, new LinkedList() ); + * Gets object from data store whose identifier is equal to the specified + * object. + */ + public Object getObjectByKey(DataKey aKey) { + return readFile(aKey.toString()); } - - /** - * Queries by the specified pre-generated index, if it exists. - * Otherwise, falls through to queryByProperty. - */ - public DataView queryByIndex( - String anIndexName, Object beginKey, Object endKey ) - { - DataIndex index = (DataIndex) indices.get( anIndexName ); - - if ( index == null ) - { - return queryByProperty( anIndexName, beginKey, endKey ); - } - - return queryByKeys( index.query( beginKey, endKey ) ); - } - - /** - * Generates an index based on the specified property - * and then executes the query. - */ - public DataView queryByProperty( - String aPropertyName, Object beginKey, Object endKey ) - { - if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY; - DataIndex index = new DefaultDataIndex( "temporary", aPropertyName ); - buildIndex( index ); - return queryByKeys( index.query( beginKey, endKey ) ); - } - - /** - * Generates an index based on the specified property - * and then executes the query. - */ - public DataView queryObjects( Object beginKey, Object endKey ) - { - return queryByProperty( IDENTITY_PROPERTY, beginKey, endKey ); - } - - /** - * Returns a view containing the objects for the specified keys. - */ - public DataView queryByKeys( Collection aKeyList ) - { - return new DefaultDataView( this, aKeyList ); - } - - /** - * As queryByIndex, but with objects returned in reverse order. - * @param anIndexName The index to be queried. - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryByIndex( - String anIndexName, Object beginKey, Object endKey ) - { - DataIndex index = (DataIndex) indices.get( anIndexName ); - - if ( index == null ) - { - return reverseQueryByProperty( anIndexName, beginKey, endKey ); - } - - List items = index.query( endKey, beginKey ); - Collections.reverse( items ); - return queryByKeys( items ); + + // queries + + /** + * Returns an empty data view, suitable for creating new entries in the soup. + * + * @return A DataView containing no entries. + */ + public DataView createView() { + return new DefaultDataView(this, new LinkedList()); } - - /** - * As queryByProperty, but with objects returned in reverse order. - * @param aPropertyName The property to be queried. If null, - * will query the objects directly with queryObjects(). - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryByProperty( - String aPropertyName, Object beginKey, Object endKey ) - { - if ( aPropertyName == null ) aPropertyName = IDENTITY_PROPERTY; - DataIndex index = new DefaultDataIndex( "temporary", aPropertyName ); - buildIndex( index ); - List items = index.query( endKey, beginKey ); - Collections.reverse( items ); - return queryByKeys( items ); + + /** + * Queries by the specified pre-generated index, if it exists. Otherwise, falls + * through to queryByProperty. + */ + public DataView queryByIndex(String anIndexName, Object beginKey, Object endKey) { + DataIndex index = (DataIndex) indices.get(anIndexName); + + if (index == null) { + return queryByProperty(anIndexName, beginKey, endKey); + } + + return queryByKeys(index.query(beginKey, endKey)); } - /** - * As queryObjects, but with objects returned in reverse order. - * @param beginValue The beginning value, or null for all values - * up to an including the end key. - * @param endValue The ending value, or null for all values - * since and including the begin key. - * @return A DataView containing the query results, or null - * for invalid query parameters. - */ - public DataView reverseQueryObjects( Object beginKey, Object endKey ) - { - return queryByProperty( IDENTITY_PROPERTY, beginKey, endKey ); - } - - public Object getValueFromObject( Object anObject, String aProperty ) - { - if ( IDENTITY_PROPERTY.equals( aProperty ) ) return anObject; - return Introspector.getValueForObject( anObject, aProperty ); + /** + * Generates an index based on the specified property and then executes the + * query. + */ + public DataView queryByProperty(String aPropertyName, Object beginKey, Object endKey) { + if (aPropertyName == null) + aPropertyName = IDENTITY_PROPERTY; + DataIndex index = new DefaultDataIndex("temporary", aPropertyName); + buildIndex(index); + return queryByKeys(index.query(beginKey, endKey)); } - - // file access methods - - abstract protected boolean writeFile( String name, Object anObject ); - abstract protected Object readFile( String name ); - abstract protected boolean deleteFile( String name ); + + /** + * Generates an index based on the specified property and then executes the + * query. + */ + public DataView queryObjects(Object beginKey, Object endKey) { + return queryByProperty(IDENTITY_PROPERTY, beginKey, endKey); + } + + /** + * Returns a view containing the objects for the specified keys. + */ + public DataView queryByKeys(Collection aKeyList) { + return new DefaultDataView(this, aKeyList); + } + + /** + * As queryByIndex, but with objects returned in reverse order. + * + * @param anIndexName The index to be queried. + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryByIndex(String anIndexName, Object beginKey, Object endKey) { + DataIndex index = (DataIndex) indices.get(anIndexName); + + if (index == null) { + return reverseQueryByProperty(anIndexName, beginKey, endKey); + } + + List items = index.query(endKey, beginKey); + Collections.reverse(items); + return queryByKeys(items); + } + + /** + * As queryByProperty, but with objects returned in reverse order. + * + * @param aPropertyName The property to be queried. If null, will query the + * objects directly with queryObjects(). + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryByProperty(String aPropertyName, Object beginKey, Object endKey) { + if (aPropertyName == null) + aPropertyName = IDENTITY_PROPERTY; + DataIndex index = new DefaultDataIndex("temporary", aPropertyName); + buildIndex(index); + List items = index.query(endKey, beginKey); + Collections.reverse(items); + return queryByKeys(items); + } + + /** + * As queryObjects, but with objects returned in reverse order. + * + * @param beginValue The beginning value, or null for all values up to an + * including the end key. + * @param endValue The ending value, or null for all values since and + * including the begin key. + * @return A DataView containing the query results, or null for invalid query + * parameters. + */ + public DataView reverseQueryObjects(Object beginKey, Object endKey) { + return queryByProperty(IDENTITY_PROPERTY, beginKey, endKey); + } + + public Object getValueFromObject(Object anObject, String aProperty) { + if (IDENTITY_PROPERTY.equals(aProperty)) + return anObject; + return Introspector.getValueForObject(anObject, aProperty); + } + + // file access methods + + abstract protected boolean writeFile(String name, Object anObject); + + abstract protected Object readFile(String name); + + abstract protected boolean deleteFile(String name); } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2003/08/14 19:29:38 chochos - * minor cleanup (imports, static method calls, etc) + * Revision 1.4 2003/08/14 19:29:38 chochos minor cleanup (imports, static + * method calls, etc) * - * Revision 1.3 2001/03/05 22:12:11 mpowers - * Created the control package for a datastore-specific implementation - * of EOObjectStore. + * Revision 1.3 2001/03/05 22:12:11 mpowers Created the control package for a + * datastore-specific implementation of EOObjectStore. * - * Revision 1.2 2001/02/15 21:12:41 mpowers - * Added accessors for key throughout the api. This breaks compatibility. - * insertObject now returns the permanent key for the newly created object. - * The old way returned a copy of the object which was an additional read - * that was often ignored. Now you can read it only if you need it. - * Furthermore, there was not other way of getting the permanent key. + * Revision 1.2 2001/02/15 21:12:41 mpowers Added accessors for key throughout + * the api. This breaks compatibility. insertObject now returns the permanent + * key for the newly created object. The old way returned a copy of the object + * which was an additional read that was often ignored. Now you can read it only + * if you need it. Furthermore, there was not other way of getting the permanent + * key. * - * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:36 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:36 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/SerializedFileSoup.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/SerializedFileSoup.java index e466c78..804608f 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/SerializedFileSoup.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/SerializedFileSoup.java @@ -25,96 +25,71 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -public class SerializedFileSoup extends FileSoup -{ - public SerializedFileSoup( String aPath ) - { - super( aPath ); - } +public class SerializedFileSoup extends FileSoup { + public SerializedFileSoup(String aPath) { + super(aPath); + } + + // file access methods + + protected boolean writeFile(String name, Object anObject) { + try { + File f = new File(getHomeDirectory(), name); + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); + oos.writeObject(anObject); + oos.flush(); + oos.close(); + return true; + } catch (Exception exc) { + System.err.println("SerializedFileSoup.writeFile: " + exc); + } - // file access methods - - protected boolean writeFile( String name, Object anObject ) - { - try - { - File f = new File( getHomeDirectory(), name ); - ObjectOutputStream oos = new ObjectOutputStream( - new FileOutputStream( f ) ); - oos.writeObject( anObject ); - oos.flush(); - oos.close(); - return true; - } - catch ( Exception exc ) - { - System.err.println( "SerializedFileSoup.writeFile: " + exc ); + return false; } - - return false; - } - - protected Object readFile( String name ) - { - Object result = null; - try - { - File f = new File( getHomeDirectory(), name ); - ObjectInputStream ois = new ObjectInputStream( - new FileInputStream( f ) ); - result = ois.readObject(); - ois.close(); - } - catch ( FileNotFoundException exc ) - { + protected Object readFile(String name) { + Object result = null; + + try { + File f = new File(getHomeDirectory(), name); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f)); + result = ois.readObject(); + ois.close(); + } catch (FileNotFoundException exc) { result = null; + } catch (Exception exc) { + System.err.println("SerializedFileSoup.readFile: " + exc); + exc.printStackTrace(); } - catch ( Exception exc ) - { - System.err.println( "SerializedFileSoup.readFile: " + exc ); - exc.printStackTrace(); - } - return result; - } - - protected boolean deleteFile( String name ) - { - try - { - File f = new File( getHomeDirectory(), name ); - if ( f.exists() ) - { - f.delete(); + return result; + } + + protected boolean deleteFile(String name) { + try { + File f = new File(getHomeDirectory(), name); + if (f.exists()) { + f.delete(); return true; - } - } - catch ( Exception exc ) - { - System.err.println( "SerializedFileSoup.deleteFile: " + exc ); - } + } + } catch (Exception exc) { + System.err.println("SerializedFileSoup.deleteFile: " + exc); + } + + return false; + } - return false; - } - } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:37 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:37 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/UniquelyIdentifiable.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/UniquelyIdentifiable.java index 104932e..2257fac 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/UniquelyIdentifiable.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/UniquelyIdentifiable.java @@ -18,23 +18,19 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.datastore; -public interface UniquelyIdentifiable -{ - Object getUniqueIdentifier(); - void setUniqueIdentifier( Object id ); +public interface UniquelyIdentifiable { + Object getUniqueIdentifier(); + + void setUniqueIdentifier(Object id); } /* - * $Log$ - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:20 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:37 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:37 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/XMLFileSoup.java b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/XMLFileSoup.java index 94d85fb..cb22f7a 100644 --- a/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/XMLFileSoup.java +++ b/projects/net.wotonomy.datastore/src/main/java/net/wotonomy/datastore/XMLFileSoup.java @@ -27,104 +27,80 @@ import java.net.MalformedURLException; import net.wotonomy.web.xml.XMLRPCDecoder; import net.wotonomy.web.xml.XMLRPCEncoder; -public class XMLFileSoup extends FileSoup -{ - public XMLFileSoup( String aPath ) - { - super( aPath ); - } +public class XMLFileSoup extends FileSoup { + public XMLFileSoup(String aPath) { + super(aPath); + } - // file access methods - - protected boolean writeFile( String name, Object anObject ) - { + // file access methods + + protected boolean writeFile(String name, Object anObject) { //System.out.print( "writeFile: " + name + "..." ); - try - { - File f = new File( getHomeDirectory(), name ); - FileOutputStream fos = new FileOutputStream( f ); - XMLRPCEncoder encoder = new XMLRPCEncoder(); - encoder.encode( anObject, fos ); - fos.flush(); - fos.close(); - } - catch ( Exception exc ) - { - System.err.println( "XMLFileSoup.writeFile: " + exc ); - return false; - } + try { + File f = new File(getHomeDirectory(), name); + FileOutputStream fos = new FileOutputStream(f); + XMLRPCEncoder encoder = new XMLRPCEncoder(); + encoder.encode(anObject, fos); + fos.flush(); + fos.close(); + } catch (Exception exc) { + System.err.println("XMLFileSoup.writeFile: " + exc); + return false; + } //System.out.println( "done." ); - return true; - } - - protected Object readFile( String name ) - { + return true; + } + + protected Object readFile(String name) { //System.out.print( "readFile: " + name + "..." ); - Object result = null; + Object result = null; - try - { - File f = new File( getHomeDirectory(), name ); - FileInputStream fis = new FileInputStream( f ); - XMLRPCDecoder decoder = new XMLRPCDecoder(); - result = decoder.decode( fis, f.getAbsolutePath(), f.toURL() ); - fis.close(); - } - catch ( MalformedURLException exc ) - { + try { + File f = new File(getHomeDirectory(), name); + FileInputStream fis = new FileInputStream(f); + XMLRPCDecoder decoder = new XMLRPCDecoder(); + result = decoder.decode(fis, f.getAbsolutePath(), f.toURL()); + fis.close(); + } catch (MalformedURLException exc) { result = null; - } - catch ( IOException exc ) - { + } catch (IOException exc) { result = null; } //System.out.println( "done." ); - return result; - } - - protected boolean deleteFile( String name ) - { - try - { - File f = new File( getHomeDirectory(), name ); - if ( f.exists() ) - { - f.delete(); + return result; + } + + protected boolean deleteFile(String name) { + try { + File f = new File(getHomeDirectory(), name); + if (f.exists()) { + f.delete(); return true; - } - } - catch ( Exception exc ) - { - System.err.println( "XMLFileSoup.deleteFile: " + exc ); - } + } + } catch (Exception exc) { + System.err.println("XMLFileSoup.deleteFile: " + exc); + } + + return false; + } - return false; - } - } /* - * $Log$ - * Revision 1.2 2006/02/19 16:26:19 cgruber - * Move non-unit-test code to tests project - * Fix up code to work with proper imports - * Fix maven dependencies. + * $Log$ Revision 1.2 2006/02/19 16:26:19 cgruber Move non-unit-test code to + * tests project Fix up code to work with proper imports Fix maven dependencies. * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2003/08/14 19:29:38 chochos - * minor cleanup (imports, static method calls, etc) + * Revision 1.3 2003/08/14 19:29:38 chochos minor cleanup (imports, static + * method calls, etc) * - * Revision 1.2 2001/02/07 19:26:28 mpowers - * XML classes are in new package. + * Revision 1.2 2001/02/07 19:26:28 mpowers XML classes are in new package. * - * Revision 1.1.1.1 2000/12/21 15:47:23 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:23 mpowers Contributing wotonomy. * - * Revision 1.6 2000/12/20 16:25:37 michael - * Added log to all files. + * Revision 1.6 2000/12/20 16:25:37 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java index 491722c..e79c206 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java @@ -29,633 +29,639 @@ import java.util.List; import java.util.ListIterator; /** -* NSArray is an unmodifiable List. -* Calling the mutator methods of the List interface (add, addAll, -* set, etc.) on an instance of NSArray will throw an Unsupported -* Operation exception: use a NSMutableArray instead. This is to -* simulate Objective-C's pattern of exposing mutator methods only -* on mutable subinterface, which is wonderful for communicating -* via interface the contract on returned collections (whether you -* may modify return values) as well as implementing array faults. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 929 $ -*/ -public class NSArray implements List, Serializable -{ - /** - * Actual list that backs this instance. - */ - List list; - - /** - * Return value when array index is not found. - */ - public static final int NotFound = -1; + * NSArray is an unmodifiable List. Calling the mutator methods of the List + * interface (add, addAll, set, etc.) on an instance of NSArray will throw an + * Unsupported Operation exception: use a NSMutableArray instead. This is to + * simulate Objective-C's pattern of exposing mutator methods only on mutable + * subinterface, which is wonderful for communicating via interface the contract + * on returned collections (whether you may modify return values) as well as + * implementing array faults. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 929 $ + */ +public class NSArray implements List, Serializable { + /** + * Actual list that backs this instance. + */ + List list; /** - * A constant representing an empty array. - */ - public static final NSArray EmptyArray = new NSArray(); - - /** - * Returns an NSArray backed by the specified List. - * This is useful to "protect" an internal representation - * that is returned by a method of return type NSArray. - */ - public static NSArray arrayBackedByList( List aList ) - { - return new NSArray( aList, null ); - } - - /** - * A constructor that uses the provided list as the backing - * list object. This is unlike ArrayList and other - * java.util.Collection types insofar as that API requires - * that the provided Collection be copied into the newly constructed - * Collection. - * - * TODO: See if this signature can be reasonably changed, as having a no-op parameter is a little counter-intuitive. - * @param aList A list that the caller wishes to become the backing list for the NSArray. - * @param ignored This parameter is entirely ignored, and is only there to distinguish the API. - */ - NSArray( List aList, Object ignored ) // differentiates - { - list = aList; - } - - /** - * Constructor with a size hint, used by NSMutableArray. - */ - NSArray( int aSize ) - { - list = new ArrayList( aSize ); - } - - /** - * Default constructor returns an empty array. - */ - public NSArray () - { - list = new ArrayList(); - } + * Return value when array index is not found. + */ + public static final int NotFound = -1; /** - * Produces an array containing only the specified object. - */ - public NSArray (Object anObject) - { - this(); - list.add( anObject ); - } + * A constant representing an empty array. + */ + public static final NSArray EmptyArray = new NSArray(); /** - * Produces an array containing the specified objects. - */ - public NSArray (Object[] anArray) - { - this(); - for ( int i = 0; i < anArray.length; i++ ) - { - list.add( anArray[i] ); - } - } + * Returns an NSArray backed by the specified List. This is useful to "protect" + * an internal representation that is returned by a method of return type + * NSArray. + */ + public static NSArray arrayBackedByList(List aList) { + return new NSArray(aList, null); + } /** - * Produces an array containing the objects in the specified collection. - */ - public NSArray (Collection aCollection) - { - this(); - Iterator i = aCollection.iterator(); - while ( i.hasNext() ) list.add( i.next() ); - } + * A constructor that uses the provided list as the backing list object. This is + * unlike ArrayList and other java.util.Collection types insofar as that API + * requires that the provided Collection be copied into the newly constructed + * Collection. + * + * TODO: See if this signature can be reasonably changed, as having a no-op + * parameter is a little counter-intuitive. + * + * @param aList A list that the caller wishes to become the backing list for + * the NSArray. + * @param ignored This parameter is entirely ignored, and is only there to + * distinguish the API. + */ + NSArray(List aList, Object ignored) // differentiates + { + list = aList; + } /** - * Returns the number of items in this array. - */ - public int count () - { - return list.size(); - } + * Constructor with a size hint, used by NSMutableArray. + */ + NSArray(int aSize) { + list = new ArrayList(aSize); + } /** - * Returns an array containing all objects in this array - * plus the specified object. - */ - public NSArray arrayByAddingObject (Object anObject) - { - NSArray result = new NSArray( this ); - result.protectedAdd( anObject ); - return result; - } + * Default constructor returns an empty array. + */ + public NSArray() { + list = new ArrayList(); + } /** - * Returns an array containing all objects in this array - * plus all objects in the specified list. - */ - public NSArray arrayByAddingObjectsFromArray (Collection aCollection) - { - NSArray result = new NSArray( this ); - result.protectedAddAll( aCollection ); - return result; - } + * Produces an array containing only the specified object. + */ + public NSArray(Object anObject) { + this(); + list.add(anObject); + } /** - * Returns a string containing the string representations of - * each element in this array, with each element separated from - * each neighboring element by the specified string. - */ - public String componentsJoinedByString (String separator) - { - StringBuffer buf = new StringBuffer(); - Iterator it = list.iterator(); - if ( it.hasNext() ) - { - buf.append( it.next().toString() ); - } - while ( it.hasNext() ) - { - buf.append( separator ); - buf.append( String.valueOf(it.next()) ); - } - return buf.toString(); - } - - /** - * Returns whether an equivalent object is contained in this array. - */ - public boolean containsObject (Object anObject) - { - return list.contains( anObject ); - } + * Produces an array containing the specified objects. + */ + public NSArray(Object[] anArray) { + this(); + for (int i = 0; i < anArray.length; i++) { + list.add(anArray[i]); + } + } /** - * Returns the first object in this array that is equivalent to - * an object in the specified list, or null if no objects are - * in common. - */ - public Object firstObjectCommonWithArray (Collection aCollection) - { - if ( aCollection == null ) return null; - - Object o; - Iterator it = list.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - if ( aCollection.contains( o ) ) return o; - } - return null; - } - - /** - * Returns whether the specified list contains elements equivalent - * to those in this array in the same order. - */ - public boolean isEqualToArray (List aList) - { - return list.equals( aList ); - } + * Produces an array containing the objects in the specified collection. + */ + public NSArray(Collection aCollection) { + this(); + Iterator i = aCollection.iterator(); + while (i.hasNext()) + list.add(i.next()); + } /** - * Returns the last object in this array, or null if the array is empty. - */ - public Object lastObject () - { - int i; - if ( (i = list.size()) == 0 ) return null; - return list.get( i - 1 ); - } + * Returns the number of items in this array. + */ + public int count() { + return list.size(); + } + + /** + * Returns an array containing all objects in this array plus the specified + * object. + */ + public NSArray arrayByAddingObject(Object anObject) { + NSArray result = new NSArray(this); + result.protectedAdd(anObject); + return result; + } + + /** + * Returns an array containing all objects in this array plus all objects in the + * specified list. + */ + public NSArray arrayByAddingObjectsFromArray(Collection aCollection) { + NSArray result = new NSArray(this); + result.protectedAddAll(aCollection); + return result; + } + + /** + * Returns a string containing the string representations of each element in + * this array, with each element separated from each neighboring element by the + * specified string. + */ + public String componentsJoinedByString(String separator) { + StringBuffer buf = new StringBuffer(); + Iterator it = list.iterator(); + if (it.hasNext()) { + buf.append(it.next().toString()); + } + while (it.hasNext()) { + buf.append(separator); + buf.append(String.valueOf(it.next())); + } + return buf.toString(); + } + + /** + * Returns whether an equivalent object is contained in this array. + */ + public boolean containsObject(Object anObject) { + return list.contains(anObject); + } + + /** + * Returns the first object in this array that is equivalent to an object in the + * specified list, or null if no objects are in common. + */ + public Object firstObjectCommonWithArray(Collection aCollection) { + if (aCollection == null) + return null; + + Object o; + Iterator it = list.iterator(); + while (it.hasNext()) { + o = it.next(); + if (aCollection.contains(o)) + return o; + } + return null; + } + + /** + * Returns whether the specified list contains elements equivalent to those in + * this array in the same order. + */ + public boolean isEqualToArray(List aList) { + return list.equals(aList); + } + + /** + * Returns the last object in this array, or null if the array is empty. + */ + public Object lastObject() { + int i; + if ((i = list.size()) == 0) + return null; + return list.get(i - 1); + } /** * */ -/* - public NSArray sortedArrayUsingSelector (NSSelector); -*/ + /* + * public NSArray sortedArrayUsingSelector (NSSelector); + */ /** - * Returns an array comprised of only those elements whose - * indices fall within the specified range. - */ - public NSArray subarrayWithRange (NSRange aRange) - { - //TODO: Test this logic. - NSArray result = new NSArray(); - if ( aRange == null ) return result; - - int loc = aRange.location(); - int max = aRange.maxRange(); - int count = count(); - for ( int i = loc; i <= max && i < count; i++ ) - { - result.protectedAdd( list.get( i ) ); - } - return result; - } - - /** - * Returns an enumeration over the the elements of the array. - */ - public Enumeration objectEnumerator () - { - //TODO: Test this logic. - return new Enumeration() - { - Iterator it = NSArray.this.iterator(); - public boolean hasMoreElements() - { + * Returns an array comprised of only those elements whose indices fall within + * the specified range. + */ + public NSArray subarrayWithRange(NSRange aRange) { + // TODO: Test this logic. + NSArray result = new NSArray(); + if (aRange == null) + return result; + + int loc = aRange.location(); + int max = aRange.maxRange(); + int count = count(); + for (int i = loc; i <= max && i < count; i++) { + result.protectedAdd(list.get(i)); + } + return result; + } + + /** + * Returns an enumeration over the the elements of the array. + */ + public Enumeration objectEnumerator() { + // TODO: Test this logic. + return new Enumeration() { + Iterator it = NSArray.this.iterator(); + + public boolean hasMoreElements() { return it.hasNext(); - } - public Object nextElement() - { - return it.next(); - } - }; + } + + public Object nextElement() { + return it.next(); + } + }; } /** - * Returns an enumeration over the elements of the array in reverse order. - */ - public java.util.Enumeration reverseObjectEnumerator () - { - return new java.util.Enumeration() - { - ListIterator it = null; - public ListIterator getIterator() - { - if ( it == null ) - { - it = NSArray.this.listIterator(); - // zoom to end - while ( it.hasNext() ) it.next(); + * Returns an enumeration over the elements of the array in reverse order. + */ + public java.util.Enumeration reverseObjectEnumerator() { + return new java.util.Enumeration() { + ListIterator it = null; + + public ListIterator getIterator() { + if (it == null) { + it = NSArray.this.listIterator(); + // zoom to end + while (it.hasNext()) + it.next(); } return it; - } - public boolean hasMoreElements() - { + } + + public boolean hasMoreElements() { return getIterator().hasPrevious(); - } - public Object nextElement() - { - return getIterator().previous(); - } - }; + } + + public Object nextElement() { + return getIterator().previous(); + } + }; } /** - * Copies the elements of this array into the specified object array - * as the array's capacity permits. - */ - public void getObjects (Object[] anArray) - { - getObjects(anArray,null); - } - - /** - * Copies the elements of this array that fall within the specified range - * into the specified object array as the array's capacity permits. This - * method must not overflow, even in the face of a null range, an over or - * under-sized array, or a bad range. It may underflow and fail to - * entirely populate the array, if the array is larger than the data to - * be copied. - * - * TODO: Check whether in WebObjects the range supposed to be measured against the parameter or the NSArray itself??? -ceg - * - * @param anArray An object array to be filled by this method. - * @param range An NSRange object representing the range of data in the NSArray to be copied. - */ - public void getObjects (Object[] array, NSRange range) - { - if ( array == null ) return; - if ( range == null) range = new NSRange(0,array.length); - int limit = Math.min(Math.min(array.length,range.length()),(count()-range.location())); - for ( int i = 0; i < limit ; i++ ) { - //anArray[ i-aRange.location() ] = objectAtIndex( i ); - array[ i ] = objectAtIndex( range.location() + i ); - } - } - - /** - * Returns the index of the first object in the array equivalent - * to the specified object. Returns NotFound if the item is not found. - */ - public int indexOfObject (Object anObject) - { - int result = list.indexOf( anObject ); - if ( result == -1 ) return NotFound; // in case this changes - return result; - } + * Copies the elements of this array into the specified object array as the + * array's capacity permits. + */ + public void getObjects(Object[] anArray) { + getObjects(anArray, null); + } /** - * Returns the index of the first object in the array - * within the specified range equivalent to the specified object. - * Returns NotFound if the item is not found. - */ - public int indexOfObject (Object anObject, NSRange aRange) - { - if ( ( anObject == null ) || ( aRange == null ) ) return NotFound; - - int loc = aRange.location(); - int max = aRange.maxRange(); - for ( int i = loc; i < max; i++ ) - { - if ( anObject.equals( list.get(i) ) ) - { - return i; - } - } - return NotFound; - } - - /** - * Returns the index of the specified object if it exists - * in the array, comparing by reference. - * Returns NotFound if the item is not found. - */ - public int indexOfIdenticalObject (Object anObject) - { - int size = list.size(); - for ( int i = 0; i < size; i++ ) - { - if ( anObject == list.get(i) ) - { - return i; - } - } - return NotFound; - } - - /** - * Returns the index of the first object in the array - * within the specified range equivalent to the specified object. - */ - public int indexOfIdenticalObject (Object anObject, NSRange aRange) - { - if ( aRange == null ) return NotFound; - - int loc = aRange.location(); - int max = aRange.maxRange(); - for ( int i = loc; i < max; i++ ) - { - if ( anObject == list.get(i) ) - { - return i; - } - } - return NotFound; - } - - /** - * Returns the object at the specified index. Throws an - * IndexOutOfRange exception if the index is out of range. - */ - public Object objectAtIndex (int anIndex) - { - return list.get( anIndex ); - } + * Copies the elements of this array that fall within the specified range into + * the specified object array as the array's capacity permits. This method must + * not overflow, even in the face of a null range, an over or under-sized array, + * or a bad range. It may underflow and fail to entirely populate the array, if + * the array is larger than the data to be copied. + * + * TODO: Check whether in WebObjects the range supposed to be measured against + * the parameter or the NSArray itself??? -ceg + * + * @param anArray An object array to be filled by this method. + * @param range An NSRange object representing the range of data in the + * NSArray to be copied. + */ + public void getObjects(Object[] array, NSRange range) { + if (array == null) + return; + if (range == null) + range = new NSRange(0, array.length); + int limit = Math.min(Math.min(array.length, range.length()), (count() - range.location())); + for (int i = 0; i < limit; i++) { + // anArray[ i-aRange.location() ] = objectAtIndex( i ); + array[i] = objectAtIndex(range.location() + i); + } + } /** - * Returns an array consisting of strings within the specified string - * as delimited by the specified separator characters. - */ - public static NSArray componentsSeparatedByString - (String aString, String aSeparator) - { - NSArray result = new NSArray(); - if ( aString == null ) return result; - if ( aSeparator == null ) return new NSArray( aString ); - - //FIXME: The spec probably considers the whole - // string as a separator, unlike string tokenizer. - java.util.StringTokenizer tokens = - new java.util.StringTokenizer( aString, aSeparator ); - while ( tokens.hasMoreTokens() ) - { - result.protectedAdd( tokens.nextToken() ); - } - - return result; - } - - public Object clone() - { - return new NSArray( list ); - } - - public NSArray immutableClone() - { - return this; - } - - public NSMutableArray mutableClone() - { - return new NSMutableArray( this ); - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_ARRAY]); - for (int i = 0; i < count(); i++) { - Object x = objectAtIndex(i); - buf.append(NSPropertyListSerialization.stringForPropertyList(x)); - if (i < count() - 1) - buf.append(", "); - } - buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_ARRAY]); - return buf.toString(); - } - - // interface List: accessors - - public boolean contains(Object o) { return list.contains(o); } - public boolean containsAll(Collection c) { return list.containsAll(c); } - public boolean equals(Object o) { return list.equals(o); } - public Object get(int index) { return list.get(index); } - public int hashCode() { - int code = 19; - code *= getClass().hashCode(); - code *= list.hashCode(); - return code; - } - public int indexOf(Object o) { return list.indexOf(o); } - public boolean isEmpty() { return list.isEmpty(); } - public int lastIndexOf(Object o) { return list.lastIndexOf(o); } - public int size() { return list.size(); } - public Object[] toArray() { return list.toArray(); } - public Object[] toArray(Object[] a) { return list.toArray(a); } - - // interface List: mutators - - - public void add(int index, Object element) - { - this.list.add(index,element); - } - - public boolean add(Object o) - { + * Returns the index of the first object in the array equivalent to the + * specified object. Returns NotFound if the item is not found. + */ + public int indexOfObject(Object anObject) { + int result = list.indexOf(anObject); + if (result == -1) + return NotFound; // in case this changes + return result; + } + + /** + * Returns the index of the first object in the array within the specified range + * equivalent to the specified object. Returns NotFound if the item is not + * found. + */ + public int indexOfObject(Object anObject, NSRange aRange) { + if ((anObject == null) || (aRange == null)) + return NotFound; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for (int i = loc; i < max; i++) { + if (anObject.equals(list.get(i))) { + return i; + } + } + return NotFound; + } + + /** + * Returns the index of the specified object if it exists in the array, + * comparing by reference. Returns NotFound if the item is not found. + */ + public int indexOfIdenticalObject(Object anObject) { + int size = list.size(); + for (int i = 0; i < size; i++) { + if (anObject == list.get(i)) { + return i; + } + } + return NotFound; + } + + /** + * Returns the index of the first object in the array within the specified range + * equivalent to the specified object. + */ + public int indexOfIdenticalObject(Object anObject, NSRange aRange) { + if (aRange == null) + return NotFound; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for (int i = loc; i < max; i++) { + if (anObject == list.get(i)) { + return i; + } + } + return NotFound; + } + + /** + * Returns the object at the specified index. Throws an IndexOutOfRange + * exception if the index is out of range. + */ + public Object objectAtIndex(int anIndex) { + return list.get(anIndex); + } + + /** + * Returns an array consisting of strings within the specified string as + * delimited by the specified separator characters. + */ + public static NSArray componentsSeparatedByString(String aString, String aSeparator) { + NSArray result = new NSArray(); + if (aString == null) + return result; + if (aSeparator == null) + return new NSArray(aString); + + // FIXME: The spec probably considers the whole + // string as a separator, unlike string tokenizer. + java.util.StringTokenizer tokens = new java.util.StringTokenizer(aString, aSeparator); + while (tokens.hasMoreTokens()) { + result.protectedAdd(tokens.nextToken()); + } + + return result; + } + + public Object clone() { + return new NSArray(list); + } + + public NSArray immutableClone() { + return this; + } + + public NSMutableArray mutableClone() { + return new NSMutableArray(this); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_ARRAY]); + for (int i = 0; i < count(); i++) { + Object x = objectAtIndex(i); + buf.append(NSPropertyListSerialization.stringForPropertyList(x)); + if (i < count() - 1) + buf.append(", "); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_ARRAY]); + return buf.toString(); + } + + // interface List: accessors + + public boolean contains(Object o) { + return list.contains(o); + } + + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + public boolean equals(Object o) { + return list.equals(o); + } + + public Object get(int index) { + return list.get(index); + } + + public int hashCode() { + int code = 19; + code *= getClass().hashCode(); + code *= list.hashCode(); + return code; + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public int size() { + return list.size(); + } + + public Object[] toArray() { + return list.toArray(); + } + + public Object[] toArray(Object[] a) { + return list.toArray(a); + } + + // interface List: mutators + + public void add(int index, Object element) { + this.list.add(index, element); + } + + public boolean add(Object o) { return this.list.add(o); - } - - public boolean addAll(Collection coll) - { - return this.list.addAll(coll); - } - - public boolean addAll(int index, Collection c) - { - return this.list.addAll(index,c); - } - - public void clear() - { + } + + public boolean addAll(Collection coll) { + return this.list.addAll(coll); + } + + public boolean addAll(int index, Collection c) { + return this.list.addAll(index, c); + } + + public void clear() { this.list.clear(); - } - - public Iterator iterator() - { - // make a copy to avoid ConcurrentModificationExceptions - final Iterator i = new LinkedList( list ).iterator(); - return new Iterator() - { - public boolean hasNext() {return i.hasNext();} - public Object next() {return i.next();} - public void remove() { throw new UnsupportedOperationException(); } - }; - } - - public ListIterator listIterator() { return listIterator(0); } - - public ListIterator listIterator(final int index) - { - // make a copy to avoid ConcurrentModificationExceptions - final ListIterator i = new LinkedList( list ).listIterator(index); - return new ListIterator() - { - public boolean hasNext() {return i.hasNext();} - public Object next() {return i.next();} - public boolean hasPrevious() {return i.hasPrevious();} - public Object previous() {return i.previous();} - public int nextIndex() {return i.nextIndex();} - public int previousIndex() {return i.previousIndex();} - public void remove() { throw new UnsupportedOperationException(); } - public void set(Object o) { throw new UnsupportedOperationException(); } - public void add(Object o) { throw new UnsupportedOperationException(); } - }; - } - - public Object remove(int index) - { + } + + public Iterator iterator() { + // make a copy to avoid ConcurrentModificationExceptions + final Iterator i = new LinkedList(list).iterator(); + return new Iterator() { + public boolean hasNext() { + return i.hasNext(); + } + + public Object next() { + return i.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(final int index) { + // make a copy to avoid ConcurrentModificationExceptions + final ListIterator i = new LinkedList(list).listIterator(index); + return new ListIterator() { + public boolean hasNext() { + return i.hasNext(); + } + + public Object next() { + return i.next(); + } + + public boolean hasPrevious() { + return i.hasPrevious(); + } + + public Object previous() { + return i.previous(); + } + + public int nextIndex() { + return i.nextIndex(); + } + + public int previousIndex() { + return i.previousIndex(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void set(Object o) { + throw new UnsupportedOperationException(); + } + + public void add(Object o) { + throw new UnsupportedOperationException(); + } + }; + } + + public Object remove(int index) { return this.list.remove(index); - } - - public boolean remove(Object o) - { + } + + public boolean remove(Object o) { return this.list.remove(o); - } + } - public boolean removeAll(Collection coll) - { + public boolean removeAll(Collection coll) { return this.list.removeAll(coll); - } + } - public boolean retainAll(Collection coll) - { + public boolean retainAll(Collection coll) { return this.list.retainAll(coll); - } - - public Object set(int index, Object element) - { - return this.list.set(index,element); - } - - public List subList(int fromIndex, int toIndex) - { - return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); - } - - /** - * Provided for the use of subclasses like ArrayFault. - */ - protected boolean protectedAdd( Object o ) - { - return list.add( o ); - } - - /** - * Provided for the use of subclasses like ArrayFault. - */ - protected boolean protectedAddAll( Collection coll ) - { - return list.addAll( coll ); - } + } + + public Object set(int index, Object element) { + return this.list.set(index, element); + } + + public List subList(int fromIndex, int toIndex) { + return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); + } + + /** + * Provided for the use of subclasses like ArrayFault. + */ + protected boolean protectedAdd(Object o) { + return list.add(o); + } + + /** + * Provided for the use of subclasses like ArrayFault. + */ + protected boolean protectedAddAll(Collection coll) { + return list.addAll(coll); + } } /* - * $Log$ - * Revision 1.2 2006/03/10 00:52:27 cgruber - * Add tests for NSArray and fix some problems that became obvious as a result. + * $Log$ Revision 1.2 2006/03/10 00:52:27 cgruber Add tests for NSArray and fix + * some problems that became obvious as a result. * - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.16 2005/07/13 14:12:44 cgruber - * Add mutableClone() and immutableClone() per. WebObjects 5.3 conformance. + * Revision 1.16 2005/07/13 14:12:44 cgruber Add mutableClone() and + * immutableClone() per. WebObjects 5.3 conformance. * - * Revision 1.15 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.15 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.14 2003/08/05 00:48:56 chochos - * use NSPropertyListSerialization to get the opening and closing tokens for the string representation + * Revision 1.14 2003/08/05 00:48:56 chochos use NSPropertyListSerialization to + * get the opening and closing tokens for the string representation * - * Revision 1.13 2003/08/04 20:26:10 chochos - * use NSPropertyListSerialization inside toString() + * Revision 1.13 2003/08/04 20:26:10 chochos use NSPropertyListSerialization + * inside toString() * - * Revision 1.12 2003/08/04 18:18:43 chochos - * toString() yields strings in the same format as Apple's NSArray + * Revision 1.12 2003/08/04 18:18:43 chochos toString() yields strings in the + * same format as Apple's NSArray * - * Revision 1.11 2003/01/28 19:44:20 mpowers - * Fixed reverse enumerator. + * Revision 1.11 2003/01/28 19:44:20 mpowers Fixed reverse enumerator. * - * Revision 1.10 2003/01/18 23:49:55 mpowers - * Added mutableClone(). + * Revision 1.10 2003/01/18 23:49:55 mpowers Added mutableClone(). * - * Revision 1.9 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.9 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.8 2003/01/16 22:47:30 mpowers - * Compatibility changes to support compiling woextensions source. - * (34 out of 56 classes compile!) + * Revision 1.8 2003/01/16 22:47:30 mpowers Compatibility changes to support + * compiling woextensions source. (34 out of 56 classes compile!) * - * Revision 1.7 2003/01/10 19:16:40 mpowers - * Implemented support for page caching. + * Revision 1.7 2003/01/10 19:16:40 mpowers Implemented support for page + * caching. * - * Revision 1.6 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.6 2002/10/24 21:15:36 mpowers New implementations of NSArray and + * subclasses. * - * Revision 1.5 2002/10/24 18:16:30 mpowers - * Now enforcing NSArray's immutable nature. + * Revision 1.5 2002/10/24 18:16:30 mpowers Now enforcing NSArray's immutable + * nature. * - * Revision 1.4 2002/03/08 19:02:54 mpowers - * Long-overdue speed optimization of indexOfIdenticalObject. + * Revision 1.4 2002/03/08 19:02:54 mpowers Long-overdue speed optimization of + * indexOfIdenticalObject. * - * Revision 1.3 2002/02/13 22:02:56 mpowers - * Fixed: bug in componentsSeparatedByString when separator is null - * (thanks to Cedrik LIME). + * Revision 1.3 2002/02/13 22:02:56 mpowers Fixed: bug in + * componentsSeparatedByString when separator is null (thanks to Cedrik LIME). * - * Revision 1.2 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.2 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:37 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:37 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java index b735404..49dda88 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java @@ -67,7 +67,7 @@ public class NSBundle { private static NSMutableDictionary _languageCodes = new NSMutableDictionary(); private static NSBundle _mainBundle = null; - + protected static NetworkClassLoader _classLoader = new NetworkClassLoader(ClassLoader.getSystemClassLoader()); /* Instance variables */ @@ -94,8 +94,8 @@ public class NSBundle { /** * The default constructor, which is only public to support other framework - * functionality, and to be API compatible with Apple's WebObjects. - * Generally, framework users should use bundleForXXXX() methods. + * functionality, and to be API compatible with Apple's WebObjects. Generally, + * framework users should use bundleForXXXX() methods. */ public NSBundle() { } @@ -118,11 +118,10 @@ public class NSBundle { } /** - * Returns the bundle that contains the provided class, if any. Otherwise, - * it returns null. Because NSBundles have a specialized class-loader, if - * any two bundles contain duiplicates of the same class, the second will - * fail to load. TODO: Determine if class-load scoping of duplicate classes - * is appropriate. + * Returns the bundle that contains the provided class, if any. Otherwise, it + * returns null. Because NSBundles have a specialized class-loader, if any two + * bundles contain duiplicates of the same class, the second will fail to load. + * TODO: Determine if class-load scoping of duplicate classes is appropriate. * * @param class1 * @return NSBundle @@ -133,8 +132,7 @@ public class NSBundle { } /** - * @deprecated Apple's WebObjects says you should not load from arbitrary - * path. + * @deprecated Apple's WebObjects says you should not load from arbitrary path. * @param path * @return */ @@ -150,9 +148,9 @@ public class NSBundle { /** * Note:This method is only in Wotonomy. * - * This method returns a bundle at a given URL, registering that bundle as - * well. If the bundle has already been loaded/registered, it is simply - * returned from the cache. + * This method returns a bundle at a given URL, registering that bundle as well. + * If the bundle has already been loaded/registered, it is simply returned from + * the cache. * * @param url * @return @@ -170,14 +168,11 @@ public class NSBundle { StringBuffer filename = new StringBuffer(f.getName()); int extensionIndex = filename.lastIndexOf("."); if (extensionIndex == -1) { - NSLog.err - .appendln("Named URL does not point to a bundle with an extension: " - + url); + NSLog.err.appendln("Named URL does not point to a bundle with an extension: " + url); return null; } String basename = filename.substring(0, extensionIndex); - String extension = filename.substring(extensionIndex + 1, filename - .length()); + String extension = filename.substring(extensionIndex + 1, filename.length()); System.out.println("basename: " + basename); System.out.println("extension: " + extension); result = new NSBundle(); @@ -185,16 +180,15 @@ public class NSBundle { result.isFramework = extension.equals("framework"); if (f.isDirectory()) { try { - File javadir = new File(f.getCanonicalPath() + sep + "Contents" - + sep + "Resources" + sep + "Java"); + File javadir = new File(f.getCanonicalPath() + sep + "Contents" + sep + "Resources" + sep + "Java"); System.out.println(javadir); System.out.println(javadir.exists()); File[] jars = javadir.listFiles(); - - } catch (IOException e) { } + + } catch (IOException e) { + } } else { - throw new RuntimeException( - "Compressed bundle files not currently supported."); + throw new RuntimeException("Compressed bundle files not currently supported."); } throw new RuntimeException("Method not finished."); } else { @@ -205,10 +199,8 @@ public class NSBundle { JarFile f; throw new RuntimeException("Method not finished."); } catch (IOException e) { - NSLog.err - .appendln("IOException loading framework jar from URL " - + url + " - message: " - + e.getLocalizedMessage()); + NSLog.err.appendln( + "IOException loading framework jar from URL " + url + " - message: " + e.getLocalizedMessage()); StringWriter stacktrace = new StringWriter(); e.printStackTrace(new PrintWriter(stacktrace)); NSLog.err.appendln(stacktrace); @@ -218,10 +210,9 @@ public class NSBundle { } /** - * This method returns a bundle, either from cache, or if it doesn't exist - * yet, it attempts to look it up - first from the classpath, then from the - * resource path. TODO: Determine if the lookup order is the desired - * semantic. + * This method returns a bundle, either from cache, or if it doesn't exist yet, + * it attempts to look it up - first from the classpath, then from the resource + * path. TODO: Determine if the lookup order is the desired semantic. * * @param name * @return @@ -237,9 +228,9 @@ public class NSBundle { /** * Used to set the "Main" application bundle, in which primary resources are - * loaded for GUI applications. This is mostly only relevant for - * XXApplication objects. This should therefore not be generally used by - * consumers of the framework. + * loaded for GUI applications. This is mostly only relevant for XXApplication + * objects. This should therefore not be generally used by consumers of the + * framework. * * @param aBundle */ @@ -252,14 +243,13 @@ public class NSBundle { } /** - * Get the default prefix for locale. TODO: This really needs to be made - * dynamic somehow. + * Get the default prefix for locale. TODO: This really needs to be made dynamic + * somehow. * * @return */ protected static String defaultLocalePrefix() { - String language = (String) _languageCodes.objectForKey(Locale - .getDefault().getLanguage()); + String language = (String) _languageCodes.objectForKey(Locale.getDefault().getLanguage()); return language + ".lproj"; } @@ -283,8 +273,8 @@ public class NSBundle { } /** - * Returns a byte array for the given resource path. TODO: Lookup semantics - * in WebObjects javadocs. + * Returns a byte array for the given resource path. TODO: Lookup semantics in + * WebObjects javadocs. * * @param path * @return @@ -303,8 +293,8 @@ public class NSBundle { } /** - * Returns an input stream for a given resource path. TODO: Lookup semantics - * in WebObjects javadocs. + * Returns an input stream for a given resource path. TODO: Lookup semantics in + * WebObjects javadocs. * * @param path * @return @@ -339,14 +329,12 @@ public class NSBundle { * @deprecated Don't use this method, use * resourcePathForLocalizedResourceNamed() instead. */ - public String pathForResource(String aName, String anExtension, - String subDir) { + public String pathForResource(String aName, String anExtension, String subDir) { return this.resourcePathForLocalizedResourceNamed(aName, subDir); } /** - * @deprecated Don't use this method, use resourcePathsForResources() - * instead. + * @deprecated Don't use this method, use resourcePathsForResources() instead. */ public NSArray pathsForResources(String aName, String anExtension) { throw new UnsupportedOperationException("Method not yet implemented."); @@ -362,27 +350,24 @@ public class NSBundle { } /** - * @deprecated Resources are now accessed using the bytesForResourcePath() - * and inputStreamForResourcePath() methods. + * @deprecated Resources are now accessed using the bytesForResourcePath() and + * inputStreamForResourcePath() methods. */ public String resourcePath() { throw new UnsupportedOperationException("Method not yet implemented."); // TODO: Implement. } - public String resourcePathForLocalizedResourceNamed(String aName, - String subDir) { + public String resourcePathForLocalizedResourceNamed(String aName, String subDir) { throw new UnsupportedOperationException("Method not yet implemented."); // TODO: Implement. } - public NSArray resourcePathsForDirectories(String extension, - String subdirPath) { + public NSArray resourcePathsForDirectories(String extension, String subdirPath) { throw new UnsupportedOperationException("Method not yet implemented."); } - public NSArray resourcePathsForLocalizedResources(String extension, - String subdirPath) { + public NSArray resourcePathsForLocalizedResources(String extension, String subdirPath) { throw new UnsupportedOperationException("Method not yet implemented."); } @@ -395,8 +380,8 @@ public class NSBundle { int i = 0; if (classNames != null) i = classNames.count(); - return "<" + getClass().getName() + " name:'" + name + "' bundlePath:'" - + path + "' packages:'" + packages + "' " + i + " classes >"; + return "<" + getClass().getName() + " name:'" + name + "' bundlePath:'" + path + "' packages:'" + packages + + "' " + i + " classes >"; } /* Static initialization code */ @@ -412,7 +397,7 @@ public class NSBundle { static { NSBundle._initLanguages(); - + } } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java index ca32dd5..8a1721a 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java @@ -25,89 +25,87 @@ import java.io.InputStream; import java.io.OutputStream; /** -* A class that defines a simple encode/decode paradigm. Subclasses -* would handle the target format. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A class that defines a simple encode/decode paradigm. Subclasses would handle + * the target format. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public abstract class NSCoder { - public NSCoder() { - } + public NSCoder() { + } - public abstract void encodeBoolean(boolean flag); + public abstract void encodeBoolean(boolean flag); - public abstract void encodeByte(byte byte0); + public abstract void encodeByte(byte byte0); - public abstract void encodeBytes(byte abyte0[]); + public abstract void encodeBytes(byte abyte0[]); - public abstract void encodeChar(char c); + public abstract void encodeChar(char c); - public abstract void encodeShort(short word0); + public abstract void encodeShort(short word0); - public abstract void encodeInt(int i); + public abstract void encodeInt(int i); - public abstract void encodeLong(long l); + public abstract void encodeLong(long l); - public abstract void encodeFloat(float f); + public abstract void encodeFloat(float f); - public abstract void encodeDouble(double d); + public abstract void encodeDouble(double d); - public abstract void encodeObject(Object obj); + public abstract void encodeObject(Object obj); - public abstract void encodeClass(Class class1); + public abstract void encodeClass(Class class1); - public abstract void encodeObjects(Object aobj[]); + public abstract void encodeObjects(Object aobj[]); - public abstract boolean decodeBoolean(); + public abstract boolean decodeBoolean(); - public abstract byte decodeByte(); + public abstract byte decodeByte(); - public abstract byte[] decodeBytes(); + public abstract byte[] decodeBytes(); - public abstract char decodeChar(); + public abstract char decodeChar(); - public abstract short decodeShort(); + public abstract short decodeShort(); - public abstract int decodeInt(); + public abstract int decodeInt(); - public abstract long decodeLong(); + public abstract long decodeLong(); - public abstract float decodeFloat(); + public abstract float decodeFloat(); - public abstract double decodeDouble(); + public abstract double decodeDouble(); - public abstract Object decodeObject(); + public abstract Object decodeObject(); - public abstract Class decodeClass(); + public abstract Class decodeClass(); - public abstract Object[] decodeObjects(); + public abstract Object[] decodeObjects(); - public void prepareForWriting(OutputStream outputstream) { - } + public void prepareForWriting(OutputStream outputstream) { + } - public void prepareForReading(InputStream inputstream) { - } + public void prepareForReading(InputStream inputstream) { + } - public void finishCoding() { - } + public void finishCoding() { + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.2 2002/06/25 19:03:02 cgruber - * Internal documentation fixes. + * Revision 1.2 2002/06/25 19:03:02 cgruber Internal documentation fixes. * - * Revision 1.1 2002/06/25 07:52:57 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:57 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java index 56e4124..c578dbc 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java @@ -24,15 +24,14 @@ package net.wotonomy.foundation; import java.math.BigInteger; /** -* A helper interface supporting the NSCoder APIs. At present it -* very confusing to me how this even works from a structural -* perspective, but to be consistent with WebObjects APIs, this will -* have to be properly implemented. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ + * A helper interface supporting the NSCoder APIs. At present it very confusing + * to me how this even works from a structural perspective, but to be consistent + * with WebObjects APIs, this will have to be properly implemented. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ public interface NSCoding { @@ -69,9 +68,7 @@ public interface NSCoding { } /** Not yet implemented */ - protected static void _encodeBigInteger( - NSCoder nscoder, - BigInteger biginteger) { + protected static void _encodeBigInteger(NSCoder nscoder, BigInteger biginteger) { throw new UnsupportedOperationException("Not Yet Implemented"); } @@ -273,8 +270,7 @@ public interface NSCoding { /** Helper class for NSCoding. */ public static abstract class Support { - private static NSMutableDictionary classSupportMap = - new NSMutableDictionary(16); + private static NSMutableDictionary classSupportMap = new NSMutableDictionary(16); public static Support supportForClass(Class aClass) { Support support = null; @@ -294,9 +290,9 @@ public interface NSCoding { classSupportMap.setObjectForKey(support, class1); } - /** Return the class of a given object. It boggles the mind - * as to why this is not a static, but in the original, it's - * not. <sigh> + /** + * Return the class of a given object. It boggles the mind as to why this is not + * a static, but in the original, it's not. <sigh> */ public Class classForCoder(Object obj) { return obj.getClass(); @@ -324,48 +320,40 @@ public interface NSCoding { setSupportForClass(new _LongSupport(), java.lang.Long.class); setSupportForClass(new _FloatSupport(), java.lang.Float.class); setSupportForClass(new _DoubleSupport(), java.lang.Double.class); - setSupportForClass( - new _BigIntegerSupport(), - java.math.BigInteger.class); - setSupportForClass( - new _BigDecimalSupport(), - java.math.BigDecimal.class); + setSupportForClass(new _BigIntegerSupport(), java.math.BigInteger.class); + setSupportForClass(new _BigDecimalSupport(), java.math.BigDecimal.class); setSupportForClass(new _DateSupport(), java.util.Date.class); - setSupportForClass( - new _CharacterSupport(), - java.lang.Character.class); + setSupportForClass(new _CharacterSupport(), java.lang.Character.class); } public Support() { } } - //CEG: I'm not sure why these are here, since NSCoding is an interface. - // It doesn't seem to even be used anywhere. - //CEG: I'm not even sure why this compiles!! - //public abstract Class classForCoder(); + // CEG: I'm not sure why these are here, since NSCoding is an interface. + // It doesn't seem to even be used anywhere. + // CEG: I'm not even sure why this compiles!! + // public abstract Class classForCoder(); - //CEG: I'm not sure why these are here, since NSCoding is an interface. - // It doesn't seem to even be used anywhere. - //CEG: I'm not even sure why this compiles!! - //public abstract void encodeWithCoder(NSCoder nscoder); + // CEG: I'm not sure why these are here, since NSCoding is an interface. + // It doesn't seem to even be used anywhere. + // CEG: I'm not even sure why this compiles!! + // public abstract void encodeWithCoder(NSCoder nscoder); } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.2 2002/06/25 19:03:02 cgruber - * Internal documentation fixes. + * Revision 1.2 2002/06/25 19:03:02 cgruber Internal documentation fixes. * - * Revision 1.1 2002/06/25 07:52:56 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:56 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java index 287a59b..6c88d19 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java @@ -23,82 +23,79 @@ package net.wotonomy.foundation; import java.util.Comparator; - /** -* An object that compares two other objects. As a convenience, it -* also implements java.util.Comparator. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 913 $ -*/ + * An object that compares two other objects. As a convenience, it also + * implements java.util.Comparator. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 913 $ + */ public abstract class NSComparator implements Comparator { - protected static class _NSSelectorComparator extends NSComparator { + protected static class _NSSelectorComparator extends NSComparator { - public int compare(Object obj, Object obj1) throws ComparisonException { + public int compare(Object obj, Object obj1) throws ComparisonException { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public _NSSelectorComparator(NSSelector nsselector) { + public _NSSelectorComparator(NSSelector nsselector) { throw new UnsupportedOperationException("Not Yet Implemented"); - } - } - - private static class StandInComparator extends NSComparator { + } + } - public StandInComparator() { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + private static class StandInComparator extends NSComparator { - public int compare(Object obj, Object obj1) throws ComparisonException { + public StandInComparator() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - } + public int compare(Object obj, Object obj1) throws ComparisonException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public static class ComparisonException extends ClassCastException { + } - public ComparisonException(String s) { - super(s); - } - } + public static class ComparisonException extends ClassCastException { + public ComparisonException(String s) { + super(s); + } + } - public static final NSComparator AscendingStringComparator = new StandInComparator(); - public static final NSComparator DescendingStringComparator = new StandInComparator(); - public static final NSComparator AscendingCaseInsensitiveStringComparator = new StandInComparator(); - public static final NSComparator DescendingCaseInsensitiveStringComparator = new StandInComparator(); - public static final NSComparator AscendingNumberComparator = new StandInComparator(); - public static final NSComparator DescendingNumberComparator = new StandInComparator(); - public static final NSComparator AscendingTimestampComparator = new StandInComparator(); - public static final NSComparator DescendingTimestampComparator = new StandInComparator(); - public static final int OrderedAscending = -1; - public static final int OrderedSame = 0; - public static final int OrderedDescending = 1; + public static final NSComparator AscendingStringComparator = new StandInComparator(); + public static final NSComparator DescendingStringComparator = new StandInComparator(); + public static final NSComparator AscendingCaseInsensitiveStringComparator = new StandInComparator(); + public static final NSComparator DescendingCaseInsensitiveStringComparator = new StandInComparator(); + public static final NSComparator AscendingNumberComparator = new StandInComparator(); + public static final NSComparator DescendingNumberComparator = new StandInComparator(); + public static final NSComparator AscendingTimestampComparator = new StandInComparator(); + public static final NSComparator DescendingTimestampComparator = new StandInComparator(); + public static final int OrderedAscending = -1; + public static final int OrderedSame = 0; + public static final int OrderedDescending = 1; - public NSComparator() { - } + public NSComparator() { + } - public abstract int compare(Object obj, Object obj1) throws ClassCastException; + public abstract int compare(Object obj, Object obj1) throws ClassCastException; - public static int _compareObjects(Comparable comparable, Comparable comparable1) { + public static int _compareObjects(Comparable comparable, Comparable comparable1) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } } /* - * $Log$ - * Revision 1.2 2006/03/10 00:52:27 cgruber - * Add tests for NSArray and fix some problems that became obvious as a result. + * $Log$ Revision 1.2 2006/03/10 00:52:27 cgruber Add tests for NSArray and fix + * some problems that became obvious as a result. * - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.1 2002/06/25 07:52:56 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:56 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java index 36c527c..67f1d59 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java @@ -24,96 +24,85 @@ import java.io.IOException; import java.io.InputStream; /** -* A pure java implementation of NSData, which -* is basically a wrapper on a byte array. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSData -{ - public static final NSData EmptyData = new NSData(); + * A pure java implementation of NSData, which is basically a wrapper on a byte + * array. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSData { + public static final NSData EmptyData = new NSData(); - protected byte[] bytes; + protected byte[] bytes; - /** - * Default constructor creates a zero-data object. - */ - public NSData () - { - bytes = new byte[0]; - } + /** + * Default constructor creates a zero-data object. + */ + public NSData() { + bytes = new byte[0]; + } - /** - * Creates an object containing a copy of the specified bytes. - */ - public NSData (byte[] data) - { - this( data, 0, data.length ); - } + /** + * Creates an object containing a copy of the specified bytes. + */ + public NSData(byte[] data) { + this(data, 0, data.length); + } - /** - * Creates an object containing a copy of the bytes from the specified - * array within the specified range. - */ - public NSData (byte[] data, int start, int length) - { - bytes = new byte[ length ]; - for ( int i = 0; i < length; i++ ) - { - bytes[i] = data[ start+i ]; - } - } + /** + * Creates an object containing a copy of the bytes from the specified array + * within the specified range. + */ + public NSData(byte[] data, int start, int length) { + bytes = new byte[length]; + for (int i = 0; i < length; i++) { + bytes[i] = data[start + i]; + } + } - /** - * Creates an object containing the bytes of the specified string. - */ - public NSData (String aString) - { - this( aString.getBytes() ); - } + /** + * Creates an object containing the bytes of the specified string. + */ + public NSData(String aString) { + this(aString.getBytes()); + } - /** - * Creates an object containing the contents of the specified file. - * Errors reading the file will produce an empty or partially blank array. - */ - public NSData (File aFile) - { - int len = (int) aFile.length(); - byte[] data = new byte[ len ]; - try - { - new java.io.FileInputStream( aFile ).read( data ); - } - catch ( Exception exc ) - { - // produce an empty or partially blank array - } + /** + * Creates an object containing the contents of the specified file. Errors + * reading the file will produce an empty or partially blank array. + */ + public NSData(File aFile) { + int len = (int) aFile.length(); + byte[] data = new byte[len]; + try { + new java.io.FileInputStream(aFile).read(data); + } catch (Exception exc) { + // produce an empty or partially blank array + } bytes = data; - } + } - /** - * Creates an object containing the contents of the specified URL. - */ - public NSData (java.net.URL aURL) - { - throw new RuntimeException( "Not Implemented" ); - } + /** + * Creates an object containing the contents of the specified URL. + */ + public NSData(java.net.URL aURL) { + throw new RuntimeException("Not Implemented"); + } - /** - * Creates an object containing a copy of the contents of the - * specified NSData object. - */ - public NSData (NSData aData) - { - this( aData.bytes() ); - } + /** + * Creates an object containing a copy of the contents of the specified NSData + * object. + */ + public NSData(NSData aData) { + this(aData.bytes()); + } /** - * Creates a new NSData object from the bytes in the input stream. - * The input stream is read fully and is not closed. - * @param stream The stream to read from. + * Creates a new NSData object from the bytes in the input stream. The input + * stream is read fully and is not closed. + * + * @param stream The stream to read from. * @param chunkSize The buffer size used to read from the stream. * @throws IOException if the stream cannot be read from. */ @@ -130,134 +119,119 @@ public class NSData bytes = bout.toByteArray(); } - /** - * Returns the length of the contained data. - */ - public int length () - { - return bytes.length; - } + /** + * Returns the length of the contained data. + */ + public int length() { + return bytes.length; + } - /** - * Returns whether the specified data is equivalent to these data. - */ - public boolean isEqualToData (NSData aData) - { - if (length() != aData.length()) - return false; - byte[] a = bytes(); - byte[] b = aData.bytes(); - - for ( int i = 0; i < a.length; i++ ) { - if ( a[i] != b[i] ) + /** + * Returns whether the specified data is equivalent to these data. + */ + public boolean isEqualToData(NSData aData) { + if (length() != aData.length()) + return false; + byte[] a = bytes(); + byte[] b = aData.bytes(); + + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; } return true; - } + } - /** - * Return the bytes within the data that fall within the specified range. - */ - public NSData subdataWithRange (NSRange aRange) - { - int loc = aRange.location(); - byte[] src = bytes(); - byte[] data = new byte[ aRange.length() ]; - System.arraycopy(src, loc, data, 0, data.length); - return new NSData( data ); - } + /** + * Return the bytes within the data that fall within the specified range. + */ + public NSData subdataWithRange(NSRange aRange) { + int loc = aRange.location(); + byte[] src = bytes(); + byte[] data = new byte[aRange.length()]; + System.arraycopy(src, loc, data, 0, data.length); + return new NSData(data); + } + + /** + * Writes the contents of this data to the specified URL. If atomically is true, + * then the data is written to a temporary file and then renamed to the name + * specified by the URL when the data transfer is complete. + */ + public boolean writeToURL(java.net.URL aURL, boolean atomically) { + throw new RuntimeException("Not Implemented"); + } - /** - * Writes the contents of this data to the specified URL. - * If atomically is true, then the data is written to a temporary - * file and then renamed to the name specified by the URL when - * the data transfer is complete. - */ - public boolean writeToURL (java.net.URL aURL, boolean atomically) - { - throw new RuntimeException( "Not Implemented" ); - } + /** + * Convenience to return the contents of the specified file. + */ + public static NSData dataWithContentsOfMappedFile(java.io.File aFile) { + return new NSData(aFile); + } - /** - * Convenience to return the contents of the specified file. - */ - public static NSData dataWithContentsOfMappedFile (java.io.File aFile) - { - return new NSData( aFile ); - } + /** + * Returns a copy of the bytes starting at the specified location and ranging + * for the specified length. + */ + public byte[] bytes(int location, int length) { + byte[] data = new byte[length]; + for (int i = 0; i < length; i++) { + data[i] = bytes[location + i]; + } + return data; + } - /** - * Returns a copy of the bytes starting at the specified location - * and ranging for the specified length. - */ - public byte[] bytes (int location, int length) - { - byte[] data = new byte[ length ]; - for ( int i = 0; i < length; i++ ) - { - data[i] = bytes[ location + i ]; - } - return data; - } - - /** - * Returns a copy of the bytes backing this data object. - * NOTE: This method is not in the NSData spec and is - * included for convenience only. - */ - public byte[] bytes() - { - return bytes( 0, length() ); - } + /** + * Returns a copy of the bytes backing this data object. NOTE: This method is + * not in the NSData spec and is included for convenience only. + */ + public byte[] bytes() { + return bytes(0, length()); + } - public String toString() { - String hex = "0123456789ABCDEF"; - StringBuffer buf = new StringBuffer(); - buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DATA]); - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - buf.append(hex.charAt((b & 0xf0) >> 4)); - buf.append(hex.charAt(b & 0x0f)); - if (i % 5 == 4) - buf.append(' '); - } - buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DATA]); - return buf.toString(); - } + public String toString() { + String hex = "0123456789ABCDEF"; + StringBuffer buf = new StringBuffer(); + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DATA]); + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + buf.append(hex.charAt((b & 0xf0) >> 4)); + buf.append(hex.charAt(b & 0x0f)); + if (i % 5 == 4) + buf.append(' '); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DATA]); + return buf.toString(); + } public boolean isEqual(Object obj) { if (obj == this) return true; if (obj instanceof NSData) - return isEqualToData((NSData)obj); + return isEqualToData((NSData) obj); return false; } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/19 01:53:52 chochos - * added constructor with an InputStream + * Revision 1.5 2003/08/19 01:53:52 chochos added constructor with an + * InputStream * - * Revision 1.4 2003/08/05 00:51:31 chochos - * get the enclosing tokens from NSPropertyListSerialization + * Revision 1.4 2003/08/05 00:51:31 chochos get the enclosing tokens from + * NSPropertyListSerialization * - * Revision 1.3 2003/08/04 22:45:47 chochos - * toString() prints out the bytes in hex (in property list format) + * Revision 1.3 2003/08/04 22:45:47 chochos toString() prints out the bytes in + * hex (in property list format) * - * Revision 1.2 2003/08/02 01:52:00 chochos - * added EmptyData + * Revision 1.2 2003/08/02 01:52:00 chochos added EmptyData * - * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java index d5b6f61..a2121fd 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java @@ -23,17 +23,16 @@ import java.util.GregorianCalendar; import java.util.TimeZone; /** -* A pure java implementation of NSDate that extends -* java.util.Date for greater java compatibility. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ -public class NSDate extends Date -{ + * A pure java implementation of NSDate that extends java.util.Date for greater + * java compatibility. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ +public class NSDate extends Date { // NSComparisonResult compatibility - + public static final int NSOrderedAscending = -1; public static final int NSOrderedSame = 0; public static final int NSOrderedDescending = 1; @@ -41,158 +40,142 @@ public class NSDate extends Date // public static final double TimeIntervalSince1970; // public static final NSDate DateFor1970; - /** - * Default constructor represents the current date. - */ - public NSDate () - { - super(); - } - - /** - * Represents the specified number of seconds from the current date. - */ - public NSDate (double seconds) - { - super( (long) new NSDate().getTime() + - timeIntervalToMilliseconds(seconds) ); - } - - /** - * Represents the specified number of seconds from the specified date. - */ - public NSDate (double seconds, Date sinceDate) - { - super( (long) sinceDate.getTime() + - timeIntervalToMilliseconds(seconds) ); - } - - /** - * Returns the interval between this date and 1 January 2001 GMT. - */ - public double timeIntervalSinceReferenceDate () - { - GregorianCalendar referenceDate = - new GregorianCalendar( TimeZone.getTimeZone( "GMT" ) ); - referenceDate.set( 2001, 0, 0, 0, 0, 0 ); - return timeIntervalSinceDate( referenceDate.getTime() ); - } - - /** - * Returns the interval between this date and the specified date - * in seconds. - */ - public double timeIntervalSinceDate (Date aDate) - { - return millisecondsToTimeInterval( - this.getTime() - aDate.getTime() ); - } - - /** - * Returns the interval between this date and the current date - * in seconds. - */ - public double timeIntervalSinceNow () - { - return timeIntervalSinceDate( new NSDate() ); - } - - /** - * Compares this date to the specified date and returns the - * earlier date. Unspecified which is returned if both are equal. - */ - public NSDate earlierDate (NSDate aDate) - { - if ( aDate == null ) return this; - if ( after( aDate ) ) return aDate; - return this; - } - - /** - * Compares this date to the specified date and returns the - * later date. Unspecified which is returned if both are equal. - */ - public NSDate laterDate (NSDate aDate) - { - if ( aDate == null ) return this; - if ( before( aDate ) ) return aDate; - return this; - } - - /** - * Returns a negative value if the specified date is later than - * this date, a positive value if the specified date is earlier - * than this date, or zero if the dates are equal. The return - * values are compatible with type NSComparisonResult. - */ - public int compare (Date aDate) - { - if ( before( aDate ) ) return NSOrderedAscending; - if ( after( aDate ) ) return NSOrderedDescending; - return NSOrderedSame; - } - - /** - * Returns whether the this date is equal to the specified date, - * per the result of equals(). - */ - public boolean isEqualToDate (Date aDate) - { - return equals( aDate ); - } - - /** - * Returns a date that differs from this date by the specified - * number of seconds. - */ - public NSDate dateByAddingTimeInterval (double seconds) - { - return new NSDate( seconds, this ); - } - - /** - * Returns the number of seconds between now and the reference date. - */ - public static double currentTimeIntervalSinceReferenceDate () - { - return new NSDate().timeIntervalSinceReferenceDate(); - } - - /** - * Converts seconds to milliseconds. Included for compatibility. - */ - public static long timeIntervalToMilliseconds (double seconds) - { - return (long) seconds*1000; - } - - /** - * Converts milliseconds to seconds. Included for compatibility. - */ - public static double millisecondsToTimeInterval (long millis) - { - return millis/1000.0; - } - - /** - * Returns a date that is greater than all representable dates. - */ - public static NSDate distantFuture () - { - NSDate result = new NSDate(); - result.setTime( Long.MAX_VALUE ); - return result; - } - - /** - * Returns a date that is less than all representable dates. - */ - public static NSDate distantPast () - { - NSDate result = new NSDate(); - result.setTime( Long.MIN_VALUE ); - return result; - } + /** + * Default constructor represents the current date. + */ + public NSDate() { + super(); + } + + /** + * Represents the specified number of seconds from the current date. + */ + public NSDate(double seconds) { + super((long) new NSDate().getTime() + timeIntervalToMilliseconds(seconds)); + } + + /** + * Represents the specified number of seconds from the specified date. + */ + public NSDate(double seconds, Date sinceDate) { + super((long) sinceDate.getTime() + timeIntervalToMilliseconds(seconds)); + } + + /** + * Returns the interval between this date and 1 January 2001 GMT. + */ + public double timeIntervalSinceReferenceDate() { + GregorianCalendar referenceDate = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + referenceDate.set(2001, 0, 0, 0, 0, 0); + return timeIntervalSinceDate(referenceDate.getTime()); + } + + /** + * Returns the interval between this date and the specified date in seconds. + */ + public double timeIntervalSinceDate(Date aDate) { + return millisecondsToTimeInterval(this.getTime() - aDate.getTime()); + } + + /** + * Returns the interval between this date and the current date in seconds. + */ + public double timeIntervalSinceNow() { + return timeIntervalSinceDate(new NSDate()); + } + + /** + * Compares this date to the specified date and returns the earlier date. + * Unspecified which is returned if both are equal. + */ + public NSDate earlierDate(NSDate aDate) { + if (aDate == null) + return this; + if (after(aDate)) + return aDate; + return this; + } + + /** + * Compares this date to the specified date and returns the later date. + * Unspecified which is returned if both are equal. + */ + public NSDate laterDate(NSDate aDate) { + if (aDate == null) + return this; + if (before(aDate)) + return aDate; + return this; + } + + /** + * Returns a negative value if the specified date is later than this date, a + * positive value if the specified date is earlier than this date, or zero if + * the dates are equal. The return values are compatible with type + * NSComparisonResult. + */ + public int compare(Date aDate) { + if (before(aDate)) + return NSOrderedAscending; + if (after(aDate)) + return NSOrderedDescending; + return NSOrderedSame; + } + + /** + * Returns whether the this date is equal to the specified date, per the result + * of equals(). + */ + public boolean isEqualToDate(Date aDate) { + return equals(aDate); + } + + /** + * Returns a date that differs from this date by the specified number of + * seconds. + */ + public NSDate dateByAddingTimeInterval(double seconds) { + return new NSDate(seconds, this); + } + + /** + * Returns the number of seconds between now and the reference date. + */ + public static double currentTimeIntervalSinceReferenceDate() { + return new NSDate().timeIntervalSinceReferenceDate(); + } + + /** + * Converts seconds to milliseconds. Included for compatibility. + */ + public static long timeIntervalToMilliseconds(double seconds) { + return (long) seconds * 1000; + } + + /** + * Converts milliseconds to seconds. Included for compatibility. + */ + public static double millisecondsToTimeInterval(long millis) { + return millis / 1000.0; + } + + /** + * Returns a date that is greater than all representable dates. + */ + public static NSDate distantFuture() { + NSDate result = new NSDate(); + result.setTime(Long.MAX_VALUE); + return result; + } + + /** + * Returns a date that is less than all representable dates. + */ + public static NSDate distantPast() { + NSDate result = new NSDate(); + result.setTime(Long.MIN_VALUE); + return result; + } // inherited from java.util.Date // public java.lang.String toString (); @@ -202,16 +185,12 @@ public class NSDate extends Date } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:28 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:28 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java index 235a7bb..a4e9cd5 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java @@ -24,309 +24,268 @@ import java.util.Iterator; import java.util.Map; /** -* A pure java implementation of NSDictionary that -* implements Map for greater java interoperability. -* -* @author michael@mpowers.net -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSDictionary extends HashMap implements NSKeyValueCoding -{ - public static final NSDictionary EmptyDictionary = new NSDictionary(); - - /** - * Default constructor produces an empty dictionary. - */ - public NSDictionary () - { - super(); - } - - /** - * Constructor produces an empty dictionary with an initial capacity. - */ - public NSDictionary (int initialCapacity) - { - super(initialCapacity); - } - - /** - * Produces a dictionary that contains one key referencing one value. - */ - public NSDictionary (Object key, Object value) - { - super(); - put( key, value ); - } - - /** - * Produces a dictionary containing the specified keys and values. - * An IllegalArgumentException is thrown if the arrays are not - * of the same length. - */ - public NSDictionary (Object[] objects, Object[] keys) - { - super(); - if ( keys.length != objects.length ) - { - throw new IllegalArgumentException( "Array lengths do not match." ); - } - - for ( int i = 0; i < keys.length; i++ ) - { - put( keys[i], objects[i] ); - } - } - - /** - * Produces a dictionary that is a copy of the specified map (or dictionary). - */ - public NSDictionary (Map aMap) - { - super( aMap ); - } - - /** - * Returns a count of the key-value pairs in this dictionary. - */ - public int count () - { - return size(); - } - - /** - * Returns an NSArray containing all keys in this dictionary. - */ - public NSArray allKeys () - { - return new NSArray( keySet() ); - - } - - /** - * Returns an NSArray containing all keys that reference the - * specified value. - */ - public NSArray allKeysForObject (Object value) - { - NSMutableArray result = new NSMutableArray(); - Map.Entry entry; + * A pure java implementation of NSDictionary that implements Map for greater + * java interoperability. + * + * @author michael@mpowers.net + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSDictionary extends HashMap implements NSKeyValueCoding { + public static final NSDictionary EmptyDictionary = new NSDictionary(); + + /** + * Default constructor produces an empty dictionary. + */ + public NSDictionary() { + super(); + } + + /** + * Constructor produces an empty dictionary with an initial capacity. + */ + public NSDictionary(int initialCapacity) { + super(initialCapacity); + } + + /** + * Produces a dictionary that contains one key referencing one value. + */ + public NSDictionary(Object key, Object value) { + super(); + put(key, value); + } + + /** + * Produces a dictionary containing the specified keys and values. An + * IllegalArgumentException is thrown if the arrays are not of the same length. + */ + public NSDictionary(Object[] objects, Object[] keys) { + super(); + if (keys.length != objects.length) { + throw new IllegalArgumentException("Array lengths do not match."); + } + + for (int i = 0; i < keys.length; i++) { + put(keys[i], objects[i]); + } + } + + /** + * Produces a dictionary that is a copy of the specified map (or dictionary). + */ + public NSDictionary(Map aMap) { + super(aMap); + } + + /** + * Returns a count of the key-value pairs in this dictionary. + */ + public int count() { + return size(); + } + + /** + * Returns an NSArray containing all keys in this dictionary. + */ + public NSArray allKeys() { + return new NSArray(keySet()); + + } + + /** + * Returns an NSArray containing all keys that reference the specified value. + */ + public NSArray allKeysForObject(Object value) { + NSMutableArray result = new NSMutableArray(); + Map.Entry entry; Iterator it = entrySet().iterator(); - while ( it.hasNext() ) - { + while (it.hasNext()) { entry = (Map.Entry) it.next(); // handle null values - if ( ( value == null ) && ( entry.getValue() == null ) - || ( value.equals( entry.getValue() ) ) ) - { + if ((value == null) && (entry.getValue() == null) || (value.equals(entry.getValue()))) { // if match, add to result set - result.addObject( entry.getKey() ); + result.addObject(entry.getKey()); } } - + return result; - } - - /** - * Returns an NSArray containing all values in this dictionary. - */ - public NSArray allValues () - { - return new NSArray( values() ); - } - - /** - * Returns whether the specified dictionary has the same or - * equivalent key-value pairs as this dictionary. - */ - public boolean isEqualToDictionary (NSDictionary aDictionary) - { - return equals( aDictionary ); - } - - /** - * Returns an array of objects for the specified array of keys. - * If a key isn't found, the marker parameter will be placed - * in the corresponding index(es) in the returned array. - */ - public NSArray objectsForKeys (NSArray anArray, Object aMarker) - { - NSMutableArray result = new NSMutableArray(); - if ( anArray == null ) return result; - - Object value; - Enumeration enumeration = anArray.objectEnumerator(); - while ( enumeration.hasMoreElements() ) - { - value = objectForKey( enumeration.nextElement() ); - if ( value == null ) - { - value = aMarker; - } - result.addObject( value ); - } - return result; - } - - /** - * Returns an enumeration over the keys in this dictionary. - */ - public java.util.Enumeration keyEnumerator () - { - return new java.util.Enumeration() - { - Iterator it = NSDictionary.this.keySet().iterator(); - public boolean hasMoreElements() - { + } + + /** + * Returns an NSArray containing all values in this dictionary. + */ + public NSArray allValues() { + return new NSArray(values()); + } + + /** + * Returns whether the specified dictionary has the same or equivalent key-value + * pairs as this dictionary. + */ + public boolean isEqualToDictionary(NSDictionary aDictionary) { + return equals(aDictionary); + } + + /** + * Returns an array of objects for the specified array of keys. If a key isn't + * found, the marker parameter will be placed in the corresponding index(es) in + * the returned array. + */ + public NSArray objectsForKeys(NSArray anArray, Object aMarker) { + NSMutableArray result = new NSMutableArray(); + if (anArray == null) + return result; + + Object value; + Enumeration enumeration = anArray.objectEnumerator(); + while (enumeration.hasMoreElements()) { + value = objectForKey(enumeration.nextElement()); + if (value == null) { + value = aMarker; + } + result.addObject(value); + } + return result; + } + + /** + * Returns an enumeration over the keys in this dictionary. + */ + public java.util.Enumeration keyEnumerator() { + return new java.util.Enumeration() { + Iterator it = NSDictionary.this.keySet().iterator(); + + public boolean hasMoreElements() { return it.hasNext(); - } - public Object nextElement() - { - return it.next(); - } - }; - } - - /** - * Returns an enumeration over the values in this dictionary. - */ - public java.util.Enumeration objectEnumerator () - { - return new java.util.Enumeration() - { - Iterator it = NSDictionary.this.values().iterator(); - public boolean hasMoreElements() - { + } + + public Object nextElement() { + return it.next(); + } + }; + } + + /** + * Returns an enumeration over the values in this dictionary. + */ + public java.util.Enumeration objectEnumerator() { + return new java.util.Enumeration() { + Iterator it = NSDictionary.this.values().iterator(); + + public boolean hasMoreElements() { return it.hasNext(); - } - public Object nextElement() - { - return it.next(); - } - }; + } + + public Object nextElement() { + return it.next(); + } + }; + } + + /** + * Returns the value for the specified key, or null if the key is not found. + */ + public Object objectForKey(Object aKey) { + return get(aKey); + } + + // interface NSKeyValueCoding + + public Object valueForKey(String aKey) { // System.out.println( "valueForKey: " + aKey + "->" + this ); + Object result = objectForKey(aKey); + if (result == null) + result = NSKeyValueCodingSupport.valueForKey(this, aKey); + return result; + } + + public void takeValueForKey(Object aValue, String aKey) { // System.out.println( "takeValueForKey: " + aKey + " : " + // + aValue + "->" + this ); + put(aKey, aValue); // FIXME: technically cheating since this is a read-only class + } + + public Object storedValueForKey(String aKey) { + Object result = objectForKey(aKey); + if (result == null) + result = NSKeyValueCodingSupport.storedValueForKey(this, aKey); + return result; + } + + public void takeStoredValueForKey(Object aValue, String aKey) { + put(aKey, aValue); // FIXME: technically cheating since this is a read-only class + } + + public Object handleQueryWithUnboundKey(String aKey) { + return NSKeyValueCodingSupport.handleQueryWithUnboundKey(this, aKey); + } + + public void handleTakeValueForUnboundKey(Object aValue, String aKey) { + NSKeyValueCodingSupport.handleTakeValueForUnboundKey(this, aValue, aKey); + } + + public void unableToSetNullForKey(String aKey) { + NSKeyValueCodingSupport.unableToSetNullForKey(this, aKey); + } + + public Object validateTakeValueForKeyPath(Object aValue, String aKey) { + throw new RuntimeException("Not implemented yet."); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration enumeration = keyEnumerator(); + boolean quote = false; + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DICTIONARY]); + while (enumeration.hasMoreElements()) { + if (buf.length() == 1) + buf.append(' '); + Object k = enumeration.nextElement(); + buf.append(NSPropertyListSerialization.stringForPropertyList(k)); + buf.append(" = "); + k = objectForKey(k); + buf.append(NSPropertyListSerialization.stringForPropertyList(k)); + buf.append("; "); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DICTIONARY]); + return buf.toString(); } - - /** - * Returns the value for the specified key, or null - * if the key is not found. - */ - public Object objectForKey (Object aKey) - { - return get( aKey ); - } - - // interface NSKeyValueCoding - - public Object valueForKey (String aKey) - { // System.out.println( "valueForKey: " + aKey + "->" + this ); - Object result = objectForKey( aKey ); - if ( result == null ) - result = NSKeyValueCodingSupport.valueForKey( this, aKey ); - return result; - } - - public void takeValueForKey (Object aValue, String aKey) - { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); - put( aKey, aValue ); //FIXME: technically cheating since this is a read-only class - } - - public Object storedValueForKey (String aKey) - { - Object result = objectForKey( aKey ); - if ( result == null ) - result = NSKeyValueCodingSupport.storedValueForKey( this, aKey ); - return result; - } - - public void takeStoredValueForKey (Object aValue, String aKey) - { - put( aKey, aValue ); //FIXME: technically cheating since this is a read-only class - } - - public Object handleQueryWithUnboundKey (String aKey) - { - return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); - } - - public void handleTakeValueForUnboundKey (Object aValue, String aKey) - { - NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); - } - - public void unableToSetNullForKey (String aKey) - { - NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); - } - - public Object validateTakeValueForKeyPath (Object aValue, String aKey) - { - throw new RuntimeException( "Not implemented yet." ); - } - - public String toString() { - StringBuffer buf = new StringBuffer(); - Enumeration enumeration = keyEnumerator(); - boolean quote = false; - buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DICTIONARY]); - while (enumeration.hasMoreElements()) { - if (buf.length() == 1) - buf.append(' '); - Object k = enumeration.nextElement(); - buf.append(NSPropertyListSerialization.stringForPropertyList(k)); - buf.append(" = "); - k = objectForKey(k); - buf.append(NSPropertyListSerialization.stringForPropertyList(k)); - buf.append("; "); - } - buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DICTIONARY]); - return buf.toString(); - } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 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 + * 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/05 00:50:14 chochos - * use NSPropertyListSerialization to get the tokens to enclose the string description + * Revision 1.8 2003/08/05 00:50:14 chochos use NSPropertyListSerialization to + * get the tokens to enclose the string description * - * Revision 1.7 2003/08/04 20:26:10 chochos - * use NSPropertyListSerialization inside toString() + * Revision 1.7 2003/08/04 20:26:10 chochos use NSPropertyListSerialization + * inside toString() * - * Revision 1.6 2003/08/04 18:49:38 chochos - * NSDictionary(Object[], Object[]) was taking the parameters in the wrong order; for compatibility with Apple's NSDictionary, objects comes first, then keys. + * Revision 1.6 2003/08/04 18:49:38 chochos NSDictionary(Object[], Object[]) was + * taking the parameters in the wrong order; for compatibility with Apple's + * NSDictionary, objects comes first, then keys. * - * Revision 1.5 2003/08/04 18:26:19 chochos - * fixed opening '{' + * Revision 1.5 2003/08/04 18:26:19 chochos fixed opening '{' * - * Revision 1.4 2003/01/28 22:11:30 mpowers - * Now implements NSKeyValueCoding. + * Revision 1.4 2003/01/28 22:11:30 mpowers Now implements NSKeyValueCoding. * - * Revision 1.3 2002/06/30 17:58:06 mpowers - * Add a capacity constructor and static empty dictionary: thanks cgruber. + * Revision 1.3 2002/06/30 17:58:06 mpowers Add a capacity constructor and + * static empty dictionary: thanks cgruber. * - * Revision 1.2 2001/02/23 23:43:41 mpowers - * Removed ill-advised this. + * Revision 1.2 2001/02/23 23:43:41 mpowers Removed ill-advised this. * - * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java index 46df7d2..49b84a2 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java @@ -22,14 +22,14 @@ $Id: NSDisposable.java 893 2006-02-16 13:22:23Z cgruber $ package net.wotonomy.foundation; /** -* Interface for objects that respond to dispose(); -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * Interface for objects that respond to dispose(); + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public interface NSDisposable { - public abstract void dispose(); + public abstract void dispose(); } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java index 1db0ad2..10f54af 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java @@ -23,135 +23,115 @@ import java.io.PrintWriter; import java.io.StringWriter; /** -* Serves to wrap an exception inside of a RuntimeException, -* which is not required to be declared in a throws statement. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * Serves to wrap an exception inside of a RuntimeException, which is not + * required to be declared in a throws statement. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ -public class NSForwardException extends RuntimeException -{ +public class NSForwardException extends RuntimeException { protected String message; protected Throwable wrappedThrowable; - + /** - * Default constructor. - */ - public NSForwardException() - { + * Default constructor. + */ + public NSForwardException() { super(); message = null; - wrappedThrowable = null; + wrappedThrowable = null; } - + /** - * Standard constructor with message. - */ - public NSForwardException( String aMessage ) - { - super( aMessage ); + * Standard constructor with message. + */ + public NSForwardException(String aMessage) { + super(aMessage); message = aMessage; wrappedThrowable = null; } - + /** - * Specifies a throwable to wrap. - */ - public NSForwardException( Throwable aThrowable ) - { + * Specifies a throwable to wrap. + */ + public NSForwardException(Throwable aThrowable) { super(); message = null; wrappedThrowable = aThrowable; } - + /** - * Specifies a message and a throwable to wrap. - */ - public NSForwardException( Throwable aThrowable, String aMessage ) - { - super( aMessage ); + * Specifies a message and a throwable to wrap. + */ + public NSForwardException(Throwable aThrowable, String aMessage) { + super(aMessage); message = aMessage; wrappedThrowable = aThrowable; } - - /** - * Returns the wrapped throwable. - */ - public Throwable originalException() - { - return wrappedThrowable; - } - - public void printStackTrace(PrintWriter s) - { - if ( message != null ) - { - s.println( toString() ); + + /** + * Returns the wrapped throwable. + */ + public Throwable originalException() { + return wrappedThrowable; + } + + public void printStackTrace(PrintWriter s) { + if (message != null) { + s.println(toString()); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace( s ); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(s); return; } - super.printStackTrace( s ); + super.printStackTrace(s); } - - public void printStackTrace(PrintStream s) - { - if ( message != null ) - { - s.println( toString() ); + + public void printStackTrace(PrintStream s) { + if (message != null) { + s.println(toString()); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace( s ); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(s); return; } - super.printStackTrace( s ); + super.printStackTrace(s); } - - public void printStackTrace() - { - if ( message != null ) - { - System.err.println( toString() ); + + public void printStackTrace() { + if (message != null) { + System.err.println(toString()); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace(); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(); return; } super.printStackTrace(); } - - public String stackTrace() - { - StringWriter writer = new StringWriter(); - PrintWriter printWriter = new PrintWriter( writer ); - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace( printWriter ); + + public String stackTrace() { + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(printWriter); + } else { + super.printStackTrace(printWriter); } - else - { - super.printStackTrace( printWriter ); - } - printWriter.flush(); - printWriter.close(); - return writer.toString(); - } - - public String toString() - { - String result = message; - if ( result == null ) result = ""; - if ( wrappedThrowable != null ) - { - result = wrappedThrowable.toString() + " : " + result; + printWriter.flush(); + printWriter.close(); + return writer.toString(); + } + + public String toString() { + String result = message; + if (result == null) + result = ""; + if (wrappedThrowable != null) { + result = wrappedThrowable.toString() + " : " + result; } - return result; - } - + return result; + } + } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java index 5792303..9eece7e 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java @@ -25,374 +25,293 @@ import net.wotonomy.foundation.internal.NullPrimitiveException; import net.wotonomy.foundation.internal.WotonomyException; /** -* NSKeyValueCoding defines an interface for classes that -* need to have more control over the wotonomy's property -* introspection facilities.

-* -* On an object that implements this interface, wotonomy -* will call these methods, and otherwise use the static -* methods on NSKeyValueCodingSupport.

-* -* NSKeyValueCodingSupport implements the default behaviors -* for each of these methods, so classes implementing this -* interface can call those methods to acheive the same -* behavior.

-* -* 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.

-* -* 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 NSKeyValueCoding -{ + * NSKeyValueCoding defines an interface for classes that need to have more + * control over the wotonomy's property introspection facilities.
+ *
+ * + * On an object that implements this interface, wotonomy will call these + * methods, and otherwise use the static methods on NSKeyValueCodingSupport. + *
+ *
+ * + * NSKeyValueCodingSupport implements the default behaviors for each of these + * methods, so classes implementing this interface can call those methods to + * acheive the same behavior.
+ *
+ * + * 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.
+ *
+ * + * 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 NSKeyValueCoding { public static final Null NullValue = new Null(); - /** - * 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 ); - - /** - * Static utility methods that - * call the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - public class Utility - { - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public Object valueForKey( - Object anObject, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - return ((NSKeyValueCoding)anObject).valueForKey( aKey ); - } - return DefaultImplementation.valueForKey( anObject, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public void takeValueForKey( - Object anObject, Object aValue, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).takeValueForKey( aValue, aKey ); - } - DefaultImplementation.takeValueForKey( anObject, aValue, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public Object storedValueForKey( - Object anObject, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - return ((NSKeyValueCoding)anObject).storedValueForKey( aKey ); - } - return DefaultImplementation.storedValueForKey( anObject, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public void takeStoredValueForKey( - Object anObject, Object aValue, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).takeStoredValueForKey( aValue, aKey ); - } - DefaultImplementation.takeStoredValueForKey( anObject, aValue, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public Object handleQueryWithUnboundKey( - Object anObject, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - return ((NSKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey ); - } - return DefaultImplementation.handleQueryWithUnboundKey( anObject, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public void handleTakeValueForUnboundKey( - Object anObject, Object aValue, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).handleTakeValueForUnboundKey( aValue, aKey ); - } - DefaultImplementation.handleTakeValueForUnboundKey( anObject, aValue, aKey ); - } - - /** - * Calls the appropriate method if the object implements - * NSKeyValueCoding, otherwise calls the method - * on DefaultImplementation. - */ - static public void unableToSetNullForKey( - Object anObject, String aKey ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); - } - DefaultImplementation.unableToSetNullForKey( anObject, aKey ); - } - } - - public class DefaultImplementation - { - /** - * Returns the value for the specified property key - * on the specified object.

- * - * If the property does not exist, this method calls - * handleQueryWithUnboundKey on the object if it - * implements NSKeyValueCoding, otherwise calls - * handleQueryWithUnboundKey on this class.

- */ - static public Object valueForKey( - Object anObject, String aKey ) - { - if ( anObject == null ) return null; - //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 NSKeyValueCoding ) - { - return ((NSKeyValueCoding)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 NSKeyValueCoding, 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 NSKeyValueCoding, - * otherwise calls unableToSetNullForKey on this class. - */ - static public void takeValueForKey( - Object anObject, Object aValue, String aKey ) - { - if ( anObject == null ) return; - //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 NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); - } - else - { - unableToSetNullForKey( anObject, aKey ); - } - } - catch ( MissingPropertyException exc ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)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 NSKeyValueCoding. - * - * 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 NSKeyValueCoding. - * - * 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 NSKeyValueCoding. - * - * 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 ); - } - } + /** + * 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); + + /** + * Static utility methods that call the appropriate method if the object + * implements NSKeyValueCoding, otherwise calls the method on + * DefaultImplementation. + */ + public class Utility { + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public Object valueForKey(Object anObject, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + return ((NSKeyValueCoding) anObject).valueForKey(aKey); + } + return DefaultImplementation.valueForKey(anObject, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public void takeValueForKey(Object anObject, Object aValue, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).takeValueForKey(aValue, aKey); + } + DefaultImplementation.takeValueForKey(anObject, aValue, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public Object storedValueForKey(Object anObject, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + return ((NSKeyValueCoding) anObject).storedValueForKey(aKey); + } + return DefaultImplementation.storedValueForKey(anObject, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public void takeStoredValueForKey(Object anObject, Object aValue, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).takeStoredValueForKey(aValue, aKey); + } + DefaultImplementation.takeStoredValueForKey(anObject, aValue, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public Object handleQueryWithUnboundKey(Object anObject, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + return ((NSKeyValueCoding) anObject).handleQueryWithUnboundKey(aKey); + } + return DefaultImplementation.handleQueryWithUnboundKey(anObject, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public void handleTakeValueForUnboundKey(Object anObject, Object aValue, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).handleTakeValueForUnboundKey(aValue, aKey); + } + DefaultImplementation.handleTakeValueForUnboundKey(anObject, aValue, aKey); + } + + /** + * Calls the appropriate method if the object implements NSKeyValueCoding, + * otherwise calls the method on DefaultImplementation. + */ + static public void unableToSetNullForKey(Object anObject, String aKey) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).unableToSetNullForKey(aKey); + } + DefaultImplementation.unableToSetNullForKey(anObject, aKey); + } + } + + public class DefaultImplementation { + /** + * Returns the value for the specified property key on the specified object. + *
+ *
+ * + * If the property does not exist, this method calls handleQueryWithUnboundKey + * on the object if it implements NSKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class.
+ *
+ */ + static public Object valueForKey(Object anObject, String aKey) { + if (anObject == null) + return null; + // 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 NSKeyValueCoding) { + return ((NSKeyValueCoding) 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 NSKeyValueCoding, + * 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 NSKeyValueCoding, otherwise calls + * unableToSetNullForKey on this class. + */ + static public void takeValueForKey(Object anObject, Object aValue, String aKey) { + if (anObject == null) + return; + // 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 NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).unableToSetNullForKey(aKey); + } else { + unableToSetNullForKey(anObject, aKey); + } + } catch (MissingPropertyException exc) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) 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 NSKeyValueCoding. + * + * 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 NSKeyValueCoding. + * + * 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 NSKeyValueCoding. + * + * 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); + } + } public class Null { public Null() { @@ -406,26 +325,19 @@ public interface NSKeyValueCoding } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2003/08/07 02:43:56 chochos - * added NullValue, which points to a Null instance. + * Revision 1.3 2003/08/07 02:43:56 chochos added NullValue, which points to a + * Null instance. * - * Revision 1.2 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.2 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.1 2003/01/17 14:40:49 mpowers - * Adding files to fix build. + * Revision 1.1 2003/01/17 14:40:49 mpowers Adding files to fix build. * - * Revision 1.2 2001/03/28 16:12:30 mpowers - * Documented interface. + * 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. + * Revision 1.1 2001/03/27 23:25:05 mpowers Contributing interface, no docs yet. * * */ - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java index 785facd..39428ad 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java @@ -22,192 +22,161 @@ import java.util.List; import java.util.Map; /** -* NSKeyValueCodingAdditions defines an interface for classes -* that need to have more control over the wotonomy's bulk -* property copying and cloning facilities.

-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public interface NSKeyValueCodingAdditions extends NSKeyValueCoding -{ - /** - * Returns the value for the specified key path, which is - * a series of keys delimited by ".", for example: - * "createTime.year.length". - */ - Object valueForKeyPath( String aKeyPath ); - - /** - * 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. - */ - void takeValueForKeyPath( Object aValue, String aKeyPath ); - - /** - * Returns a Map of the specified keys to their values, - * each of which might be obtained by calling valueForKey. - */ - NSDictionary valuesForKeys( List aKeyList ); - - /** - * Takes the keys from the specified map as properties - * and applies the corresponding values, each of which - * might be set by calling takeValueForKey. - */ - void takeValuesFromDictionary( Map aMap ); - - - /** - * Static utility methods that - * call the appropriate method if the object implements - * NSKeyValueCodingAdditions, otherwise calls the method - * on DefaultImplementation. - */ - public class 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 takeValueForKeyPath( - Object object, Object aValue, String aKeyPath) - { - if (object instanceof NSKeyValueCodingAdditions) { - ((NSKeyValueCodingAdditions)object).takeValueForKeyPath(aValue, aKeyPath); - } else { - DefaultImplementation.takeValueForKeyPath(object, aValue, aKeyPath); - } - } - - /** - * 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 Object valueForKeyPath( - Object object, String aKeyPath) - { - if (object instanceof NSKeyValueCodingAdditions) { - return ((NSKeyValueCodingAdditions)object).valueForKeyPath(aKeyPath); - } else { - return DefaultImplementation.valueForKeyPath(object, aKeyPath); - } - } - } - - /** - * Provides a reflection-based implementation for classes that - * don't implement NSKeyValueCodingAdditions. - */ - public class DefaultImplementation - { - /** - * Provides a reflection-based implementation for classes that - * don't implement NSKeyValueCodingAdditions. - */ - public static void takeValuesFromDictionary( - Object object, Map dictionary) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Provides a reflection-based implementation for classes that - * don't implement NSKeyValueCodingAdditions. - */ - public static void takeValueForKeyPath( - Object object, Object aValue, String aKeyPath) - { - // currently, NSKeyValueCoding.takeValueForKey accepts paths - NSKeyValueCoding.DefaultImplementation.takeValueForKey( object, aValue, aKeyPath ); - } - - /** - * Provides a reflection-based implementation for classes that - * don't implement NSKeyValueCodingAdditions. - */ - public static NSDictionary valuesForKeys( - Object object, List keys) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Provides a reflection-based implementation for classes that - * don't implement NSKeyValueCodingAdditions. - */ - public static Object valueForKeyPath( - Object object, String aKeyPath) - { - // currently, NSKeyValueCoding.valueForKey accepts paths - return NSKeyValueCoding.DefaultImplementation.valueForKey( object, aKeyPath ); - } - } + * NSKeyValueCodingAdditions defines an interface for classes that need to have + * more control over the wotonomy's bulk property copying and cloning + * facilities.
+ *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public interface NSKeyValueCodingAdditions extends NSKeyValueCoding { + /** + * Returns the value for the specified key path, which is a series of keys + * delimited by ".", for example: "createTime.year.length". + */ + Object valueForKeyPath(String aKeyPath); + + /** + * 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. + */ + void takeValueForKeyPath(Object aValue, String aKeyPath); + + /** + * Returns a Map of the specified keys to their values, each of which might be + * obtained by calling valueForKey. + */ + NSDictionary valuesForKeys(List aKeyList); + + /** + * Takes the keys from the specified map as properties and applies the + * corresponding values, each of which might be set by calling takeValueForKey. + */ + void takeValuesFromDictionary(Map aMap); + + /** + * Static utility methods that call the appropriate method if the object + * implements NSKeyValueCodingAdditions, otherwise calls the method on + * DefaultImplementation. + */ + public class 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 takeValueForKeyPath(Object object, Object aValue, String aKeyPath) { + if (object instanceof NSKeyValueCodingAdditions) { + ((NSKeyValueCodingAdditions) object).takeValueForKeyPath(aValue, aKeyPath); + } else { + DefaultImplementation.takeValueForKeyPath(object, aValue, aKeyPath); + } + } + + /** + * 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 Object valueForKeyPath(Object object, String aKeyPath) { + if (object instanceof NSKeyValueCodingAdditions) { + return ((NSKeyValueCodingAdditions) object).valueForKeyPath(aKeyPath); + } else { + return DefaultImplementation.valueForKeyPath(object, aKeyPath); + } + } + } + + /** + * Provides a reflection-based implementation for classes that don't implement + * NSKeyValueCodingAdditions. + */ + public class DefaultImplementation { + /** + * Provides a reflection-based implementation for classes that don't implement + * NSKeyValueCodingAdditions. + */ + public static void takeValuesFromDictionary(Object object, Map dictionary) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Provides a reflection-based implementation for classes that don't implement + * NSKeyValueCodingAdditions. + */ + public static void takeValueForKeyPath(Object object, Object aValue, String aKeyPath) { + // currently, NSKeyValueCoding.takeValueForKey accepts paths + NSKeyValueCoding.DefaultImplementation.takeValueForKey(object, aValue, aKeyPath); + } + + /** + * Provides a reflection-based implementation for classes that don't implement + * NSKeyValueCodingAdditions. + */ + public static NSDictionary valuesForKeys(Object object, List keys) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Provides a reflection-based implementation for classes that don't implement + * NSKeyValueCodingAdditions. + */ + public static Object valueForKeyPath(Object object, String aKeyPath) { + // currently, NSKeyValueCoding.valueForKey accepts paths + return NSKeyValueCoding.DefaultImplementation.valueForKey(object, aKeyPath); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.2 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.1 2003/01/17 14:40:49 mpowers - * Adding files to fix build. + * Revision 1.1 2003/01/17 14:40:49 mpowers Adding files to fix build. * - * Revision 1.3 2001/12/10 15:25:11 mpowers - * Now properly extending NSKeyValueCoding. + * Revision 1.3 2001/12/10 15:25:11 mpowers Now properly extending + * NSKeyValueCoding. * - * Revision 1.2 2001/04/28 14:12:23 mpowers - * Refactored cloning/copying into KeyValueCodingUtilities. + * 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.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.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. + * Revision 1.1 2001/03/27 23:25:05 mpowers Contributing interface, no docs yet. * * */ - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java index a947896..8fa4646 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java @@ -25,204 +25,154 @@ import net.wotonomy.foundation.internal.NullPrimitiveException; import net.wotonomy.foundation.internal.WotonomyException; /** -* NSKeyValueCodingSupport defines default behavior for -* classes implementing NSKeyValueSupport.

-* -* On an object that does not implement NSKeyValueCoding, -* wotonomy will call the methods on this class directly. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ -public class NSKeyValueCodingSupport -{ - /** - * Returns the value for the specified property key - * on the specified object.

- * - * If the property does not exist, this method calls - * handleQueryWithUnboundKey on the object if it - * implements NSKeyValueCoding, otherwise calls - * handleQueryWithUnboundKey on this class.

- */ - 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 NSKeyValueCoding ) - { - return ((NSKeyValueCoding)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 NSKeyValueCoding, 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 NSKeyValueCoding, - * 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 NSKeyValueCoding ) - { - ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); - } - else - { - unableToSetNullForKey( anObject, aKey ); - } - } - catch ( MissingPropertyException exc ) - { - if ( anObject instanceof NSKeyValueCoding ) - { - ((NSKeyValueCoding)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 NSKeyValueCoding. - * - * 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 NSKeyValueCoding. - * - * 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 NSKeyValueCoding. - * - * 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 ); - } + * NSKeyValueCodingSupport defines default behavior for classes implementing + * NSKeyValueSupport.
+ *
+ * + * On an object that does not implement NSKeyValueCoding, wotonomy will call the + * methods on this class directly. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ +public class NSKeyValueCodingSupport { + /** + * Returns the value for the specified property key on the specified object. + *
+ *
+ * + * If the property does not exist, this method calls handleQueryWithUnboundKey + * on the object if it implements NSKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class.
+ *
+ */ + 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 NSKeyValueCoding) { + return ((NSKeyValueCoding) 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 NSKeyValueCoding, + * 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 NSKeyValueCoding, 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 NSKeyValueCoding) { + ((NSKeyValueCoding) anObject).unableToSetNullForKey(aKey); + } else { + unableToSetNullForKey(anObject, aKey); + } + } catch (MissingPropertyException exc) { + if (anObject instanceof NSKeyValueCoding) { + ((NSKeyValueCoding) 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 NSKeyValueCoding. + * + * 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 NSKeyValueCoding. + * + * 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 NSKeyValueCoding. + * + * 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.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/17 14:40:50 mpowers - * Adding files to fix build. + * Revision 1.1 2003/01/17 14:40:50 mpowers Adding files to fix build. * - * Revision 1.4 2001/05/18 21:04:33 mpowers - * Reimplemented EditingContext.initializeObject. + * 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.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.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 NSKeyValueCodingSupport. + * Revision 1.1 2001/03/28 17:49:33 mpowers Implemented NSKeyValueCodingSupport. * * */ - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java index e6a5147..45afdde 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java @@ -24,85 +24,78 @@ package net.wotonomy.foundation; import EDU.oswego.cs.dl.util.concurrent.Mutex; /** -* A simple mutually-exclusive lock. Currently an API-compliance -* subclass of an external Mutex class, conforming to the API and -* behavior of com.webobjects.foundation.NSLock. This class implements -* the NSLocking protocol (interface), and is implemented using -* Doug Lea's concurrent programming package. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -* -*/ + * A simple mutually-exclusive lock. Currently an API-compliance subclass of an + * external Mutex class, conforming to the API and behavior of + * com.webobjects.foundation.NSLock. This class implements the NSLocking + * protocol (interface), and is implemented using Doug Lea's concurrent + * programming package. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + * + */ public class NSLock extends Mutex implements NSLocking { - - public NSLock() { - } - - public synchronized void lock() { - try { - acquire(); - } catch (InterruptedException interruptedexception) { - notify(); - } - } - - public synchronized void unlock() { - release(); - } - - public synchronized boolean tryLock() { - return tryLock(0); - } - - public synchronized boolean tryLock(long l) { - try { - return attempt(l); - } catch (InterruptedException interruptedexception) { - notify(); - return false; - } - } - - public boolean tryLock(NSTimestamp nstimestamp) { - return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); - } - - public String toString() { + public NSLock() { + } + + public synchronized void lock() { + try { + acquire(); + } catch (InterruptedException interruptedexception) { + notify(); + } + } + + public synchronized void unlock() { + release(); + } + + public synchronized boolean tryLock() { + return tryLock(0); + } + + public synchronized boolean tryLock(long l) { + try { + return attempt(l); + } catch (InterruptedException interruptedexception) { + notify(); + return false; + } + } + + public boolean tryLock(NSTimestamp nstimestamp) { + return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); + } + + public String toString() { return getClass().getName() + " <" + (inuse_ ? "Locked" : "Unlocked") + ">"; - } + } - public synchronized boolean isLocked() { + public synchronized boolean isLocked() { return inuse_; - } + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.4 2002/06/25 19:03:02 cgruber - * Internal documentation fixes. + * Revision 1.4 2002/06/25 19:03:02 cgruber Internal documentation fixes. * - * Revision 1.3 2002/06/25 18:52:56 cgruber - * Fix javadocs that resulted from bad cut-and-paste of the - * boilerplate. + * Revision 1.3 2002/06/25 18:52:56 cgruber Fix javadocs that resulted from bad + * cut-and-paste of the boilerplate. * - * Revision 1.2 2002/06/25 17:45:52 cgruber - * Add implementation of NSLock using Doug Lea's concurrent - * programming APIs. + * Revision 1.2 2002/06/25 17:45:52 cgruber Add implementation of NSLock using + * Doug Lea's concurrent programming APIs. * - * Revision 1.1 2002/06/25 07:52:57 cgruber - * Add quite a few abstract classes, interfaces, and classes. All - * API consistent with WebObjects, but with no implementation, nor - * any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:57 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java index f8c8ff4..d5925b5 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java @@ -22,38 +22,35 @@ $Id: NSLocking.java 892 2006-02-16 12:47:16Z cgruber $ package net.wotonomy.foundation; /** -* Defines a simple locking protocol. Very course-grain locking. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ + * Defines a simple locking protocol. Very course-grain locking. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ public interface NSLocking { - public static final long OneSecond = 1000L; - public static final long OneMinute = 60000L; - public static final long OneHour = 0x36ee80L; - public static final long OneDay = 0x5265c00L; - public static final long OneWeek = 0x240c8400L; - public static final long OneYear = 0x758f0dfc0L; - public static final long OneCentury = 0x2debe176700L; + public static final long OneSecond = 1000L; + public static final long OneMinute = 60000L; + public static final long OneHour = 0x36ee80L; + public static final long OneDay = 0x5265c00L; + public static final long OneWeek = 0x240c8400L; + public static final long OneYear = 0x758f0dfc0L; + public static final long OneCentury = 0x2debe176700L; - public abstract void lock(); + public abstract void lock(); - public abstract void unlock(); + public abstract void unlock(); } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.2 2002/06/21 22:11:19 cgruber - * Add a log trail + * Revision 1.2 2002/06/21 22:11:19 cgruber Add a log trail * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java index 52e4090..b447bd3 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java @@ -24,466 +24,399 @@ import java.io.PrintStream; import java.util.Date; /** -* NSLog is foundation's built-in logging facility: IMPLEMENTED, BUT NOT TESTED. -* By default, all groups are enabled, and debug level is DebugLevelOff. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSLog -{ - public static long DebugGroupApplicationGeneration = 1L << 3; - public static long DebugGroupArchiving = 1L << 6; - public static long DebugGroupAssociations = 1L << 19; - public static long DebugGroupComponentBindings = 1L << 9; - public static long DebugGroupControllers = 1L << 20; - public static long DebugGroupComponents = 1L << 26; - public static long DebugGroupDatabaseAccess = 1L << 16; - public static long DebugGroupDeployment = 1L << 22; - public static long DebugGroupEnterpriseObjects = 1L << 1; - public static long DebugGroupFormatting = 1L << 10; - public static long DebugGroupIO = 1L << 13; - public static long DebugGroupJSPServlets = 1L << 27; - public static long DebugGroupKeyValueCoding = 1L << 8; - public static long DebugGroupModel = 1L << 15; - public static long DebugGroupMultithreading = 1L << 4; - public static long DebugGroupParsing = 1L << 23; - public static long DebugGroupQualifiers = 1L << 11; - public static long DebugGroupReflection = 1L << 24; - public static long DebugGroupRequestHandling = 1L << 25; - public static long DebugGroupResources = 1L << 5; - public static long DebugGroupRules = 1L << 21; - public static long DebugGroupSQLGeneration = 1L << 17; - public static long DebugGroupTiming = 1L << 14; - public static long DebugGroupUserInterface = 1L << 18; - public static long DebugGroupValidation = 1L << 7; - public static long DebugGroupWebObjects = 1L << 2; - public static long DebugGroupWebServices = 1L << 2; - - public static int DebugLevelOff = 0; - public static int DebugLevelCritical = 1; - public static int DebugLevelInformational = 2; - public static int DebugLevelDetailed = 3; - - /** - * The logger to which debug statements should be - * conditionally written. By default, these messages - * appear on the standard error stream. - */ - public static Logger debug; - - /** - * The logger to which error messages should be written, - * which may not always be user-visible. By default, - * these messages appear on the standard error stream. - */ - public static Logger err; - - /** - * The logger to which user-visible messages should be written. - * By default, these messages appear on the standard output stream. - */ - public static Logger out; - - private static long allowedGroups; - private static int allowedLevel; - - static - { - debug = new PrintStreamLogger( System.err ); - err = new PrintStreamLogger( System.err ); - out = new PrintStreamLogger( System.out ); - - //TODO: need to initialize the debug level and groups based - // on the value of the NSDebugLevel and NSDebugGroup properties - allowedGroups = Long.MAX_VALUE; - allowedLevel = 0; - } - - /** - * Adds the specified group masks to those allowed for logging. - */ - public static void allowDebugLoggingForGroups(long aDebugGroups) - { - allowedGroups = allowedGroups | aDebugGroups; - } - - /** - * Returns the current logging debug level. - */ - public static int allowedDebugLevel() - { - return allowedLevel; - } - - /** - * Returns whether logging is allowed for the specified groups masks. - */ - public static boolean debugLoggingAllowedForGroups(long aDebugGroups) - { - return ( allowedGroups == ( allowedGroups | aDebugGroups ) ); - } - - /** - * Returns whether logging is allowed for the specified level. - */ - public static boolean debugLoggingAllowedForLevel(int aDebugLevel) - { - return ( allowedLevel >= aDebugLevel ); - } - - /** - * Returns whether logging allowed for the specified groups masks - * at the specified level. Convenience method. - */ - public static boolean debugLoggingAllowedForLevelAndGroups(int aDebugLevel, - long aDebugGroups) - { - return ( ( allowedLevel >= aDebugLevel ) - && ( allowedGroups == ( allowedGroups | aDebugGroups ) ) ); - } - - /** - * Convenience to obtain a java PrintStream for the specified file path. - * Returns null if the stream could not be created. - */ - public static PrintStream printStreamForPath(String aPath) - { - try - { - return new PrintStream( new FileOutputStream( aPath ) ); - } - catch ( Throwable t ) - { - } - return null; - } - - /** - * Removes the specified group masks from those allowed for logging. - */ - public static void refuseDebugLoggingForGroups(long aDebugGroups) - { - allowedGroups = ( allowedGroups | aDebugGroups ) ^ aDebugGroups; - } - - /** - * Sets the allowed groups to only those specified by the mask. - */ - public static void setAllowedDebugGroups(long aDebugGroups) - { - allowedGroups = aDebugGroups; - } - - /** - * Sets the current debug level. - */ - public static void setAllowedDebugLevel(int aDebugLevel) - { - allowedLevel = aDebugLevel; - } - - /** - * Sets the current debug logger. - * Does nothing if logger is null. - */ - public static void setDebug(NSLog.Logger logger) - { - if ( logger != null ) - { - debug = logger; - } - } - - /** - * Sets the current error logger. - * Does nothing if logger is null. - */ - public static void setErr(NSLog.Logger logger) - { - if ( logger != null ) - { - err = logger; - } - } - - /** - * Sets the current output logger. - * Does nothing if logger is null. - */ - public static void setOut(NSLog.Logger logger) - { - if ( logger != null ) - { - out = logger; - } - } - - /** - * Convenience to write the throwable's stack trace - * to a string. - */ - public static String throwableAsString(Throwable t) - { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream( os ); - t.printStackTrace( ps ); - return os.toString(); - } - - /** - * The abstract superclass of all Logger implementations. - */ - static abstract public class Logger - { - private boolean enabled; - private boolean verbose; - - /** - * Default constructor sets enabled - * and verbose to true. - */ - public Logger() - { - enabled = true; - verbose = true; - } - - /** - * Convenience to append a Boolean. - */ - public void appendln(boolean aValue) - { - appendln( new Boolean( aValue ) ); - } - - /** - * Convenience to append a Byte. - */ - public void appendln(byte aValue) - { - appendln( new Byte( aValue ) ); - } - - /** - * Convenience to write a String - * comprised of the byte array using - * the default encoding. - */ - public void appendln(byte[] aValue) - { - appendln( new String( aValue ) ); - } - - /** - * Convenience to append a Character. - */ - public void appendln(char aValue) - { - appendln( new Character( aValue ) ); - } - - /** - * Convenience to append a String - * comprised of the character array. - */ - public void appendln(char[] aValue) - { - appendln( new String( aValue ) ); - } - - /** - * Convenience to append a Double. - */ - public void appendln(double aValue) - { - appendln( new Double( aValue ) ); - } - - /** - * Convenience to append a Float. - */ - public void appendln(float aValue) - { - appendln( new Float( aValue ) ); - } - - /** - * Convenience to append a Integer. - */ - public void appendln(int aValue) - { - appendln( new Integer( aValue ) ); - } - - /** - * Convenience to append a Long. - */ - public void appendln(long aValue) - { - appendln( new Long( aValue ) ); - } - - /** - * Convenience to append a Short - */ - public void appendln(short aValue) - { - appendln( new Short( aValue ) ); - } - - /** - * Convenience to append a Throwable. - */ - public void appendln(Throwable aValue) - { - appendln( NSLog.throwableAsString( aValue ) ); - } - - /** - * Writes the object to the log. - */ - public abstract void appendln(Object aValue); - - /** - * Appends a line to the log. - */ - public abstract void appendln(); - - /** - * Flushes any buffered output to the log. - */ - public abstract void flush(); - - /** - * Returns whether the logger is enabled, - * the meaning of which is defined by the - * implementing class. - */ - public boolean isEnabled() - { - return enabled; - } - - /** - * Returns whether the logger is verbose, - * the meaning of which is defined by the - * implementing class. - */ - public boolean isVerbose() - { - return verbose; - } - - /** - * Sets whether the logger is enabled, - * the meaning of which is defined by the - * implementing class. - */ - public void setIsEnabled(boolean aBool) - { - enabled = aBool; - } - - /** - * Sets whether the logger is verbose, - * the meaning of which is defined by the - * implementing class. - */ - public void setIsVerbose(boolean aBool) - { - verbose = aBool; - } - } - - /** - * The default implementation of Logger that writes to a Java - * PrintStream. If not enabled, no output is written. - * If verbose, output is in format: "[time] message". - */ - static public class PrintStreamLogger extends Logger - { - private PrintStream thePrintStream; - - /** - * Constructor takes a PrintStream. - */ - public PrintStreamLogger(PrintStream ps) - { - thePrintStream = ps; - } - - /** - * Sends a newline to the print stream. - */ - public void appendln() - { - if ( isEnabled() ) - { - thePrintStream.println(); - } - } - - /** - * Writes the throwable to the print stream. - */ - public void appendln(Throwable aValue) - { - appendln( NSLog.throwableAsString( aValue ) ); - } - - /** - * Writes aValue.toString to the print stream. - */ - public void appendln(Object aValue) - { - if ( isEnabled() ) - { - if ( isVerbose() ) - { - thePrintStream.print( '[' + new Date().toString() + "] <" + - Thread.currentThread().getName() + "> " ); - } - if ( aValue == null ) aValue = "null"; - thePrintStream.println( aValue.toString() ); - } - } - - /** - * Flushes the print stream. - */ - public void flush() - { - thePrintStream.flush(); - } - - /** - * Returns the current print stream. - */ - public PrintStream printStream() - { - return thePrintStream; - } - - /** - * Replaces the current print stream. - */ - public void setPrintStream(PrintStream aStream) - { - thePrintStream = aStream; - } - } + * NSLog is foundation's built-in logging facility: IMPLEMENTED, BUT NOT TESTED. + * By default, all groups are enabled, and debug level is DebugLevelOff. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSLog { + public static long DebugGroupApplicationGeneration = 1L << 3; + public static long DebugGroupArchiving = 1L << 6; + public static long DebugGroupAssociations = 1L << 19; + public static long DebugGroupComponentBindings = 1L << 9; + public static long DebugGroupControllers = 1L << 20; + public static long DebugGroupComponents = 1L << 26; + public static long DebugGroupDatabaseAccess = 1L << 16; + public static long DebugGroupDeployment = 1L << 22; + public static long DebugGroupEnterpriseObjects = 1L << 1; + public static long DebugGroupFormatting = 1L << 10; + public static long DebugGroupIO = 1L << 13; + public static long DebugGroupJSPServlets = 1L << 27; + public static long DebugGroupKeyValueCoding = 1L << 8; + public static long DebugGroupModel = 1L << 15; + public static long DebugGroupMultithreading = 1L << 4; + public static long DebugGroupParsing = 1L << 23; + public static long DebugGroupQualifiers = 1L << 11; + public static long DebugGroupReflection = 1L << 24; + public static long DebugGroupRequestHandling = 1L << 25; + public static long DebugGroupResources = 1L << 5; + public static long DebugGroupRules = 1L << 21; + public static long DebugGroupSQLGeneration = 1L << 17; + public static long DebugGroupTiming = 1L << 14; + public static long DebugGroupUserInterface = 1L << 18; + public static long DebugGroupValidation = 1L << 7; + public static long DebugGroupWebObjects = 1L << 2; + public static long DebugGroupWebServices = 1L << 2; + + public static int DebugLevelOff = 0; + public static int DebugLevelCritical = 1; + public static int DebugLevelInformational = 2; + public static int DebugLevelDetailed = 3; + + /** + * The logger to which debug statements should be conditionally written. By + * default, these messages appear on the standard error stream. + */ + public static Logger debug; + + /** + * The logger to which error messages should be written, which may not always be + * user-visible. By default, these messages appear on the standard error stream. + */ + public static Logger err; + + /** + * The logger to which user-visible messages should be written. By default, + * these messages appear on the standard output stream. + */ + public static Logger out; + + private static long allowedGroups; + private static int allowedLevel; + + static { + debug = new PrintStreamLogger(System.err); + err = new PrintStreamLogger(System.err); + out = new PrintStreamLogger(System.out); + + // TODO: need to initialize the debug level and groups based + // on the value of the NSDebugLevel and NSDebugGroup properties + allowedGroups = Long.MAX_VALUE; + allowedLevel = 0; + } + + /** + * Adds the specified group masks to those allowed for logging. + */ + public static void allowDebugLoggingForGroups(long aDebugGroups) { + allowedGroups = allowedGroups | aDebugGroups; + } + + /** + * Returns the current logging debug level. + */ + public static int allowedDebugLevel() { + return allowedLevel; + } + + /** + * Returns whether logging is allowed for the specified groups masks. + */ + public static boolean debugLoggingAllowedForGroups(long aDebugGroups) { + return (allowedGroups == (allowedGroups | aDebugGroups)); + } + + /** + * Returns whether logging is allowed for the specified level. + */ + public static boolean debugLoggingAllowedForLevel(int aDebugLevel) { + return (allowedLevel >= aDebugLevel); + } + + /** + * Returns whether logging allowed for the specified groups masks at the + * specified level. Convenience method. + */ + public static boolean debugLoggingAllowedForLevelAndGroups(int aDebugLevel, long aDebugGroups) { + return ((allowedLevel >= aDebugLevel) && (allowedGroups == (allowedGroups | aDebugGroups))); + } + + /** + * Convenience to obtain a java PrintStream for the specified file path. Returns + * null if the stream could not be created. + */ + public static PrintStream printStreamForPath(String aPath) { + try { + return new PrintStream(new FileOutputStream(aPath)); + } catch (Throwable t) { + } + return null; + } + + /** + * Removes the specified group masks from those allowed for logging. + */ + public static void refuseDebugLoggingForGroups(long aDebugGroups) { + allowedGroups = (allowedGroups | aDebugGroups) ^ aDebugGroups; + } + + /** + * Sets the allowed groups to only those specified by the mask. + */ + public static void setAllowedDebugGroups(long aDebugGroups) { + allowedGroups = aDebugGroups; + } + + /** + * Sets the current debug level. + */ + public static void setAllowedDebugLevel(int aDebugLevel) { + allowedLevel = aDebugLevel; + } + + /** + * Sets the current debug logger. Does nothing if logger is null. + */ + public static void setDebug(NSLog.Logger logger) { + if (logger != null) { + debug = logger; + } + } + + /** + * Sets the current error logger. Does nothing if logger is null. + */ + public static void setErr(NSLog.Logger logger) { + if (logger != null) { + err = logger; + } + } + + /** + * Sets the current output logger. Does nothing if logger is null. + */ + public static void setOut(NSLog.Logger logger) { + if (logger != null) { + out = logger; + } + } + + /** + * Convenience to write the throwable's stack trace to a string. + */ + public static String throwableAsString(Throwable t) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(os); + t.printStackTrace(ps); + return os.toString(); + } + + /** + * The abstract superclass of all Logger implementations. + */ + static abstract public class Logger { + private boolean enabled; + private boolean verbose; + + /** + * Default constructor sets enabled and verbose to true. + */ + public Logger() { + enabled = true; + verbose = true; + } + + /** + * Convenience to append a Boolean. + */ + public void appendln(boolean aValue) { + appendln(new Boolean(aValue)); + } + + /** + * Convenience to append a Byte. + */ + public void appendln(byte aValue) { + appendln(new Byte(aValue)); + } + + /** + * Convenience to write a String comprised of the byte array using the default + * encoding. + */ + public void appendln(byte[] aValue) { + appendln(new String(aValue)); + } + + /** + * Convenience to append a Character. + */ + public void appendln(char aValue) { + appendln(new Character(aValue)); + } + + /** + * Convenience to append a String comprised of the character array. + */ + public void appendln(char[] aValue) { + appendln(new String(aValue)); + } + + /** + * Convenience to append a Double. + */ + public void appendln(double aValue) { + appendln(new Double(aValue)); + } + + /** + * Convenience to append a Float. + */ + public void appendln(float aValue) { + appendln(new Float(aValue)); + } + + /** + * Convenience to append a Integer. + */ + public void appendln(int aValue) { + appendln(new Integer(aValue)); + } + + /** + * Convenience to append a Long. + */ + public void appendln(long aValue) { + appendln(new Long(aValue)); + } + + /** + * Convenience to append a Short + */ + public void appendln(short aValue) { + appendln(new Short(aValue)); + } + + /** + * Convenience to append a Throwable. + */ + public void appendln(Throwable aValue) { + appendln(NSLog.throwableAsString(aValue)); + } + + /** + * Writes the object to the log. + */ + public abstract void appendln(Object aValue); + + /** + * Appends a line to the log. + */ + public abstract void appendln(); + + /** + * Flushes any buffered output to the log. + */ + public abstract void flush(); + + /** + * Returns whether the logger is enabled, the meaning of which is defined by the + * implementing class. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Returns whether the logger is verbose, the meaning of which is defined by the + * implementing class. + */ + public boolean isVerbose() { + return verbose; + } + + /** + * Sets whether the logger is enabled, the meaning of which is defined by the + * implementing class. + */ + public void setIsEnabled(boolean aBool) { + enabled = aBool; + } + + /** + * Sets whether the logger is verbose, the meaning of which is defined by the + * implementing class. + */ + public void setIsVerbose(boolean aBool) { + verbose = aBool; + } + } + + /** + * The default implementation of Logger that writes to a Java PrintStream. If + * not enabled, no output is written. If verbose, output is in format: "[time] + * message". + */ + static public class PrintStreamLogger extends Logger { + private PrintStream thePrintStream; + + /** + * Constructor takes a PrintStream. + */ + public PrintStreamLogger(PrintStream ps) { + thePrintStream = ps; + } + + /** + * Sends a newline to the print stream. + */ + public void appendln() { + if (isEnabled()) { + thePrintStream.println(); + } + } + + /** + * Writes the throwable to the print stream. + */ + public void appendln(Throwable aValue) { + appendln(NSLog.throwableAsString(aValue)); + } + + /** + * Writes aValue.toString to the print stream. + */ + public void appendln(Object aValue) { + if (isEnabled()) { + if (isVerbose()) { + thePrintStream.print('[' + new Date().toString() + "] <" + Thread.currentThread().getName() + "> "); + } + if (aValue == null) + aValue = "null"; + thePrintStream.println(aValue.toString()); + } + } + + /** + * Flushes the print stream. + */ + public void flush() { + thePrintStream.flush(); + } + + /** + * Returns the current print stream. + */ + public PrintStream printStream() { + return thePrintStream; + } + + /** + * Replaces the current print stream. + */ + public void setPrintStream(PrintStream aStream) { + thePrintStream = aStream; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/31 22:33:00 mpowers - * Contributing NSLog. + * Revision 1.1 2003/01/31 22:33:00 mpowers Contributing NSLog. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java index abed576..3492141 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java @@ -24,136 +24,134 @@ package net.wotonomy.foundation; import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock; /** -* A Read-Write lock that allows unlimited number of calling threads to -* acquire read locks, but only one thread to acquire a write lock. It -* is also reentrant, allowing each thread to re-acquire it's lock -* recursively. For that reason it is somewhat slower, as there is a -* hash lookup when attempting to acquire and release reader locks. Of -* course a writer lock is quite a bit slower than a reader lock. A -* write lock is mutally exclusive with read locks, though a thread -* that has obtained a read-lock may be promoted to a write lock and -* vice versa when conditions permit. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A Read-Write lock that allows unlimited number of calling threads to acquire + * read locks, but only one thread to acquire a write lock. It is also + * reentrant, allowing each thread to re-acquire it's lock recursively. For that + * reason it is somewhat slower, as there is a hash lookup when attempting to + * acquire and release reader locks. Of course a writer lock is quite a bit + * slower than a reader lock. A write lock is mutally exclusive with read locks, + * though a thread that has obtained a read-lock may be promoted to a write lock + * and vice versa when conditions permit. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public class NSMultiReaderLock extends ReentrantWriterPreferenceReadWriteLock implements NSLocking { - NSMutableDictionary _readerSuspended = new NSMutableDictionary(); - - public NSMultiReaderLock() { - } - - public void lockForReading() { - try { - readerLock_.acquire(); - } catch (InterruptedException interruptedexception) { - // Null behavior, as notify() is already called - // by acquire(); - // We may want to log here. - } - } - - public void unlockForReading() { - readerLock_.release(); - } - - public void lock() { - lockForWriting(); - } - - public void lockForWriting() { - try { - writerLock_.acquire(); - } catch (InterruptedException interruptedexception) { - // Null behavior, as notify() is already called - // by acquire(); - // We may want to log here. - } - } - - public void unlock() { - unlockForWriting(); - } - - public void unlockForWriting() { - writerLock_.release(); - } - - /** @see com.webobjects.foundation.NSMultiReaderLock#suspendReaderLock() */ - public void suspendReaderLocks() { - Thread thisThread = Thread.currentThread(); - Integer suspendedReaders = (Integer)_readerSuspended.get(thisThread); - if (suspendedReaders != null && suspendedReaders.intValue() > 0) return; - // logic is to override startRead / endRead and ensure that the suspension - // isn't improperly stopped. + NSMutableDictionary _readerSuspended = new NSMutableDictionary(); + + public NSMultiReaderLock() { + } + + public void lockForReading() { + try { + readerLock_.acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + public void unlockForReading() { + readerLock_.release(); + } + + public void lock() { + lockForWriting(); + } + + public void lockForWriting() { + try { + writerLock_.acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + public void unlock() { + unlockForWriting(); + } + + public void unlockForWriting() { + writerLock_.release(); + } + + /** @see com.webobjects.foundation.NSMultiReaderLock#suspendReaderLock() */ + public void suspendReaderLocks() { + Thread thisThread = Thread.currentThread(); + Integer suspendedReaders = (Integer) _readerSuspended.get(thisThread); + if (suspendedReaders != null && suspendedReaders.intValue() > 0) + return; + // logic is to override startRead / endRead and ensure that the suspension + // isn't improperly stopped. throw new UnsupportedOperationException("Not Yet Implemented"); - } - - /** @see com.webobjects.foundation.NSMultiReaderLock#retrieveReaderLock() */ - public void retrieveReaderLocks() { - Thread thisThread = Thread.currentThread(); - Integer suspendedReaders = (Integer)_readerSuspended.get(thisThread); - if (suspendedReaders != null && suspendedReaders.intValue() > 0) return; - // logic is to override startRead / endRead and ensure that the suspension - // isn't improperly stopped. - throw new UnsupportedOperationException("Not Yet Implemented"); - } - - public boolean tryLockForWriting() { - try { - return writerLock_.attempt(0); - } catch (InterruptedException interruptedexception) { - // notify() is already called by attempt(); - // We may want to log here. - return false; - } - } - - public boolean tryLockForReading() { - try { - return readerLock_.attempt(0); - } catch (InterruptedException interruptedexception) { - // notify() is already called by attempt(); - // We may want to log here. - return false; - } - } - - public String toString() { + } + + /** @see com.webobjects.foundation.NSMultiReaderLock#retrieveReaderLock() */ + public void retrieveReaderLocks() { + Thread thisThread = Thread.currentThread(); + Integer suspendedReaders = (Integer) _readerSuspended.get(thisThread); + if (suspendedReaders != null && suspendedReaders.intValue() > 0) + return; + // logic is to override startRead / endRead and ensure that the suspension + // isn't improperly stopped. throw new UnsupportedOperationException("Not Yet Implemented"); - } - - protected String _padString(long l, int i) { + } + + public boolean tryLockForWriting() { + try { + return writerLock_.attempt(0); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + + public boolean tryLockForReading() { + try { + return readerLock_.attempt(0); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + + public String toString() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - protected String _padString(String s, int i, boolean flag) { + protected String _padString(long l, int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } + protected String _padString(String s, int i, boolean flag) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.2 2002/06/26 00:40:22 cgruber - * Add implementation, using ReentrantWriterPreferenceReadWriteLock - * as a base. + * Revision 1.2 2002/06/26 00:40:22 cgruber Add implementation, using + * ReentrantWriterPreferenceReadWriteLock as a base. * - * suspendReaderLocks and retreiveReaderLocks is the one - * that's likeliest to be a pain. + * suspendReaderLocks and retreiveReaderLocks is the one that's likeliest to be + * a pain. * - * Revision 1.1 2002/06/25 07:52:57 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:57 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java index f705000..c75de01 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java @@ -24,406 +24,345 @@ import java.util.List; import java.util.ListIterator; /** -* NSMutableArray extends NSArray to allow modification. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSMutableArray extends NSArray -{ - /** - * Returns an NSArray backed by the specified List. - * This is useful to "protect" an internal representation - * that is returned by a method of return type NSArray. - */ - public static NSMutableArray mutableArrayBackedByList( List aList ) - { - return new NSMutableArray( aList, null ); - } - - NSMutableArray( List aList, Object ignored ) // differentiates - { - super( aList, ignored ); - } - + * NSMutableArray extends NSArray to allow modification. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSMutableArray extends NSArray { + /** + * Returns an NSArray backed by the specified List. This is useful to "protect" + * an internal representation that is returned by a method of return type + * NSArray. + */ + public static NSMutableArray mutableArrayBackedByList(List aList) { + return new NSMutableArray(aList, null); + } + + NSMutableArray(List aList, Object ignored) // differentiates + { + super(aList, ignored); + } + /** - * Default constructor returns an empty array. - */ - public NSMutableArray () - { - super(); + * Default constructor returns an empty array. + */ + public NSMutableArray() { + super(); //System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); - } + } /** - * Constructor with a size hint. - */ - public NSMutableArray ( int aSize ) - { - super(); + * Constructor with a size hint. + */ + public NSMutableArray(int aSize) { + super(); //System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); - } + } /** - * Produces an array containing only the specified object. - */ - public NSMutableArray (Object anObject) - { - super( anObject ); + * Produces an array containing only the specified object. + */ + public NSMutableArray(Object anObject) { + super(anObject); //System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); - } + } /** - * Produces an array containing the specified objects. - */ - public NSMutableArray (Object[] anArray) - { - super( anArray ); + * Produces an array containing the specified objects. + */ + public NSMutableArray(Object[] anArray) { + super(anArray); //System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); - } + } /** - * Produces an array containing the objects in the specified collection. - */ - public NSMutableArray (Collection aCollection) - { - super( aCollection ); + * Produces an array containing the objects in the specified collection. + */ + public NSMutableArray(Collection aCollection) { + super(aCollection); //System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); - } + } /** - * Removes the last object from the array. - */ - public void removeLastObject () - { - list.remove( count() - 1 ); - } + * Removes the last object from the array. + */ + public void removeLastObject() { + list.remove(count() - 1); + } /** - * Removes the object at the specified index. - */ - public void removeObjectAtIndex (int index) - { - list.remove( index ); - } + * Removes the object at the specified index. + */ + public void removeObjectAtIndex(int index) { + list.remove(index); + } /** - * Adds all objects in the specified collection. - */ - public void addObjectsFromArray (Collection aCollection) - { - list.addAll( aCollection ); - } + * Adds all objects in the specified collection. + */ + public void addObjectsFromArray(Collection aCollection) { + list.addAll(aCollection); + } /** - * Removes all objects from the array. - */ - public void removeAllObjects () - { - list.clear(); - } + * Removes all objects from the array. + */ + public void removeAllObjects() { + list.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( list.get( i ) ) ) - { - list.remove( i ); - i = i - 1; - max = max - 1; - } - } - } + * 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(list.get(i))) { + list.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 == list.get( i ) ) - { - list.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 == list.get(i)) { + list.remove(i); + i = i - 1; + max = max - 1; + } + } + } /** - * Removes all objects in the specified collection from the array. - */ - public void removeObjectsInArray (Collection aCollection) - { - list.removeAll( aCollection ); - } + * Removes all objects in the specified collection from the array. + */ + public void removeObjectsInArray(Collection aCollection) { + list.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++ ) - { - list.remove( aRange.location() ); - } - } + * 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++) { + list.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; - + * 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() ) - { + 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 ); + int loc = Math.min(otherRange.location(), otherArray.size() - 1); + otherRange = new NSRange(loc, otherArray.size() - loc); } - + Object o; - List subList = list.subList( - currentRange.location(), currentRange.maxRange() ); + List subList = list.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--; + 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++ ) - { - list.add( otherArray.get( i ) ); + for (int i = otherIndex; i < otherRange.maxRange(); i++) { + list.add(otherArray.get(i)); } } /** - * Clears the current array and then populates it with the - * contents of the specified collection. - */ - public void setArray (Collection aCollection) - { - list.clear(); - list.addAll( aCollection ); - } + * Clears the current array and then populates it with the contents of the + * specified collection. + */ + public void setArray(Collection aCollection) { + list.clear(); + list.addAll(aCollection); + } /** - * Sorts this array using the values from the specified selector. - */ - public void sortUsingSelector (NSSelector aSelector) - { - //TODO: implement - throw new UnsupportedOperationException( "Not implemented yet." ); - } + * Sorts this array using the values from the specified selector. + */ + public void sortUsingSelector(NSSelector aSelector) { + // TODO: implement + throw new UnsupportedOperationException("Not implemented yet."); + } /** - * Removes all objects equivalent to the specified object. - */ - public void removeObject (Object anObject) - { - list.remove( anObject ); - } + * Removes all objects equivalent to the specified object. + */ + public void removeObject(Object anObject) { + list.remove(anObject); + } /** - * Removes all occurences of the specified object, - * comparing by reference. - */ - public void removeIdenticalObject (Object anObject) - { - Iterator it = list.iterator(); - while ( it.hasNext() ) - { - if ( it.next() == anObject ) - { - it.remove(); - } - } - } + * Removes all occurences of the specified object, comparing by reference. + */ + public void removeIdenticalObject(Object anObject) { + Iterator it = list.iterator(); + while (it.hasNext()) { + if (it.next() == anObject) { + it.remove(); + } + } + } /** - * Inserts the specified object into this array at the - * specified index. - */ - public void insertObjectAtIndex (Object anObject, int anIndex) - { - list.add( anIndex, anObject ); - } - + * Inserts the specified object into this array at the specified index. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + list.add(anIndex, anObject); + } + /** - * Replaces the object at the specified index with the - * specified object. - */ - public void replaceObjectAtIndex (int anIndex, Object anObject) - { - list.set( anIndex, anObject ); - } + * Replaces the object at the specified index with the specified object. + */ + public void replaceObjectAtIndex(int anIndex, Object anObject) { + list.set(anIndex, anObject); + } /** - * Adds the specified object to the end of this array. - */ - public void addObject (Object anObject) - { - list.add( anObject ); - } - - public Object clone() - { - return new NSMutableArray( list ); - } - - public NSArray immutableClone() { - return new NSArray(this); - } - - public NSMutableArray mutableClone() { - return new NSMutableArray(this); - } - - // interface List: mutators - - public void add(int index, Object element) - { - list.add( index, element ); - } - - public boolean add(Object o) - { - return list.add(o); - } - - public boolean addAll(Collection coll) - { - return list.addAll(coll); - } - - public boolean addAll(int index, Collection c) - { - return list.addAll( index, c ); - } - - public void clear() - { - list.clear(); - } - - public Iterator iterator() - { - return list.iterator(); - } - - public ListIterator listIterator() - { - return list.listIterator(); - } - - public ListIterator listIterator(int index) - { - return list.listIterator(); - } - - public Object remove(int index) - { - return list.remove( index ); - } - - public boolean remove(Object o) - { - return list.remove(o); - } - - public boolean removeAll(Collection coll) - { - return list.removeAll(coll); - } - - public boolean retainAll(Collection coll) - { - return list.retainAll(coll); - } - - public Object set(int index, Object element) - { - return list.set( index, element ); - } - - public List subList(int fromIndex, int toIndex) - { - return list.subList( fromIndex, toIndex ); - } - + * Adds the specified object to the end of this array. + */ + public void addObject(Object anObject) { + list.add(anObject); + } + + public Object clone() { + return new NSMutableArray(list); + } + + public NSArray immutableClone() { + return new NSArray(this); + } + + public NSMutableArray mutableClone() { + return new NSMutableArray(this); + } + + // interface List: mutators + + public void add(int index, Object element) { + list.add(index, element); + } + + public boolean add(Object o) { + return list.add(o); + } + + public boolean addAll(Collection coll) { + return list.addAll(coll); + } + + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + public void clear() { + list.clear(); + } + + public Iterator iterator() { + return list.iterator(); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(); + } + + public Object remove(int index) { + return list.remove(index); + } + + public boolean remove(Object o) { + return list.remove(o); + } + + public boolean removeAll(Collection coll) { + return list.removeAll(coll); + } + + public boolean retainAll(Collection coll) { + return list.retainAll(coll); + } + + public Object set(int index, Object element) { + return list.set(index, element); + } + + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2005/07/13 14:12:44 cgruber - * Add mutableClone() and immutableClone() per. WebObjects 5.3 conformance. + * Revision 1.8 2005/07/13 14:12:44 cgruber Add mutableClone() and + * immutableClone() per. WebObjects 5.3 conformance. * - * Revision 1.7 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2003/01/16 22:47:30 mpowers - * Compatibility changes to support compiling woextensions source. - * (34 out of 56 classes compile!) + * Revision 1.6 2003/01/16 22:47:30 mpowers Compatibility changes to support + * compiling woextensions source. (34 out of 56 classes compile!) * - * Revision 1.5 2003/01/10 19:16:40 mpowers - * Implemented support for page caching. + * Revision 1.5 2003/01/10 19:16:40 mpowers Implemented support for page + * caching. * - * Revision 1.4 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.4 2002/10/24 21:15:36 mpowers New implementations of NSArray and + * subclasses. * - * Revision 1.3 2002/10/24 18:16:30 mpowers - * Now enforcing NSArray's immutable nature. + * Revision 1.3 2002/10/24 18:16:30 mpowers Now enforcing NSArray's immutable + * nature. * - * Revision 1.2 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.2 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java index c677ea7..c24e04d 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java @@ -19,95 +19,82 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation; /** -* A pure java implementation of NSMutableData, which -* is basically an editable wrapper for a byte array. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ -public class NSMutableData - extends NSData -{ - /** - * Default constructor creates a zero-data object. - */ - public NSMutableData () - { - super(); - } - - /** - * Creates an object containing the contents of the specified URL. - */ - public NSMutableData (java.net.URL aURL) - { - super( aURL ); - } - - /** - * Creates an object containing a copy of the contents of the - * specified NSData object. - */ - public NSMutableData (NSData aData) - { - super( aData ); - } - - /** - * Creates an object containing the specified number of bytes - * initialized to all zeroes. - */ - public NSMutableData (int size) - { - super( new byte[size] ); // inits to zeroes - } + * A pure java implementation of NSMutableData, which is basically an editable + * wrapper for a byte array. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ +public class NSMutableData extends NSData { + /** + * Default constructor creates a zero-data object. + */ + public NSMutableData() { + super(); + } + /** + * Creates an object containing the contents of the specified URL. + */ + public NSMutableData(java.net.URL aURL) { + super(aURL); + } /** - * Sets the length of the data to the specified length. - * If shorter, the data is truncated. If longer, the extra - * bytes are initialized to zeroes. - */ - public void setLength (int length) - { - byte[] data = new byte[ length ]; // inits to zeroes - int limit = length; - if (limit > bytes.length) - limit = bytes.length; - for ( int i = 0; i < limit; i++ ) - { - data[i] = this.bytes[ i ]; - } - this.bytes = data; - } + * Creates an object containing a copy of the contents of the specified NSData + * object. + */ + public NSMutableData(NSData aData) { + super(aData); + } /** - * Appends the specified data to the end of this data. - */ - public void appendData (NSData aData) - { - int len = aData.length(); - byte[] data = new byte[ bytes.length + len ]; - - int i; - for ( i = 0; i < bytes.length; i++ ) - { - data[i] = bytes[i]; - } - - byte[] src = aData.bytes( 0, len ); - for ( int j = 0; j < len; j++ ) - { - data[i+j] = src[j]; - } - - bytes = data; - } + * Creates an object containing the specified number of bytes initialized to all + * zeroes. + */ + public NSMutableData(int size) { + super(new byte[size]); // inits to zeroes + } + + /** + * Sets the length of the data to the specified length. If shorter, the data is + * truncated. If longer, the extra bytes are initialized to zeroes. + */ + public void setLength(int length) { + byte[] data = new byte[length]; // inits to zeroes + int limit = length; + if (limit > bytes.length) + limit = bytes.length; + for (int i = 0; i < limit; i++) { + data[i] = this.bytes[i]; + } + this.bytes = data; + } + + /** + * Appends the specified data to the end of this data. + */ + public void appendData(NSData aData) { + int len = aData.length(); + byte[] data = new byte[bytes.length + len]; + + int i; + for (i = 0; i < bytes.length; i++) { + data[i] = bytes[i]; + } + + byte[] src = aData.bytes(0, len); + for (int j = 0; j < len; j++) { + data[i + j] = src[j]; + } + + bytes = data; + } public void appendByte(byte b) { setLength(bytes.length + 1); - bytes[bytes.length-1] = b; + bytes[bytes.length - 1] = b; } public void appendBytes(byte[] b) { @@ -118,50 +105,41 @@ public class NSMutableData } /** - * Increases the size of the byte array by the specified amount. - */ - public void increaseLengthBy (int increment) - { - setLength( length() + increment ); - } + * Increases the size of the byte array by the specified amount. + */ + public void increaseLengthBy(int increment) { + setLength(length() + increment); + } /** - * Sets the bytes in the array within the specified range to zero. - */ - public void resetBytesInRange (NSRange aRange) - { - int loc = aRange.location(); - int max = aRange.maxRange(); - for ( int i = loc; i < max; i++ ) - { - bytes[i] = 0; - } - } + * Sets the bytes in the array within the specified range to zero. + */ + public void resetBytesInRange(NSRange aRange) { + int loc = aRange.location(); + int max = aRange.maxRange(); + for (int i = loc; i < max; i++) { + bytes[i] = 0; + } + } /** - * Copies the data in the specified object to this object, - * completely replacing the previous contents. - */ - public void setData (NSData aData) - { - bytes = aData.bytes( 0, aData.length() ); - } + * Copies the data in the specified object to this object, completely replacing + * the previous contents. + */ + public void setData(NSData aData) { + bytes = aData.bytes(0, aData.length()); + } } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:57:13 chochos - * appendByte(), appendBytes() + * Revision 1.2 2003/08/06 23:57:13 chochos appendByte(), appendBytes() * - * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java index 0b291a7..ba58189 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java @@ -22,145 +22,122 @@ import java.util.Enumeration; import java.util.Map; /** -* A pure java implementation of NSMutableDictionary that -* implements Map for greater java interoperability. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSMutableDictionary - extends NSDictionary -{ - /** - * Default constructor produces an empty dictionary. - */ - public NSMutableDictionary () - { - super(); - } - - /** - * Default constructor produces an empty dictionary. - */ - public NSMutableDictionary (int initialSize) - { - super(initialSize); - } - - /** - * Produces a dictionary that contains one key referencing one value. - */ - public NSMutableDictionary (Object key, Object value) - { - super( key, value ); - } - - /** - * Produces a dictionary containing the specified keys and values. - * An IllegalArgumentException is thrown if the arrays are not - * of the same length. - */ - public NSMutableDictionary (Object[] keys, Object[] values) - { - super( keys, values ); + * A pure java implementation of NSMutableDictionary that implements Map for + * greater java interoperability. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSMutableDictionary extends NSDictionary { + /** + * Default constructor produces an empty dictionary. + */ + public NSMutableDictionary() { + super(); + } + + /** + * Default constructor produces an empty dictionary. + */ + public NSMutableDictionary(int initialSize) { + super(initialSize); + } + + /** + * Produces a dictionary that contains one key referencing one value. + */ + public NSMutableDictionary(Object key, Object value) { + super(key, value); + } + + /** + * Produces a dictionary containing the specified keys and values. An + * IllegalArgumentException is thrown if the arrays are not of the same length. + */ + public NSMutableDictionary(Object[] keys, Object[] values) { + super(keys, values); } - /** - * Produces a dictionary that is a copy of the specified map (or dictionary). - */ - public NSMutableDictionary (Map aMap) - { - super( aMap ); - } - - /** - * Removes the key-value pair for the specified key. - */ - public void removeObjectForKey (Object aKey) - { - remove( aKey ); - } - - /** - * Copies all mappings from the specified dictionary to this dictionary, - * replacing any mappings this map had for any keys in the specified map. - */ - public void addEntriesFromDictionary (Map aMap) - { - putAll( aMap ); - } - - /** - * Removes all mappings from this dictionary. - */ - public void removeAllObjects () - { - clear(); - } - - /** - * Removes all keys in the specified array from this dictionary. - */ - public void removeObjectsForKeys (NSArray anArray) - { - Enumeration enumeration = anArray.objectEnumerator(); - while ( enumeration.hasMoreElements() ) - { - removeObjectForKey( enumeration.nextElement() ); - } - } - - /** - * Clears all mappings in this dictionary and then adds all entries - * in the specified dictionary. - */ - public void setDictionary (Map aMap) - { - removeAllObjects(); - addEntriesFromDictionary( aMap ); - } - - /** - * Sets the value for the specified key. If the key currently - * exists to the dictionary, the old value is replaced with the - * specified value. An IllegalArgumentException is thrown if - * either the key or value is null. - */ - public void setObjectForKey (Object aValue, Object aKey) - { - if ( ( aKey == null ) || ( aValue == null ) ) - { - throw new IllegalArgumentException( - "Cannot use null objects with an NSMutableDictionary." ); - } - put( aKey, aValue ); - } + /** + * Produces a dictionary that is a copy of the specified map (or dictionary). + */ + public NSMutableDictionary(Map aMap) { + super(aMap); + } + + /** + * Removes the key-value pair for the specified key. + */ + public void removeObjectForKey(Object aKey) { + remove(aKey); + } + + /** + * Copies all mappings from the specified dictionary to this dictionary, + * replacing any mappings this map had for any keys in the specified map. + */ + public void addEntriesFromDictionary(Map aMap) { + putAll(aMap); + } + + /** + * Removes all mappings from this dictionary. + */ + public void removeAllObjects() { + clear(); + } + + /** + * Removes all keys in the specified array from this dictionary. + */ + public void removeObjectsForKeys(NSArray anArray) { + Enumeration enumeration = anArray.objectEnumerator(); + while (enumeration.hasMoreElements()) { + removeObjectForKey(enumeration.nextElement()); + } + } + + /** + * Clears all mappings in this dictionary and then adds all entries in the + * specified dictionary. + */ + public void setDictionary(Map aMap) { + removeAllObjects(); + addEntriesFromDictionary(aMap); + } + + /** + * Sets the value for the specified key. If the key currently exists to the + * dictionary, the old value is replaced with the specified value. An + * IllegalArgumentException is thrown if either the key or value is null. + */ + public void setObjectForKey(Object aValue, Object aKey) { + if ((aKey == null) || (aValue == null)) { + throw new IllegalArgumentException("Cannot use null objects with an NSMutableDictionary."); + } + put(aKey, aValue); + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 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 + * 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 2002/06/30 17:16:26 mpowers - * Added new constructor taking an int: thanks cgruber. + * Revision 1.3 2002/06/30 17:16:26 mpowers Added new constructor taking an int: + * thanks cgruber. * * - * Revision 1.2 2001/02/23 23:43:41 mpowers - * Removed ill-advised this. + * Revision 1.2 2001/02/23 23:43:41 mpowers Removed ill-advised this. * - * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java index a0bcda1..2bfb692 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java @@ -19,98 +19,84 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation; /** -* A pure java implementation of NSMutableRange. -* An NSMutableRange is a modifiable NSRange. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSMutableRange extends NSRange -{ - /** - * Default constructor produces an empty range. - */ - public NSMutableRange () - { - super(); - } + * A pure java implementation of NSMutableRange. An NSMutableRange is a + * modifiable NSRange. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSMutableRange extends NSRange { + /** + * Default constructor produces an empty range. + */ + public NSMutableRange() { + super(); + } - /** - * Produces a range that has the same location and length as - * the specified range. - */ - public NSMutableRange (NSRange aRange) - { - super( aRange ); - } + /** + * Produces a range that has the same location and length as the specified + * range. + */ + public NSMutableRange(NSRange aRange) { + super(aRange); + } - /** - * Produces a range with the specified location and length. - */ - public NSMutableRange (int location, int length) - { - super( location, length ); - } + /** + * Produces a range with the specified location and length. + */ + public NSMutableRange(int location, int length) { + super(location, length); + } - /** - * Sets the location of this range. - */ - public void setLocation (int location) - { - loc = location; - } + /** + * Sets the location of this range. + */ + public void setLocation(int location) { + loc = location; + } - /** - * Sets the length of this range. - */ - public void setLength (int length) - { - len = length; - } - - /** - * Modifies this range to be the union of this - * range and the specified range. - */ - public void unionRange (NSRange aRange) - { - NSRange range = rangeByUnioningRange( aRange ); - setLocation( range.location() ); - setLength( range.length() ); - } + /** + * Sets the length of this range. + */ + public void setLength(int length) { + len = length; + } - /** - * Modifies this range to be the intersection of this - * range and the specified range. - */ - public void intersectRange (NSRange aRange) - { - NSRange range = rangeByIntersectingRange( aRange ); - setLocation( range.location() ); - setLength( range.length() ); - } + /** + * Modifies this range to be the union of this range and the specified range. + */ + public void unionRange(NSRange aRange) { + NSRange range = rangeByUnioningRange(aRange); + setLocation(range.location()); + setLength(range.length()); + } - /** - * Returns a copy of this range. - */ - public Object clone () - { - return new NSMutableRange( location(), length() ); - } + /** + * Modifies this range to be the intersection of this range and the specified + * range. + */ + public void intersectRange(NSRange aRange) { + NSRange range = rangeByIntersectingRange(aRange); + setLocation(range.location()); + setLength(range.length()); + } + + /** + * Returns a copy of this range. + */ + public Object clone() { + return new NSMutableRange(location(), length()); + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java index f288d3f..6b091e2 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java @@ -21,144 +21,125 @@ package net.wotonomy.foundation; import java.util.Map; /** -* An NSNotification is a generic message that can be -* dispatched by the NSNotificationCenter. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSNotification -{ - public static boolean showStack = false; - - protected String name; - protected Object object; - protected Map userInfo; - - // for debugging only - private Throwable stackTrace; - - /** - * Default constructor creates a new notification - * with no name, object, or info dictionary. - */ - public NSNotification () - { - this( null, null, null ); - } - - /** - * Constructor specifying name and object. - */ - public NSNotification ( String aName, Object anObject ) - { - this( aName, anObject, null ); - } - - /** - * Constructor specifying name, object, and a Map - * containing application specific information. - */ - public NSNotification ( - String aName, Object anObject, Map aUserInfo ) - { - name = aName; - object = anObject; - if ( showStack ) stackTrace = new RuntimeException(); - userInfo = aUserInfo; - } - - /** - * Returns the name of this notification. - */ - public String name () - { - return name; - } - - /** - * Returns the object of this notification. - */ - public Object object () - { - return object; - } - - /** - * Returns an NSDictionary that is a copy of - * the map containing application specific - * information relating to this notification, - * or null if no such data exists. - */ - public NSDictionary userInfo () - { - if ( userInfo == null ) return null; - return new NSDictionary( userInfo ); - } - - /** - * Returns a Map containing application specific - * information relating to this notification, - * or null if no such data exists. - * Note: this method is not in the spec. - */ - public Map userInfoMap () - { - return userInfo; - } - - /** - * Returns the stack trace when this notification was generated, - * or null if showStack is false, which is the default. - * NOTE: This method is not part of the specification. - */ - public Throwable stackTrace() - { - return stackTrace; - } - - /** - * Returns a human-readable string representation. - */ - public String toString() - { - return "[ " + name() + " : " + object() + " : " + userInfo() + " ]"; - } + * An NSNotification is a generic message that can be dispatched by the + * NSNotificationCenter. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSNotification { + public static boolean showStack = false; + + protected String name; + protected Object object; + protected Map userInfo; + + // for debugging only + private Throwable stackTrace; + + /** + * Default constructor creates a new notification with no name, object, or info + * dictionary. + */ + public NSNotification() { + this(null, null, null); + } + + /** + * Constructor specifying name and object. + */ + public NSNotification(String aName, Object anObject) { + this(aName, anObject, null); + } + + /** + * Constructor specifying name, object, and a Map containing application + * specific information. + */ + public NSNotification(String aName, Object anObject, Map aUserInfo) { + name = aName; + object = anObject; + if (showStack) + stackTrace = new RuntimeException(); + userInfo = aUserInfo; + } + + /** + * Returns the name of this notification. + */ + public String name() { + return name; + } + + /** + * Returns the object of this notification. + */ + public Object object() { + return object; + } + + /** + * Returns an NSDictionary that is a copy of the map containing application + * specific information relating to this notification, or null if no such data + * exists. + */ + public NSDictionary userInfo() { + if (userInfo == null) + return null; + return new NSDictionary(userInfo); + } + + /** + * Returns a Map containing application specific information relating to this + * notification, or null if no such data exists. Note: this method is not in the + * spec. + */ + public Map userInfoMap() { + return userInfo; + } + + /** + * Returns the stack trace when this notification was generated, or null if + * showStack is false, which is the default. NOTE: This method is not part of + * the specification. + */ + public Throwable stackTrace() { + return stackTrace; + } + + /** + * Returns a human-readable string representation. + */ + public String toString() { + return "[ " + name() + " : " + object() + " : " + userInfo() + " ]"; + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2002/10/24 18:16:04 mpowers - * No longer generating stack trace by default. + * Revision 1.7 2002/10/24 18:16:04 mpowers No longer generating stack trace by + * default. * - * Revision 1.6 2002/06/21 22:02:47 mpowers - * Oops: Fixed NPE. + * Revision 1.6 2002/06/21 22:02:47 mpowers Oops: Fixed NPE. * - * Revision 1.5 2002/06/21 21:50:41 mpowers - * Added a method to get the map directly from the notification. - * Changed the internal representation to a map not a dictionary. - * We had been creating a new dictionary with each creation. + * Revision 1.5 2002/06/21 21:50:41 mpowers Added a method to get the map + * directly from the notification. Changed the internal representation to a map + * not a dictionary. We had been creating a new dictionary with each creation. * This also allows people to modify the contents of the userInfo. * - * Revision 1.4 2001/04/09 21:41:49 mpowers - * Better debugging. + * Revision 1.4 2001/04/09 21:41:49 mpowers Better debugging. * - * Revision 1.3 2001/02/21 18:31:07 mpowers - * Finished and tested implementation of NSNotificationCenter. + * Revision 1.3 2001/02/21 18:31:07 mpowers Finished and tested implementation + * of NSNotificationCenter. * - * Revision 1.2 2001/02/20 23:57:03 mpowers - * Implemented NSNotificationCenter. + * Revision 1.2 2001/02/20 23:57:03 mpowers Implemented NSNotificationCenter. * - * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java index aaf8261..cf0af53 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java @@ -30,642 +30,502 @@ import java.util.Vector; import net.wotonomy.foundation.internal.WotonomyException; /** -* NSNotificationCenter broadcasts NSNotifications to -* registered observers. Observers can register for all -* notifications of a specific type, or all notifications -* about a specific object, or both. Observers specify -* the method that will be called when they are notified. -* A global notification center can be accessed with -* defaultCenter(), but other centers can be created and -* used independently of the default center.

-* -* 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: 893 $ -*/ -public class NSNotificationCenter -{ - /** - * Null marker class simplifies equals() logic - * for CompoundKey class below. - */ - public static final Object NullMarker = new Object(); - - private static NSNotificationCenter defaultCenter = null; - - /** - * A Map of (name,object) pairs to a List - * of (observer,selector) pairs. - */ - private Hashtable observers; // thread-safe - - /** - * Default constructor creates a new notification center. - */ - public NSNotificationCenter() - { - observers = new Hashtable(); - } - - /** - * Returns the system default center, creating one - * if it has not yet been created. - */ - static public NSNotificationCenter defaultCenter() - { - if ( defaultCenter == null ) - { - defaultCenter = new NSNotificationCenter(); - } - return defaultCenter; - } - + * NSNotificationCenter broadcasts NSNotifications to registered observers. + * Observers can register for all notifications of a specific type, or all + * notifications about a specific object, or both. Observers specify the method + * that will be called when they are notified. A global notification center can + * be accessed with defaultCenter(), but other centers can be created and used + * independently of the default center.
+ *
+ * + * 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: 893 $ + */ +public class NSNotificationCenter { + /** + * Null marker class simplifies equals() logic for CompoundKey class below. + */ + public static final Object NullMarker = new Object(); + + private static NSNotificationCenter defaultCenter = null; + /** - * Addes the specified observer to the notification queue for - * notifications with the specified name or the specified - * object or both. - * @param anObserver The observer that wishes to be notified. - * @param aSelector The selector that will be invoked. - * Must have exactly one argument, to which a notification - * will be passed. - * @param notificationName The name of the notifications for - * which the observer will be notified. If null, will notify - * only based on matching anObject. - * @param anObject The object of the notifications for which - * the observer will be notified. If null, will notify - * only based on matching notificationName. - */ - public void addObserver( - Object anObserver, NSSelector aSelector, - String notificationName, Object anObject ) - { - // remove freed objects - processKeyQueue(); - - Object name = notificationName; - if ( name == null ) - { - name = NullMarker; - } - if ( anObject == null ) - { - anObject = NullMarker; - } - Object key = new CompoundKey( name, anObject ); - Object value = new CompoundValue( anObserver, aSelector ); - List list = (List) observers.get( key ); - if ( list == null ) - { - // create new list with value and put it in map - list = new Vector(); // thread-safe - list.add( value ); - observers.put( new CompoundKey( - name, anObject, keyQueue ), list ); - } - else - { - // add only if not already in list - if ( ! list.contains( value ) ) - { - list.add( value ); - } - } - } - + * A Map of (name,object) pairs to a List of (observer,selector) pairs. + */ + private Hashtable observers; // thread-safe + + /** + * Default constructor creates a new notification center. + */ + public NSNotificationCenter() { + observers = new Hashtable(); + } + + /** + * Returns the system default center, creating one if it has not yet been + * created. + */ + static public NSNotificationCenter defaultCenter() { + if (defaultCenter == null) { + defaultCenter = new NSNotificationCenter(); + } + return defaultCenter; + } + + /** + * Addes the specified observer to the notification queue for notifications with + * the specified name or the specified object or both. + * + * @param anObserver The observer that wishes to be notified. + * @param aSelector The selector that will be invoked. Must have exactly + * one argument, to which a notification will be passed. + * @param notificationName The name of the notifications for which the observer + * will be notified. If null, will notify only based on + * matching anObject. + * @param anObject The object of the notifications for which the + * observer will be notified. If null, will notify only + * based on matching notificationName. + */ + public void addObserver(Object anObserver, NSSelector aSelector, String notificationName, Object anObject) { + // remove freed objects + processKeyQueue(); + + Object name = notificationName; + if (name == null) { + name = NullMarker; + } + if (anObject == null) { + anObject = NullMarker; + } + Object key = new CompoundKey(name, anObject); + Object value = new CompoundValue(anObserver, aSelector); + List list = (List) observers.get(key); + if (list == null) { + // create new list with value and put it in map + list = new Vector(); // thread-safe + list.add(value); + observers.put(new CompoundKey(name, anObject, keyQueue), list); + } else { + // add only if not already in list + if (!list.contains(value)) { + list.add(value); + } + } + } + /** - * Posts the specified notification. Notifies all registered - * observers that match either the notification name or - * the notification object, or both. - * @param aNotification The notification that will be passed - * to the observers selector. - */ - public void postNotification( - NSNotification aNotification ) - { - List mergedList = new LinkedList(); - Object key, observerList; - - Object name = aNotification.name(); - Object object = aNotification.object(); - - if ( name != null ) - { - if ( object != null ) - { // both are specified - observerList = observers.get( new CompoundKey( name, object ) ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - observerList = observers.get( new CompoundKey( name, NullMarker ) ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - observerList = observers.get( new CompoundKey( NullMarker, object ) ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - } - else - { // object is null - observerList = observers.get( new CompoundKey( name, NullMarker ) ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - } - } - else - if ( object != null ) - { // name is null - observerList = observers.get( new CompoundKey( NullMarker, object ) ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - } - - key = new CompoundKey( - NullMarker, NullMarker ); - observerList = observers.get( key ); - if ( observerList != null ) - { - mergedList.addAll( (List) observerList ); - } - - CompoundValue value; - Iterator it = mergedList.iterator(); - while ( it.hasNext() ) - { - value = (CompoundValue) it.next(); - if ( value.get() == null ) - { - it.remove(); - } - else - { - try - { - value.selector().invoke( - value.get(), - new Object[] { aNotification } ); - } - catch ( Exception exc ) - { - WotonomyException w = new WotonomyException( - "Error notifying object: " + value.get() + " : " + aNotification, exc ); + * Posts the specified notification. Notifies all registered observers that + * match either the notification name or the notification object, or both. + * + * @param aNotification The notification that will be passed to the observers + * selector. + */ + public void postNotification(NSNotification aNotification) { + List mergedList = new LinkedList(); + Object key, observerList; + + Object name = aNotification.name(); + Object object = aNotification.object(); + + if (name != null) { + if (object != null) { // both are specified + observerList = observers.get(new CompoundKey(name, object)); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + observerList = observers.get(new CompoundKey(name, NullMarker)); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + observerList = observers.get(new CompoundKey(NullMarker, object)); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + } else { // object is null + observerList = observers.get(new CompoundKey(name, NullMarker)); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + } + } else if (object != null) { // name is null + observerList = observers.get(new CompoundKey(NullMarker, object)); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + } + + key = new CompoundKey(NullMarker, NullMarker); + observerList = observers.get(key); + if (observerList != null) { + mergedList.addAll((List) observerList); + } + + CompoundValue value; + Iterator it = mergedList.iterator(); + while (it.hasNext()) { + value = (CompoundValue) it.next(); + if (value.get() == null) { + it.remove(); + } else { + try { + value.selector().invoke(value.get(), new Object[] { aNotification }); + } catch (Exception exc) { + WotonomyException w = new WotonomyException( + "Error notifying object: " + value.get() + " : " + aNotification, exc); // throw w; - w.printStackTrace(); -postNotification( "Error notifying object", this, new NSDictionary( "exception", w ) ); - } - } - } - - } - + w.printStackTrace(); + postNotification("Error notifying object", this, new NSDictionary("exception", w)); + } + } + } + + } + /** - * Posts a notification created from the specified name - * and object. Calls postNotification( NSNotification ). - * @param notificationName a String key to distinguish - * this notification. - * @param anObject any object, by convention this is - * the originator of the notification. - */ - public void postNotification( - String notificationName, Object anObject ) - { - postNotification( new NSNotification( - notificationName, anObject ) ); - } - + * Posts a notification created from the specified name and object. Calls + * postNotification( NSNotification ). + * + * @param notificationName a String key to distinguish this notification. + * @param anObject any object, by convention this is the originator of + * the notification. + */ + public void postNotification(String notificationName, Object anObject) { + postNotification(new NSNotification(notificationName, anObject)); + } + /** - * Posts a notification created from the specified name, - * object, and info. Calls postNotification( NSNotification ). - * @param notificationName a String key to distinguish - * this notification. - * @param anObject any object, by convention this is - * the originator of the notification. - * @param userInfo a Map containing information specific - * to the originator of the notification and that may - * be of interest to a knowledgable observer. - */ - public void postNotification( - String notificationName, Object anObject, Map userInfo ) - { - postNotification( new NSNotification( - notificationName, anObject, userInfo ) ); - } - + * Posts a notification created from the specified name, object, and info. Calls + * postNotification( NSNotification ). + * + * @param notificationName a String key to distinguish this notification. + * @param anObject any object, by convention this is the originator of + * the notification. + * @param userInfo a Map containing information specific to the + * originator of the notification and that may be of + * interest to a knowledgable observer. + */ + public void postNotification(String notificationName, Object anObject, Map userInfo) { + postNotification(new NSNotification(notificationName, anObject, userInfo)); + } + /** - * Unregisters the specified observer from all notification - * queues for which it is registered. - * @param anObserver The observer to be unregistered. - */ - public void removeObserver( - Object anObserver ) - { - // remove freed objects - processKeyQueue(); - - Iterator it = new LinkedList( observers.keySet() ).iterator(); - while ( it.hasNext() ) - { - removeObserver( anObserver, it.next() ); - } - } - + * Unregisters the specified observer from all notification queues for which it + * is registered. + * + * @param anObserver The observer to be unregistered. + */ + public void removeObserver(Object anObserver) { + // remove freed objects + processKeyQueue(); + + Iterator it = new LinkedList(observers.keySet()).iterator(); + while (it.hasNext()) { + removeObserver(anObserver, it.next()); + } + } + /** - * Unregisters the specified observer from all notifications - * queues associated with the specified name or object or both. - * @param anObserver The observer to be unregistered, if null - * will unregister all observers for the specified notification - * name and object. - * @param notificationName The name of the notification for which - * the observer will be unregistered, if null will unregister - * the specified observer for all notifications with the - * specified object. - * @param anObject The object for the notification for which - * the observer will be unregistered, if null will unregister - * the specified observer for all objects with the specified - * notification. - */ - public void removeObserver( - Object anObserver, String notificationName, Object anObject ) - { - // remove freed objects - processKeyQueue(); - - // get key matches - List keys = matchingKeys( notificationName, anObject ); - - // remove specified observer from each matching key - Iterator it = keys.iterator(); - while ( it.hasNext() ) - { - removeObserver( anObserver, it.next() ); - } - } - - /** - * Returns all keys that match the specified name and object, - * but in this case null parameters are considered wildcards. - * Pass NullMarkers if you want to explicitly match nulls. - */ - private List matchingKeys( String name, Object object ) - { - List result = new LinkedList(); - - boolean willAdd; - CompoundKey key; - Iterator it = observers.keySet().iterator(); - while ( it.hasNext() ) - { - key = (CompoundKey) it.next(); - willAdd = false; - if ( ( name == null ) || ( name == key.name() ) ) - { - if ( ( object == null ) || ( object == key.get() ) ) - { - willAdd = true; - } - } - if ( willAdd ) - { - result.add( key ); - } - } - return result; - } - - /** - * Removes the specified observer from the list referenced - * by the specified key in the observer map. - */ - private void removeObserver( - Object anObserver, Object key ) - { - // if observer null, remove all observers for key - if ( anObserver == null ) - { - observers.remove( key ); - return; - } - - List list = (List) observers.get( key ); - if ( list == null ) return; - - // remove specified observer from list - Object observer; - Iterator it = list.iterator(); - while ( it.hasNext() ) - { - observer = ((CompoundValue)it.next()).get(); - if ( ( observer == null ) || ( anObserver == observer ) ) - { - // remove if match or freed object - it.remove(); - - // do not return; process entire list - } - } - if ( list.size() == 0 ) - { - observers.remove( key ); - } - } - - /* Reference queues for cleared WeakKeys */ - private ReferenceQueue keyQueue = new ReferenceQueue(); - - /** - * Removes any keys whose object has been garbage collected. - * (Garbage collected values are removed as they are encountered.) - */ - private void processKeyQueue() - { - CompoundKey ck; - while ((ck = (CompoundKey)keyQueue.poll()) != null) - { - //System.out.println( "EOObserverCenter.processQueue: removing object" ); - observers.remove(ck); - } - } - - /** - * Key combining a name with an object. - * The object is weakly referenced, and keys - * are deallocated by reference queue. - * equals() compares by reference. - */ - private static class CompoundKey extends WeakReference - { - private Object name; - private int hashCode; - - /** - * Creates compound key. - * Neither name nor object may be null. - * Use NullMarker to represent null - * in either name or object. - */ - public CompoundKey ( - Object aName, Object anObject ) - { - super( anObject ); - name = aName; - hashCode = aName.hashCode() + anObject.hashCode(); - } - - /** - * Creates compound key with queue. - * Neither name nor object may be null. - * Use NullMarker to represent null - * in either name or object. - */ - public CompoundKey ( - Object aName, Object anObject, ReferenceQueue aQueue ) - { - super( anObject, aQueue ); - name = aName; - hashCode = aName.hashCode() + anObject.hashCode(); - } - - public Object name() - { - return name; - } - - public int hashCode() - { - return hashCode; - } - - public boolean equals( Object anObject ) - { - if ( this == anObject ) return true; - // assumes only used with other compound keys - CompoundKey key = (CompoundKey) anObject; - if ( name == key.name || ( name != null && name.equals( key.name ) ) ) - { - Object object = get(); - if ( object != null ) - { - // compares by reference - if ( object == ( key.get() ) ) - { - return true; - } - } - } - return false; - } - - public String toString() - { - return "[CompoundKey:"+name()+":"+get()+"]"; - } - } - - /** - * Value combining an object with a selector. - * The object is weakly referenced, and null - * values are not allowed. - */ - private static class CompoundValue extends WeakReference - { - private NSSelector selector; - private int hashCode; - - public CompoundValue( Object anObject, NSSelector aSelector ) - { - super( anObject ); - hashCode = anObject.hashCode(); - selector = aSelector; - } - - public NSSelector selector() - { - return selector; - } - - public int hashCode() - { - return hashCode; - } - - public boolean equals( Object anObject ) - { - if ( this == anObject ) return true; - // assumes only used with other compound values - CompoundValue value = (CompoundValue) anObject; - if ( selector == value.selector || - ( selector != null && selector.equals( value.selector ) ) ) - { - Object object = get(); - if ( object != null ) - { - if ( object == value.get() ) - { - return true; - } - } - } - return false; - } - - public String toString() - { - return "[CompoundValue:"+get()+":"+selector().name()+"]"; - } - } -/* - public static void main( String[] argv ) - { - Object aSource = "aSource"; - Object bSource = "bSource"; - - Object oneTest = new OneTest(); - Object twoTest = new TwoTest(); - NSSelector notifyMeOnce = - new NSSelector( "notifyMeOnce", - new Class[] { NSNotification.class } ); - NSSelector notifyMeTwice = - new NSSelector( "notifyMeTwice", - new Class[] { NSNotification.class } ); - - NSNotificationCenter.defaultCenter().addObserver( - oneTest, notifyMeOnce, "aMessage", null ); - - NSNotificationCenter.defaultCenter().addObserver( - oneTest, notifyMeOnce, null, aSource ); - - NSNotificationCenter.defaultCenter().addObserver( - twoTest, notifyMeOnce, "aMessage", aSource ); - - NSNotificationCenter.defaultCenter().addObserver( - twoTest, notifyMeTwice, null, null ); - - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", bSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", bSource ); - System.out.println( "---" ); - - NSNotificationCenter.defaultCenter().removeObserver( - oneTest, null, aSource ); - - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", bSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", bSource ); - System.out.println( "---" ); - - NSNotificationCenter.defaultCenter().removeObserver( - null ); - - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "aMessage", bSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", aSource ); - System.out.println(); - NSNotificationCenter.defaultCenter().postNotification( - "bMessage", bSource ); - System.out.println( "---" ); - } - - static private class OneTest - { - public void notifyMeOnce( NSNotification aNotification ) - { - System.out.println( "OneTest.notifyMeOnce: " + aNotification ); - } - } - - static private class TwoTest - { - public void notifyMeOnce( NSNotification aNotification ) - { - System.out.println( "TwoTest.notifyMeOnce: " + aNotification ); - } - public void notifyMeTwice( NSNotification aNotification ) - { - System.out.println( "TwoTest.notifyMeTwice: " + aNotification ); - } - } -*/ -} + * Unregisters the specified observer from all notifications queues associated + * with the specified name or object or both. + * + * @param anObserver The observer to be unregistered, if null will + * unregister all observers for the specified + * notification name and object. + * @param notificationName The name of the notification for which the observer + * will be unregistered, if null will unregister the + * specified observer for all notifications with the + * specified object. + * @param anObject The object for the notification for which the + * observer will be unregistered, if null will + * unregister the specified observer for all objects + * with the specified notification. + */ + public void removeObserver(Object anObserver, String notificationName, Object anObject) { + // remove freed objects + processKeyQueue(); + // get key matches + List keys = matchingKeys(notificationName, anObject); + // remove specified observer from each matching key + Iterator it = keys.iterator(); + while (it.hasNext()) { + removeObserver(anObserver, it.next()); + } + } + + /** + * Returns all keys that match the specified name and object, but in this case + * null parameters are considered wildcards. Pass NullMarkers if you want to + * explicitly match nulls. + */ + private List matchingKeys(String name, Object object) { + List result = new LinkedList(); + + boolean willAdd; + CompoundKey key; + Iterator it = observers.keySet().iterator(); + while (it.hasNext()) { + key = (CompoundKey) it.next(); + willAdd = false; + if ((name == null) || (name == key.name())) { + if ((object == null) || (object == key.get())) { + willAdd = true; + } + } + if (willAdd) { + result.add(key); + } + } + return result; + } + + /** + * Removes the specified observer from the list referenced by the specified key + * in the observer map. + */ + private void removeObserver(Object anObserver, Object key) { + // if observer null, remove all observers for key + if (anObserver == null) { + observers.remove(key); + return; + } + + List list = (List) observers.get(key); + if (list == null) + return; + + // remove specified observer from list + Object observer; + Iterator it = list.iterator(); + while (it.hasNext()) { + observer = ((CompoundValue) it.next()).get(); + if ((observer == null) || (anObserver == observer)) { + // remove if match or freed object + it.remove(); + + // do not return; process entire list + } + } + if (list.size() == 0) { + observers.remove(key); + } + } + + /* Reference queues for cleared WeakKeys */ + private ReferenceQueue keyQueue = new ReferenceQueue(); + + /** + * Removes any keys whose object has been garbage collected. (Garbage collected + * values are removed as they are encountered.) + */ + private void processKeyQueue() { + CompoundKey ck; + while ((ck = (CompoundKey) keyQueue.poll()) != null) { + // System.out.println( "EOObserverCenter.processQueue: removing object" ); + observers.remove(ck); + } + } + + /** + * Key combining a name with an object. The object is weakly referenced, and + * keys are deallocated by reference queue. equals() compares by reference. + */ + private static class CompoundKey extends WeakReference { + private Object name; + private int hashCode; + + /** + * Creates compound key. Neither name nor object may be null. Use NullMarker to + * represent null in either name or object. + */ + public CompoundKey(Object aName, Object anObject) { + super(anObject); + name = aName; + hashCode = aName.hashCode() + anObject.hashCode(); + } + + /** + * Creates compound key with queue. Neither name nor object may be null. Use + * NullMarker to represent null in either name or object. + */ + public CompoundKey(Object aName, Object anObject, ReferenceQueue aQueue) { + super(anObject, aQueue); + name = aName; + hashCode = aName.hashCode() + anObject.hashCode(); + } + + public Object name() { + return name; + } + + public int hashCode() { + return hashCode; + } + + public boolean equals(Object anObject) { + if (this == anObject) + return true; + // assumes only used with other compound keys + CompoundKey key = (CompoundKey) anObject; + if (name == key.name || (name != null && name.equals(key.name))) { + Object object = get(); + if (object != null) { + // compares by reference + if (object == (key.get())) { + return true; + } + } + } + return false; + } + + public String toString() { + return "[CompoundKey:" + name() + ":" + get() + "]"; + } + } + + /** + * Value combining an object with a selector. The object is weakly referenced, + * and null values are not allowed. + */ + private static class CompoundValue extends WeakReference { + private NSSelector selector; + private int hashCode; + + public CompoundValue(Object anObject, NSSelector aSelector) { + super(anObject); + hashCode = anObject.hashCode(); + selector = aSelector; + } + + public NSSelector selector() { + return selector; + } + + public int hashCode() { + return hashCode; + } + + public boolean equals(Object anObject) { + if (this == anObject) + return true; + // assumes only used with other compound values + CompoundValue value = (CompoundValue) anObject; + if (selector == value.selector || (selector != null && selector.equals(value.selector))) { + Object object = get(); + if (object != null) { + if (object == value.get()) { + return true; + } + } + } + return false; + } + + public String toString() { + return "[CompoundValue:" + get() + ":" + selector().name() + "]"; + } + } + /* + * public static void main( String[] argv ) { Object aSource = "aSource"; Object + * bSource = "bSource"; + * + * Object oneTest = new OneTest(); Object twoTest = new TwoTest(); NSSelector + * notifyMeOnce = new NSSelector( "notifyMeOnce", new Class[] { + * NSNotification.class } ); NSSelector notifyMeTwice = new NSSelector( + * "notifyMeTwice", new Class[] { NSNotification.class } ); + * + * NSNotificationCenter.defaultCenter().addObserver( oneTest, notifyMeOnce, + * "aMessage", null ); + * + * NSNotificationCenter.defaultCenter().addObserver( oneTest, notifyMeOnce, + * null, aSource ); + * + * NSNotificationCenter.defaultCenter().addObserver( twoTest, notifyMeOnce, + * "aMessage", aSource ); + * + * NSNotificationCenter.defaultCenter().addObserver( twoTest, notifyMeTwice, + * null, null ); + * + * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "aMessage", bSource ); System.out.println(); + * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "bMessage", bSource ); System.out.println( "---" ); + * + * NSNotificationCenter.defaultCenter().removeObserver( oneTest, null, aSource + * ); + * + * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "aMessage", bSource ); System.out.println(); + * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "bMessage", bSource ); System.out.println( "---" ); + * + * NSNotificationCenter.defaultCenter().removeObserver( null ); + * + * NSNotificationCenter.defaultCenter().postNotification( "aMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "aMessage", bSource ); System.out.println(); + * NSNotificationCenter.defaultCenter().postNotification( "bMessage", aSource ); + * System.out.println(); NSNotificationCenter.defaultCenter().postNotification( + * "bMessage", bSource ); System.out.println( "---" ); } + * + * static private class OneTest { public void notifyMeOnce( NSNotification + * aNotification ) { System.out.println( "OneTest.notifyMeOnce: " + + * aNotification ); } } + * + * static private class TwoTest { public void notifyMeOnce( NSNotification + * aNotification ) { System.out.println( "TwoTest.notifyMeOnce: " + + * aNotification ); } public void notifyMeTwice( NSNotification aNotification ) + * { System.out.println( "TwoTest.notifyMeTwice: " + aNotification ); } } + */ +} /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.11 2003/06/03 14:51:15 mpowers - * Added commented-out println for debugging. + * Revision 1.11 2003/06/03 14:51:15 mpowers Added commented-out println for + * debugging. * - * Revision 1.10 2003/03/27 21:46:00 mpowers - * Better handling for null parameters on subscribe. - * Better handling for null parameters on post. + * Revision 1.10 2003/03/27 21:46:00 mpowers Better handling for null parameters + * on subscribe. Better handling for null parameters on post. * - * Revision 1.9 2003/01/28 19:44:38 mpowers - * Now comparing strings by value not reference. + * Revision 1.9 2003/01/28 19:44:38 mpowers Now comparing strings by value not + * reference. * - * Revision 1.8 2001/06/29 16:14:23 mpowers - * Fixed a javac compiler error that jikes allowed: shoe's on the other foot! + * Revision 1.8 2001/06/29 16:14:23 mpowers Fixed a javac compiler error that + * jikes allowed: shoe's on the other foot! * - * Revision 1.7 2001/06/07 22:09:03 mpowers - * Exceptions during a notification are no longer being thrown - * so we can assure that all notifications get handled. + * Revision 1.7 2001/06/07 22:09:03 mpowers Exceptions during a notification are + * no longer being thrown so we can assure that all notifications get handled. * Instead, we're printing stack traces... * - * Revision 1.6 2001/04/09 21:41:50 mpowers - * Better debugging. + * Revision 1.6 2001/04/09 21:41:50 mpowers Better debugging. * - * Revision 1.5 2001/03/15 21:09:06 mpowers - * Fixed notifications with null objects. + * Revision 1.5 2001/03/15 21:09:06 mpowers Fixed notifications with null + * objects. * - * Revision 1.4 2001/02/21 21:18:34 mpowers - * Clarified need to retain references. + * Revision 1.4 2001/02/21 21:18:34 mpowers Clarified need to retain references. * - * Revision 1.3 2001/02/21 18:31:07 mpowers - * Finished and tested implementation of NSNotificationCenter. + * Revision 1.3 2001/02/21 18:31:07 mpowers Finished and tested implementation + * of NSNotificationCenter. * - * Revision 1.1.1.1 2000/12/21 15:47:39 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:39 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java index 7350a39..81dc6e8 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java @@ -23,323 +23,264 @@ import java.util.LinkedList; import java.util.List; /** -* NSNotificationQueue coalesces notifications to be -* posted to the NSNotificationCenter and can post them -* asynchronously. While calling postNotification on -* the notification center does not return until all -* receivers have been notified, calling enqueueNotification -* can return immediately. Use this class when you want -* to coalesce notifications or notify asynchronously, or -* both, which is the typical case. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSNotificationQueue -{ - private static NSNotificationQueue defaultQueue = null; - - /** - * Posting style specifying that the notification should - * be posted on the next available event loop. - */ - public static final int PostASAP = 4; - - /** - * Posting style specifying that the notification should - * be posted on the next available idle loop. - */ - public static final int PostWhenIdle = 8; - - /** - * Posting style specifying that the notification should - * be posted immediately. The enqueue method will not - * return until all receivers have been notified. - */ - public static final int PostNow = 16; - - /** - * Used to indicate that this notification should not - * be coalesced with other notifications. - * Ignored if combined with other coalesce flags. - */ - public static final int NotificationNoCoalescing = 0; - - /** - * Used to indicate that this notification should - * be coalesced with other notifications with the - * same name. - * May be combined with NotificationCoalescingOnSender. - */ - public static final int NotificationCoalescingOnName = 1; - - /** - * Used to indicate that this notification should - * be coalesced with other notifications with the - * same object argument (which is typically the sender). - * May be combined with NotificationCoalescingOnName. - */ - public static final int NotificationCoalescingOnSender = 2; - - /** - * The ASAP queue, which should probably use a LinkedList. - */ - private List queue; - - /** - * The idle queue, which should probably use a LinkedList. - */ - private List idleQueue; - - /** - * The notification center we will be using. - */ - private NSNotificationCenter center; - - /** - * Our private ASAP notifier. - */ - private Notifier notifier; - - /** - * Our private idle notifier. - */ - private Notifier idleNotifier; - - /** - * Default constructor creates a new notification queue - * that uses the default notification center. - */ - public NSNotificationQueue() - { - this( NSNotificationCenter.defaultCenter() ); - } - - /** - * Creates a new notification queue that uses the - * specified notification center. - */ - public NSNotificationQueue( - NSNotificationCenter aCenter ) - { - queue = new LinkedList(); - idleQueue = new LinkedList(); - center = aCenter; - notifier = new Notifier( this, queue ); - idleNotifier = new Notifier( this, idleQueue ); - } - - /** - * Returns the system default queue, creating one - * if it has not yet been created. The system default - * queue uses the system default notification center. - */ - static public NSNotificationQueue defaultQueue() - { - if ( defaultQueue == null ) - { - defaultQueue = new NSNotificationQueue(); - } - return defaultQueue; - } - - /** - * Removes notifications from the queue that match - * the specified notification, considering the - * specified coalesce mask. - */ - public void dequeueMatchingNotifications( - NSNotification aNotification, - int aCoalesceMask) - { - if ( aCoalesceMask == NotificationNoCoalescing ) return; - dequeueFromQueue( aNotification, aCoalesceMask, queue ); - dequeueFromQueue( aNotification, aCoalesceMask, idleQueue ); - } - - private void dequeueFromQueue( - NSNotification aNotification, - int aCoalesceMask, - List aQueue ) - { - synchronized ( aQueue ) - { - int flag; - NSNotification notification; - Object name = aNotification.name(); - Object object = aNotification.object(); - Iterator it = aQueue.iterator(); - while ( it.hasNext() ) - { - flag = 0; - notification = (NSNotification) it.next(); - // if NotificationCoalescingOnName - if ( ( aCoalesceMask == 1 ) || ( aCoalesceMask == 3 ) ) - { - if ( name == null ) - { - if ( notification.name() != null ) - { - flag += NotificationCoalescingOnName; - } - } - else - { - // compare by value - if ( name.equals( notification.name() ) ) - { - flag += NotificationCoalescingOnName; - } - } - } - // if NotificationCoalescingOnSender - if ( aCoalesceMask >= 2 ) - { - // compare by reference - if ( object == notification.object() ) - { - flag += NotificationCoalescingOnSender; - } - } - - if ( flag == aCoalesceMask ) - { - it.remove(); - } - } - } - } - - /** - * Adds the notification to the queue to be run at - * the time specified by the posting style. The - * notification will be coalesced with other notifications - * that match the same name and object (sender) argument. - */ - public void enqueueNotification( - NSNotification aNotification, - int aPostingStyle) - { - enqueueNotificationWithCoalesceMaskForModes( - aNotification, aPostingStyle, - NotificationCoalescingOnName + NotificationCoalescingOnSender, - null ); - } - - /** - * Adds the notification to the queue to be run at - * the time specified by the posting style and coelesced - * as the specified mask indicates. - * aModeList is currently ignored and may be null. - */ - public void enqueueNotificationWithCoalesceMaskForModes( - NSNotification aNotification, - int aPostingStyle, - int aCoalesceMask, - List aModeList) - { - dequeueMatchingNotifications( aNotification, aCoalesceMask ); - - if ( aPostingStyle == PostNow ) - { - center.postNotification( aNotification ); - return; - } - - if ( aPostingStyle == PostASAP ) - { - synchronized ( queue ) - { - queue.add( aNotification ); - if ( ! notifier.willRun ) - { - // asap runs at the very first run loop ordering, plus one just in case - NSRunLoop.invokeLaterWithOrder( notifier, 1 ); - notifier.willRun = true; - } - } - return; - } - - if ( aPostingStyle == PostWhenIdle ) - { - synchronized ( idleQueue ) - { - idleQueue.add( aNotification ); - if ( ! idleNotifier.willRun ) - { - // when idle runs at the very last run loop ordering, minus one just in case - NSRunLoop.invokeLaterWithOrder( idleNotifier, Integer.MAX_VALUE - 1 ); - idleNotifier.willRun = true; - } - } - return; - } - - } - - private class Notifier implements Runnable - { - public boolean willRun; - - NSNotificationQueue parent; - List queue; - - public Notifier( - NSNotificationQueue aParent, List aQueue ) - { - willRun = false; - parent = aParent; - queue = aQueue; - } - - public void run() - { - Iterator it = new LinkedList( queue ).iterator(); - synchronized ( queue ) - { - queue.clear(); - } - willRun = false; - - // queue must already be cleared and willRun reset - // because this loop might queue more notifications - while ( it.hasNext() ) - { - parent.center.postNotification( - (NSNotification) it.next() ); - } - } - } + * NSNotificationQueue coalesces notifications to be posted to the + * NSNotificationCenter and can post them asynchronously. While calling + * postNotification on the notification center does not return until all + * receivers have been notified, calling enqueueNotification can return + * immediately. Use this class when you want to coalesce notifications or notify + * asynchronously, or both, which is the typical case. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSNotificationQueue { + private static NSNotificationQueue defaultQueue = null; + + /** + * Posting style specifying that the notification should be posted on the next + * available event loop. + */ + public static final int PostASAP = 4; + + /** + * Posting style specifying that the notification should be posted on the next + * available idle loop. + */ + public static final int PostWhenIdle = 8; + + /** + * Posting style specifying that the notification should be posted immediately. + * The enqueue method will not return until all receivers have been notified. + */ + public static final int PostNow = 16; + + /** + * Used to indicate that this notification should not be coalesced with other + * notifications. Ignored if combined with other coalesce flags. + */ + public static final int NotificationNoCoalescing = 0; + + /** + * Used to indicate that this notification should be coalesced with other + * notifications with the same name. May be combined with + * NotificationCoalescingOnSender. + */ + public static final int NotificationCoalescingOnName = 1; + + /** + * Used to indicate that this notification should be coalesced with other + * notifications with the same object argument (which is typically the sender). + * May be combined with NotificationCoalescingOnName. + */ + public static final int NotificationCoalescingOnSender = 2; + + /** + * The ASAP queue, which should probably use a LinkedList. + */ + private List queue; + + /** + * The idle queue, which should probably use a LinkedList. + */ + private List idleQueue; + + /** + * The notification center we will be using. + */ + private NSNotificationCenter center; + + /** + * Our private ASAP notifier. + */ + private Notifier notifier; + + /** + * Our private idle notifier. + */ + private Notifier idleNotifier; + + /** + * Default constructor creates a new notification queue that uses the default + * notification center. + */ + public NSNotificationQueue() { + this(NSNotificationCenter.defaultCenter()); + } + + /** + * Creates a new notification queue that uses the specified notification center. + */ + public NSNotificationQueue(NSNotificationCenter aCenter) { + queue = new LinkedList(); + idleQueue = new LinkedList(); + center = aCenter; + notifier = new Notifier(this, queue); + idleNotifier = new Notifier(this, idleQueue); + } + + /** + * Returns the system default queue, creating one if it has not yet been + * created. The system default queue uses the system default notification + * center. + */ + static public NSNotificationQueue defaultQueue() { + if (defaultQueue == null) { + defaultQueue = new NSNotificationQueue(); + } + return defaultQueue; + } + + /** + * Removes notifications from the queue that match the specified notification, + * considering the specified coalesce mask. + */ + public void dequeueMatchingNotifications(NSNotification aNotification, int aCoalesceMask) { + if (aCoalesceMask == NotificationNoCoalescing) + return; + dequeueFromQueue(aNotification, aCoalesceMask, queue); + dequeueFromQueue(aNotification, aCoalesceMask, idleQueue); + } + + private void dequeueFromQueue(NSNotification aNotification, int aCoalesceMask, List aQueue) { + synchronized (aQueue) { + int flag; + NSNotification notification; + Object name = aNotification.name(); + Object object = aNotification.object(); + Iterator it = aQueue.iterator(); + while (it.hasNext()) { + flag = 0; + notification = (NSNotification) it.next(); + // if NotificationCoalescingOnName + if ((aCoalesceMask == 1) || (aCoalesceMask == 3)) { + if (name == null) { + if (notification.name() != null) { + flag += NotificationCoalescingOnName; + } + } else { + // compare by value + if (name.equals(notification.name())) { + flag += NotificationCoalescingOnName; + } + } + } + // if NotificationCoalescingOnSender + if (aCoalesceMask >= 2) { + // compare by reference + if (object == notification.object()) { + flag += NotificationCoalescingOnSender; + } + } + + if (flag == aCoalesceMask) { + it.remove(); + } + } + } + } + + /** + * Adds the notification to the queue to be run at the time specified by the + * posting style. The notification will be coalesced with other notifications + * that match the same name and object (sender) argument. + */ + public void enqueueNotification(NSNotification aNotification, int aPostingStyle) { + enqueueNotificationWithCoalesceMaskForModes(aNotification, aPostingStyle, + NotificationCoalescingOnName + NotificationCoalescingOnSender, null); + } + + /** + * Adds the notification to the queue to be run at the time specified by the + * posting style and coelesced as the specified mask indicates. aModeList is + * currently ignored and may be null. + */ + public void enqueueNotificationWithCoalesceMaskForModes(NSNotification aNotification, int aPostingStyle, + int aCoalesceMask, List aModeList) { + dequeueMatchingNotifications(aNotification, aCoalesceMask); + + if (aPostingStyle == PostNow) { + center.postNotification(aNotification); + return; + } + + if (aPostingStyle == PostASAP) { + synchronized (queue) { + queue.add(aNotification); + if (!notifier.willRun) { + // asap runs at the very first run loop ordering, plus one just in case + NSRunLoop.invokeLaterWithOrder(notifier, 1); + notifier.willRun = true; + } + } + return; + } + + if (aPostingStyle == PostWhenIdle) { + synchronized (idleQueue) { + idleQueue.add(aNotification); + if (!idleNotifier.willRun) { + // when idle runs at the very last run loop ordering, minus one just in case + NSRunLoop.invokeLaterWithOrder(idleNotifier, Integer.MAX_VALUE - 1); + idleNotifier.willRun = true; + } + } + return; + } + + } + + private class Notifier implements Runnable { + public boolean willRun; + + NSNotificationQueue parent; + List queue; + + public Notifier(NSNotificationQueue aParent, List aQueue) { + willRun = false; + parent = aParent; + queue = aQueue; + } + + public void run() { + Iterator it = new LinkedList(queue).iterator(); + synchronized (queue) { + queue.clear(); + } + willRun = false; + + // queue must already be cleared and willRun reset + // because this loop might queue more notifications + while (it.hasNext()) { + parent.center.postNotification((NSNotification) it.next()); + } + } + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/08/11 18:18:08 chochos - * improved encoding of strings, removed warnings + * Revision 1.6 2003/08/11 18:18:08 chochos improved encoding of strings, + * removed warnings * - * Revision 1.5 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2001/11/01 15:49:26 mpowers - * With NSRunLoop, we can now correctly implement PostASAP and PostWhenIdle. + * Revision 1.4 2001/11/01 15:49:26 mpowers With NSRunLoop, we can now correctly + * implement PostASAP and PostWhenIdle. * - * Revision 1.3 2001/06/25 14:47:24 mpowers - * Fixed serious error where some notifications were not being posted at all. - * A simple change to Notifier class fixed the problem. Thanks to glista. + * Revision 1.3 2001/06/25 14:47:24 mpowers Fixed serious error where some + * notifications were not being posted at all. A simple change to Notifier class + * fixed the problem. Thanks to glista. * - * Revision 1.2 2001/02/26 15:53:22 mpowers - * Fine-tuning notification firing. + * Revision 1.2 2001/02/26 15:53:22 mpowers Fine-tuning notification firing. * Child display groups now update properly after parent save or invalidate. * - * Revision 1.1 2001/02/24 17:03:22 mpowers - * Implemented the notification queue, and changed editing context to use it. + * Revision 1.1 2001/02/24 17:03:22 mpowers Implemented the notification queue, + * and changed editing context to use it. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java index db1f216..861f576 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java @@ -21,84 +21,72 @@ package net.wotonomy.foundation; import java.io.Serializable; /** -* NSNull 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.

-* -* Use of the static singleton method nullValue() is required -* by this implementation because Java cannot return a singleton -* instance from a constructor. Even then, more than one instance -* may exist in the application due to object serialization. -* Be sure to compare with equals(). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ -public class NSNull implements Serializable -{ - private static final NSNull instance = new NSNull(); - - /** - * Create a new instance of NSNull. - */ - private NSNull () - { - } + * NSNull 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.
+ *
+ * + * Use of the static singleton method nullValue() is required by this + * implementation because Java cannot return a singleton instance from a + * constructor. Even then, more than one instance may exist in the application + * due to object serialization. Be sure to compare with equals(). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ +public class NSNull implements Serializable { + private static final NSNull instance = new NSNull(); - /** - * Returns the static instance of nullValue. - * Note that serialization may mean that more than - * one instance of NSNull exists, so be sure to - * compare with equals(). - */ - public static NSNull nullValue () - { - return instance; - } + /** + * Create a new instance of NSNull. + */ + private NSNull() { + } - /** - * 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 NSNull. - */ - public boolean equals( Object anObject ) - { - return ( anObject instanceof NSNull ); - } + /** + * Returns the static instance of nullValue. Note that serialization may mean + * that more than one instance of NSNull exists, so be sure to compare with + * equals(). + */ + public static NSNull 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 NSNull. + */ + public boolean equals(Object anObject) { + return (anObject instanceof NSNull); + } } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 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.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/03/01 20:36:09 mpowers - * Implemented equals, hashcode, and serializable. + * Revision 1.2 2001/03/01 20:36:09 mpowers Implemented equals, hashcode, and + * serializable. * - * Revision 1.1 2001/02/26 22:41:51 mpowers - * Implemented null placeholder classes. - * Duplicator now uses NSNull. - * No longer catching base exception class. + * 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.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java index 064ee3c..7e6386d 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java @@ -24,34 +24,29 @@ package net.wotonomy.foundation; import java.text.DecimalFormat; /** -* A Format that accepts C-style number formatting syntax. -* Not currently implemented, included for compile compatibility. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A Format that accepts C-style number formatting syntax. Not currently + * implemented, included for compile compatibility. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ + +public class NSNumberFormatter extends DecimalFormat { + public NSNumberFormatter() { + super(); + } -public class NSNumberFormatter extends DecimalFormat -{ - public NSNumberFormatter() - { - super(); - } - - public NSNumberFormatter(String aPattern) - { - super( aPattern ); - } + public NSNumberFormatter(String aPattern) { + super(aPattern); + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/17 14:40:50 mpowers - * Adding files to fix build. + * Revision 1.1 2003/01/17 14:40:50 mpowers Adding files to fix build. * * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java index 7f8bcc9..b52dc2d 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java @@ -3,273 +3,287 @@ package net.wotonomy.foundation; public class NSPropertyListSerialization { - public static final int PLIST_ARRAY = 0; - public static final int PLIST_DICTIONARY = 1; - public static final int PLIST_DATA = 2; - public static final int PLIST_STRING = 3; + public static final int PLIST_ARRAY = 0; + public static final int PLIST_DICTIONARY = 1; + public static final int PLIST_DATA = 2; + public static final int PLIST_STRING = 3; - public static final char[] TOKEN_BEGIN = new char[]{ - '(', '{', '<', '"' - }; - public static final char[] TOKEN_END = new char[]{ - ')', '}', '>', '"' - }; - public static final char[] QUOTING_CHARS = new char[]{ - ':', '/', '-', '.', '\\' - }; + public static final char[] TOKEN_BEGIN = new char[] { '(', '{', '<', '"' }; + public static final char[] TOKEN_END = new char[] { ')', '}', '>', '"' }; + public static final char[] QUOTING_CHARS = new char[] { ':', '/', '-', '.', '\\' }; - private NSPropertyListSerialization() { - super(); - } + private NSPropertyListSerialization() { + super(); + } - /** Creates a NSArray object from a string representation. - @s The string representation of a NSArray object. */ - public static NSArray arrayForString(String s) { - s = s.trim(); - if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_ARRAY] && s.charAt(s.length()-1) == TOKEN_END[PLIST_ARRAY])) - return null; - NSMutableArray arr = new NSMutableArray(); - int pos = 1; - int valbegin = -1; - while (pos < s.length()) { - char c = s.charAt(pos); - int tokenCount = 0; - int what = 0; - for (int i = 0 ; i < TOKEN_BEGIN.length; i++) { - if (c == TOKEN_BEGIN[i]) { - tokenCount = 1; - what = i; - break; - } - } - if (tokenCount > 0) { - //mark it - int quote = pos; - //find the closing token - do { - pos++; + /** + * Creates a NSArray object from a string representation. + * + * @s The string representation of a NSArray object. + */ + public static NSArray arrayForString(String s) { + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_ARRAY] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_ARRAY])) + return null; + NSMutableArray arr = new NSMutableArray(); + int pos = 1; + int valbegin = -1; + while (pos < s.length()) { + char c = s.charAt(pos); + int tokenCount = 0; + int what = 0; + for (int i = 0; i < TOKEN_BEGIN.length; i++) { + if (c == TOKEN_BEGIN[i]) { + tokenCount = 1; + what = i; + break; + } + } + if (tokenCount > 0) { + // mark it + int quote = pos; + // find the closing token + do { + pos++; try { c = s.charAt(pos); } catch (StringIndexOutOfBoundsException ex) { - throw new IllegalArgumentException("Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); + throw new IllegalArgumentException( + "Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); } if (c == '"' && what == PLIST_STRING) { - if (pos > 0 && s.charAt(pos-1) != '\\') { + if (pos > 0 && s.charAt(pos - 1) != '\\') { tokenCount--; } } else if (c == TOKEN_BEGIN[what]) - tokenCount++; - else if (c == TOKEN_END[what]) - tokenCount--; - } while (tokenCount > 0); - arr.addObject(propertyListFromString(s.substring(quote, pos+1))); - valbegin = -1; - //advance to the next position - do { - pos++; - c = s.charAt(pos); - } while (Character.isWhitespace(c)); - } - if (c == ',' || c ==')') { - if (valbegin > 0) { - arr.addObject(s.substring(valbegin, pos).trim()); - valbegin = -1; - } - } else if (!Character.isWhitespace(c)) { - if (valbegin < 0) { - valbegin = pos; - } - } - pos++; - } - return arr; - } + tokenCount++; + else if (c == TOKEN_END[what]) + tokenCount--; + } while (tokenCount > 0); + arr.addObject(propertyListFromString(s.substring(quote, pos + 1))); + valbegin = -1; + // advance to the next position + do { + pos++; + c = s.charAt(pos); + } while (Character.isWhitespace(c)); + } + if (c == ',' || c == ')') { + if (valbegin > 0) { + arr.addObject(s.substring(valbegin, pos).trim()); + valbegin = -1; + } + } else if (!Character.isWhitespace(c)) { + if (valbegin < 0) { + valbegin = pos; + } + } + pos++; + } + return arr; + } - /** Creates a NSDictionary instance from a string representation. - @s The string representation of a NSDictionary. */ - public static NSDictionary dictionaryForString(String s) { - s = s.trim(); - if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DICTIONARY] && s.charAt(s.length()-1) == TOKEN_END[PLIST_DICTIONARY])) - return null; - NSMutableDictionary d = new NSMutableDictionary(); - int pos = 1; - boolean parsing = true; - Object key = null; - int valbegin = -1; - while (pos < s.length()) { - //look for an opening token - char c = s.charAt(pos); - int tokenCount = 0; - int what = 0; - for (int i = 0 ; i < TOKEN_BEGIN.length; i++) { - if (c == TOKEN_BEGIN[i]) { - tokenCount = 1; - what = i; - break; - } - } - if (tokenCount > 0) { - //mark it - int quote = pos; - //find the closing token - do { - pos++; - try { + /** + * Creates a NSDictionary instance from a string representation. + * + * @s The string representation of a NSDictionary. + */ + public static NSDictionary dictionaryForString(String s) { + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DICTIONARY] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_DICTIONARY])) + return null; + NSMutableDictionary d = new NSMutableDictionary(); + int pos = 1; + boolean parsing = true; + Object key = null; + int valbegin = -1; + while (pos < s.length()) { + // look for an opening token + char c = s.charAt(pos); + int tokenCount = 0; + int what = 0; + for (int i = 0; i < TOKEN_BEGIN.length; i++) { + if (c == TOKEN_BEGIN[i]) { + tokenCount = 1; + what = i; + break; + } + } + if (tokenCount > 0) { + // mark it + int quote = pos; + // find the closing token + do { + pos++; + try { c = s.charAt(pos); - } catch (StringIndexOutOfBoundsException ex) { - throw new IllegalArgumentException("Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); - } - if (c == '"' && what == PLIST_STRING) { - if (pos > 0 && s.charAt(pos-1) != '\\') { - tokenCount--; - } - } else if (c == TOKEN_BEGIN[what]) - tokenCount++; - else if (c == TOKEN_END[what]) - tokenCount--; - } while (tokenCount > 0); - if (key == null) { - key = propertyListFromString(s.substring(quote, pos+1)); - } else { - d.setObjectForKey(propertyListFromString(s.substring(quote, pos+1)), key); - key = null; - } - valbegin = -1; - //advance to the next position - do { - pos++; - c = s.charAt(pos); - } while (Character.isWhitespace(c)); - } - if (c == ';' || c == '=' || c == '}') { - if (valbegin > 0) { - if (key == null) { - key = s.substring(valbegin, pos).trim(); - } else { - d.setObjectForKey(s.substring(valbegin, pos).trim(), key); - key = null; - } - valbegin = -1; - } - } else if (!Character.isWhitespace(c)) { - if (valbegin < 0) { - valbegin = pos; - } - } - pos++; - } - return d; - } + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException( + "Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); + } + if (c == '"' && what == PLIST_STRING) { + if (pos > 0 && s.charAt(pos - 1) != '\\') { + tokenCount--; + } + } else if (c == TOKEN_BEGIN[what]) + tokenCount++; + else if (c == TOKEN_END[what]) + tokenCount--; + } while (tokenCount > 0); + if (key == null) { + key = propertyListFromString(s.substring(quote, pos + 1)); + } else { + d.setObjectForKey(propertyListFromString(s.substring(quote, pos + 1)), key); + key = null; + } + valbegin = -1; + // advance to the next position + do { + pos++; + c = s.charAt(pos); + } while (Character.isWhitespace(c)); + } + if (c == ';' || c == '=' || c == '}') { + if (valbegin > 0) { + if (key == null) { + key = s.substring(valbegin, pos).trim(); + } else { + d.setObjectForKey(s.substring(valbegin, pos).trim(), key); + key = null; + } + valbegin = -1; + } + } else if (!Character.isWhitespace(c)) { + if (valbegin < 0) { + valbegin = pos; + } + } + pos++; + } + return d; + } - public static boolean booleanForString(String s) { - return s.trim().toLowerCase().equals("true"); - } + public static boolean booleanForString(String s) { + return s.trim().toLowerCase().equals("true"); + } - /** Creates a NSData instance from a string representation. - @s The string representation of a NSData object. */ - public static NSData dataFromPropertyList(String s) { - String hex = "0123456789ABCDEF"; - s = s.trim(); - if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DATA] && s.charAt(s.length()-1) == TOKEN_END[PLIST_DATA])) - return null; - int pos = 1; - java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); - while (pos < s.length()-1) { - char c1 = s.charAt(pos); - while (c1 == ' ') { - pos++; - if (pos == s.length()-1) - return new NSData(bout.toByteArray()); - c1 = s.charAt(pos); - } - if (hex.indexOf(c1) < 0) - throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ", pos " + pos + ")"); - pos++; - char c2 = s.charAt(pos); - if (hex.indexOf(c2) < 0) - throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ")"); - int x = (hex.indexOf(c1) << 4) | hex.indexOf(c2); - bout.write(x); - pos++; - } - return new NSData(bout.toByteArray()); - } + /** + * Creates a NSData instance from a string representation. + * + * @s The string representation of a NSData object. + */ + public static NSData dataFromPropertyList(String s) { + String hex = "0123456789ABCDEF"; + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DATA] && s.charAt(s.length() - 1) == TOKEN_END[PLIST_DATA])) + return null; + int pos = 1; + java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); + while (pos < s.length() - 1) { + char c1 = s.charAt(pos); + while (c1 == ' ') { + pos++; + if (pos == s.length() - 1) + return new NSData(bout.toByteArray()); + c1 = s.charAt(pos); + } + if (hex.indexOf(c1) < 0) + throw new IllegalArgumentException( + "The string does not represent a NSData object (" + s + ", pos " + pos + ")"); + pos++; + char c2 = s.charAt(pos); + if (hex.indexOf(c2) < 0) + throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ")"); + int x = (hex.indexOf(c1) << 4) | hex.indexOf(c2); + bout.write(x); + pos++; + } + return new NSData(bout.toByteArray()); + } - public static int intForString(String s) { - return Integer.parseInt(s); - } + public static int intForString(String s) { + return Integer.parseInt(s); + } - /** Returns the string representation of a property list. - @plist The property list. It can be a String, NSData, NSArray, NSDictionary. */ - public static String stringForPropertyList(Object plist) { - if (plist == null) - return ""; - if (plist instanceof NSArray || plist instanceof NSDictionary || plist instanceof NSData) - return plist.toString(); - String x = plist.toString(); - boolean quote = false; - for (int i = 0; i < x.length(); i++) { - char c = x.charAt(i); - for (int z = 0; z < TOKEN_BEGIN.length; z++) { - if (c == TOKEN_BEGIN[z] || c == TOKEN_END[z]) - quote = true; - } - if (!quote) { - for (int z = 0; z < QUOTING_CHARS.length; z++) { - if (c == QUOTING_CHARS[z]) - quote = true; - } - } - if (!quote && Character.isWhitespace(c)) { - quote = true; - i = x.length(); - } - } - if (quote) - return "\"" + x + "\""; - return x; - } + /** + * Returns the string representation of a property list. + * + * @plist The property list. It can be a String, NSData, NSArray, NSDictionary. + */ + public static String stringForPropertyList(Object plist) { + if (plist == null) + return ""; + if (plist instanceof NSArray || plist instanceof NSDictionary || plist instanceof NSData) + return plist.toString(); + String x = plist.toString(); + boolean quote = false; + for (int i = 0; i < x.length(); i++) { + char c = x.charAt(i); + for (int z = 0; z < TOKEN_BEGIN.length; z++) { + if (c == TOKEN_BEGIN[z] || c == TOKEN_END[z]) + quote = true; + } + if (!quote) { + for (int z = 0; z < QUOTING_CHARS.length; z++) { + if (c == QUOTING_CHARS[z]) + quote = true; + } + } + if (!quote && Character.isWhitespace(c)) { + quote = true; + i = x.length(); + } + } + if (quote) + return "\"" + x + "\""; + return x; + } - /** Returns an property list created from a string representation. - @s The string with a representation of a property list. - @returns A property list object; either a NSData, NSArray, NSDictionary, or a String. */ - public static Object propertyListFromString(String s) { - s = s.trim(); - int type = -1; - for (int i = 0; i < TOKEN_BEGIN.length; i++) { - if (TOKEN_BEGIN[i] == s.charAt(0)) { - if (TOKEN_END[i] == s.charAt(s.length()-1)) - type = i; - } - } - switch (type) { - case PLIST_DATA: - return dataFromPropertyList(s); - case PLIST_ARRAY: - return arrayForString(s); - case PLIST_DICTIONARY: - return dictionaryForString(s); - case PLIST_STRING: - if (s.equals("\"\"")) - return ""; - return s.substring(1, s.length()-1); - } - return s; - } + /** + * Returns an property list created from a string representation. + * + * @s The string with a representation of a property list. + * @returns A property list object; either a NSData, NSArray, NSDictionary, or a + * String. + */ + public static Object propertyListFromString(String s) { + s = s.trim(); + int type = -1; + for (int i = 0; i < TOKEN_BEGIN.length; i++) { + if (TOKEN_BEGIN[i] == s.charAt(0)) { + if (TOKEN_END[i] == s.charAt(s.length() - 1)) + type = i; + } + } + switch (type) { + case PLIST_DATA: + return dataFromPropertyList(s); + case PLIST_ARRAY: + return arrayForString(s); + case PLIST_DICTIONARY: + return dictionaryForString(s); + case PLIST_STRING: + if (s.equals("\"\"")) + return ""; + return s.substring(1, s.length() - 1); + } + return s; + } - /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. - * - * Revision 1.5 2003/08/11 18:18:08 chochos - * improved encoding of strings, removed warnings - * - * Revision 1.4 2003/08/11 17:33:45 chochos - * Implemented detection of escaped quotes (\"). improved quoting strings when converting property lists to strings. - * - * Revision 1.3 2003/08/04 23:50:55 chochos - * propertyListForString() now works. dictionaryForString and arrayForString can parse complex structures with nested arrays and dictionaries. - * - */ + /* + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/08/11 18:18:08 chochos improved encoding of strings, + * removed warnings + * + * Revision 1.4 2003/08/11 17:33:45 chochos Implemented detection of escaped + * quotes (\"). improved quoting strings when converting property lists to + * strings. + * + * Revision 1.3 2003/08/04 23:50:55 chochos propertyListForString() now works. + * dictionaryForString and arrayForString can parse complex structures with + * nested arrays and dictionaries. + * + */ } \ No newline at end of file diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java index 2de52f5..13dca2f 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java @@ -19,333 +19,280 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation; /** -* A pure java implementation of NSRange. -* An NSRange represents a range of numbers -* having a starting location and spanning a -* length. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 920 $ -*/ -public class NSRange implements Cloneable -{ - /** - * An empty range. - */ - public static final NSRange ZeroRange = new NSRange(); - - protected int loc; - protected int len; - - /** - * Default constructor produces an empty range. - */ - public NSRange () - { - this( 0, 0 ); - } - - /** - * Produces a range with the specified location and length. - */ - public NSRange (int location, int length) - { - loc = location; - len = length; - } - - /** - * Produces a range that has the same location and length as - * the specified range. - */ - public NSRange (NSRange aRange) - { - this( aRange.location(), aRange.length() ); - } - - /** - * Returns the location of this range. - */ - public int location () - { - return loc; - } - - /** - * Returns the length of this range. - */ - public int length () - { - return len; - } - - /** - * Returns the maximum extent of the range. This number is - * one more than the last position in the range. - */ - public int maxRange () - { - return location() + length() -1; - } - - /** - * Returns whether this is an empty range, therefore - * whether the length is zero. - */ - public boolean isEmpty () - { - return ( length() == 0 ); - } - - /** - * Returns whether the specified location is contained - * within this range. - */ - public boolean locationInRange (int location) - { - if ( location < location() ) return false; - if ( location >= maxRange() ) return false; - return true; - } - - /** - * Returns whether the specified range is equal to this range. - */ - public boolean isEqualToRange (NSRange aRange) - { - if ( aRange == null ) return false; - return ( ( aRange.location() == location() ) - && ( aRange.length() == length() ) ); - } - - /** - * Returns whether the specified object is equal to this range. - */ - public boolean equals (Object anObject) - { - if ( anObject instanceof NSRange ) - return isEqualToRange( (NSRange) anObject ); + * A pure java implementation of NSRange. An NSRange represents a range of + * numbers having a starting location and spanning a length. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 920 $ + */ +public class NSRange implements Cloneable { + /** + * An empty range. + */ + public static final NSRange ZeroRange = new NSRange(); + + protected int loc; + protected int len; + + /** + * Default constructor produces an empty range. + */ + public NSRange() { + this(0, 0); + } + + /** + * Produces a range with the specified location and length. + */ + public NSRange(int location, int length) { + loc = location; + len = length; + } + + /** + * Produces a range that has the same location and length as the specified + * range. + */ + public NSRange(NSRange aRange) { + this(aRange.location(), aRange.length()); + } + + /** + * Returns the location of this range. + */ + public int location() { + return loc; + } + + /** + * Returns the length of this range. + */ + public int length() { + return len; + } + + /** + * Returns the maximum extent of the range. This number is one more than the + * last position in the range. + */ + public int maxRange() { + return location() + length() - 1; + } + + /** + * Returns whether this is an empty range, therefore whether the length is zero. + */ + public boolean isEmpty() { + return (length() == 0); + } + + /** + * Returns whether the specified location is contained within this range. + */ + public boolean locationInRange(int location) { + if (location < location()) + return false; + if (location >= maxRange()) + return false; + return true; + } + + /** + * Returns whether the specified range is equal to this range. + */ + public boolean isEqualToRange(NSRange aRange) { + if (aRange == null) + return false; + return ((aRange.location() == location()) && (aRange.length() == length())); + } + + /** + * Returns whether the specified object is equal to this range. + */ + public boolean equals(Object anObject) { + if (anObject instanceof NSRange) + return isEqualToRange((NSRange) anObject); return false; - } - - /** - * Returns a hashCode. - */ - public int hashCode () - { - // TODO: Test this logic. - return ( location() << 2 ) & length(); // bitwise ops never my forte - } - - /** - * Returns a string representation of this range. - */ - public String toString () - { - return "[NSRange: location = " + location() - + "; length = " + length() + "]"; - } - - /** - * Returns the union of this range and the specified range, if any. - * Gaps are filled, so the result is the smallest starting position - * and the largest ending position. - */ - public NSRange rangeByUnioningRange (NSRange aRange) - { - if ( aRange == null ) return this; - - // TODO: Test this logic. - int resultLoc = Math.min( this.location(), aRange.location() ); - int resultLen = Math.max( this.location() + this.length(), - aRange.location() + aRange.length() ) - resultLoc; - return new NSRange( resultLoc, resultLen ); - } - - /** - * Returns the intersection of this range and the specified range, - * if any. If no intersection, returns an empty range. - */ - public NSRange rangeByIntersectingRange (NSRange aRange) - { - // TODO: Test this logic. - if ( ! intersectsRange( aRange ) ) return ZeroRange; - int start = Math.max( this.location(), aRange.location() ); - int end = Math.min( this.location() + this.length(), - aRange.location() + aRange.length() ); - return new NSRange( start, end - start ); - } - - /** - * Returns whether the specified range overlaps - * at any point with this range. - */ - public boolean intersectsRange (NSRange aRange) - { - // TODO: Test this logic. - if ( aRange == null ) return false; - if ( ( this.location() >= aRange.location() ) - && ( this.location() < aRange.location() + aRange.length() ) ) + } + + /** + * Returns a hashCode. + */ + public int hashCode() { + // TODO: Test this logic. + return (location() << 2) & length(); // bitwise ops never my forte + } + + /** + * Returns a string representation of this range. + */ + public String toString() { + return "[NSRange: location = " + location() + "; length = " + length() + "]"; + } + + /** + * Returns the union of this range and the specified range, if any. Gaps are + * filled, so the result is the smallest starting position and the largest + * ending position. + */ + public NSRange rangeByUnioningRange(NSRange aRange) { + if (aRange == null) + return this; + + // TODO: Test this logic. + int resultLoc = Math.min(this.location(), aRange.location()); + int resultLen = Math.max(this.location() + this.length(), aRange.location() + aRange.length()) - resultLoc; + return new NSRange(resultLoc, resultLen); + } + + /** + * Returns the intersection of this range and the specified range, if any. If no + * intersection, returns an empty range. + */ + public NSRange rangeByIntersectingRange(NSRange aRange) { + // TODO: Test this logic. + if (!intersectsRange(aRange)) + return ZeroRange; + int start = Math.max(this.location(), aRange.location()); + int end = Math.min(this.location() + this.length(), aRange.location() + aRange.length()); + return new NSRange(start, end - start); + } + + /** + * Returns whether the specified range overlaps at any point with this range. + */ + public boolean intersectsRange(NSRange aRange) { + // TODO: Test this logic. + if (aRange == null) + return false; + if ((this.location() >= aRange.location()) && (this.location() < aRange.location() + aRange.length())) return true; - if ( ( aRange.location() >= this.location() ) - && ( aRange.location() < this.location() + this.length() ) ) + if ((aRange.location() >= this.location()) && (aRange.location() < this.location() + this.length())) return true; return false; - } - - /** - * Returns whether this range is completely - * contained within the specified range. - */ - public boolean isSubrangeOfRange (NSRange aRange) - { - // TODO: Test this logic. - if ( aRange == null ) return false; - if ( ( this.location() >= aRange.location() ) - && ( this.maxRange() <= aRange.maxRange() ) ) - return true; + } + + /** + * Returns whether this range is completely contained within the specified + * range. + */ + public boolean isSubrangeOfRange(NSRange aRange) { + // TODO: Test this logic. + if (aRange == null) + return false; + if ((this.location() >= aRange.location()) && (this.maxRange() <= aRange.maxRange())) + return true; return false; - } - - /** - * Eliminates any intersections between this range and the specified - * range. This produces two ranges, either of which may be empty. - * These two ranges are returned by modifying the supplied second - * and third parameters. - */ - public void subtractRange (NSRange aRange, - NSMutableRange firstResult, NSMutableRange secondResult) - { - if ( aRange == null ) return; - - // TODO: Test this logic. - // no intersection: return this and aRange without calculation - if ( ! intersectsRange( aRange ) ) - { - if ( firstResult != null ) - { - firstResult.setLocation( this.location() ); - firstResult.setLength( this.length() ); - } - if ( secondResult != null ) - { - secondResult.setLocation( aRange.location() ); - secondResult.setLength( aRange.location() ); - } - return; - } - - // TODO: Test this logic. - // this range is completely contained by other range - if ( isSubrangeOfRange( aRange ) ) - { - if ( firstResult != null ) - { - firstResult.setLocation( aRange.location() ); - firstResult.setLength( this.location() - aRange.location() ); - } - if ( secondResult != null ) - { - secondResult.setLocation( this.maxRange() ); - secondResult.setLength( - aRange.maxRange() - this.maxRange() - 1 ); // test this - } - return; - } - - // TODO: Test this logic. - // other range is completely contained by this range - if ( aRange.isSubrangeOfRange( this ) ) - { - if ( firstResult != null ) - { - firstResult.setLocation( this.location() ); - firstResult.setLength( aRange.location() - this.location() ); - } - if ( secondResult != null ) - { - secondResult.setLocation( aRange.maxRange() ); - secondResult.setLength( - this.maxRange() - aRange.maxRange() - 1 ); // test this - } - return; - } - - // TODO: Test this logic. - // ranges intersect: remove only the intersection - - NSRange firstRange, secondRange; - if ( this.location() <= aRange.location() ) - { - firstRange = this; - secondRange = aRange; - } - else - { - firstRange = aRange; - secondRange = this; - } - - if ( firstResult != null ) - { - firstResult.setLocation( firstRange.location() ); - firstResult.setLength( - secondRange.location() - firstRange.location() ); - } - if ( secondResult != null ) - { - secondResult.setLocation( firstRange.maxRange() ); - secondResult.setLength( - secondRange.maxRange() - aRange.maxRange() - 1 ); // test this - } - return; - - } - - /** - * Returns a copy of this range. - */ - public Object clone () - { - return new NSRange( location(), length() ); - } - - /** - * Parses a range from a string of the form "{x,y}" where - * x is the location and y is the length. If not parsable, - * an IllegalArgumentException is thrown. - */ - public static NSRange fromString (String aString) - { - // TODO: Test this logic. - try - { - java.util.StringTokenizer tokens = - new java.util.StringTokenizer( aString, "{,}" ); - int loc = Integer.parseInt( tokens.nextToken() ); - int len = Integer.parseInt( tokens.nextToken() ); - return new NSRange( loc, len ); - } - catch ( Exception exc ) - { - throw new IllegalArgumentException( exc.toString() ); - } - } - + } + + /** + * Eliminates any intersections between this range and the specified range. This + * produces two ranges, either of which may be empty. These two ranges are + * returned by modifying the supplied second and third parameters. + */ + public void subtractRange(NSRange aRange, NSMutableRange firstResult, NSMutableRange secondResult) { + if (aRange == null) + return; + + // TODO: Test this logic. + // no intersection: return this and aRange without calculation + if (!intersectsRange(aRange)) { + if (firstResult != null) { + firstResult.setLocation(this.location()); + firstResult.setLength(this.length()); + } + if (secondResult != null) { + secondResult.setLocation(aRange.location()); + secondResult.setLength(aRange.location()); + } + return; + } + + // TODO: Test this logic. + // this range is completely contained by other range + if (isSubrangeOfRange(aRange)) { + if (firstResult != null) { + firstResult.setLocation(aRange.location()); + firstResult.setLength(this.location() - aRange.location()); + } + if (secondResult != null) { + secondResult.setLocation(this.maxRange()); + secondResult.setLength(aRange.maxRange() - this.maxRange() - 1); // test this + } + return; + } + + // TODO: Test this logic. + // other range is completely contained by this range + if (aRange.isSubrangeOfRange(this)) { + if (firstResult != null) { + firstResult.setLocation(this.location()); + firstResult.setLength(aRange.location() - this.location()); + } + if (secondResult != null) { + secondResult.setLocation(aRange.maxRange()); + secondResult.setLength(this.maxRange() - aRange.maxRange() - 1); // test this + } + return; + } + + // TODO: Test this logic. + // ranges intersect: remove only the intersection + + NSRange firstRange, secondRange; + if (this.location() <= aRange.location()) { + firstRange = this; + secondRange = aRange; + } else { + firstRange = aRange; + secondRange = this; + } + + if (firstResult != null) { + firstResult.setLocation(firstRange.location()); + firstResult.setLength(secondRange.location() - firstRange.location()); + } + if (secondResult != null) { + secondResult.setLocation(firstRange.maxRange()); + secondResult.setLength(secondRange.maxRange() - aRange.maxRange() - 1); // test this + } + return; + + } + + /** + * Returns a copy of this range. + */ + public Object clone() { + return new NSRange(location(), length()); + } + + /** + * Parses a range from a string of the form "{x,y}" where x is the location and + * y is the length. If not parsable, an IllegalArgumentException is thrown. + */ + public static NSRange fromString(String aString) { + // TODO: Test this logic. + try { + java.util.StringTokenizer tokens = new java.util.StringTokenizer(aString, "{,}"); + int loc = Integer.parseInt(tokens.nextToken()); + int len = Integer.parseInt(tokens.nextToken()); + return new NSRange(loc, len); + } catch (Exception exc) { + throw new IllegalArgumentException(exc.toString()); + } + } + } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:42 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:42 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:38 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:38 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java index 65f58db..999af6c 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java @@ -24,125 +24,129 @@ package net.wotonomy.foundation; import EDU.oswego.cs.dl.util.concurrent.ReentrantLock; /** -* A lock class that allows a thread to re-acquire it's lock -* recursively. Currently an API-compliance wrapper around Doug Lea's -* ReentrantLock, conforming to the API and behavior of -* com.webobjects.foundation.NSRecursiveLock. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A lock class that allows a thread to re-acquire it's lock recursively. + * Currently an API-compliance wrapper around Doug Lea's ReentrantLock, + * conforming to the API and behavior of + * com.webobjects.foundation.NSRecursiveLock. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public class NSRecursiveLock extends ReentrantLock implements NSLocking { - public NSRecursiveLock() { - } - /** Acquire the lock, catching the thrown exception to mirror the - * behavior of com.webobjects.foundation.NSRecursiveLock. Note that - * ReentrantLock.acquire() performs a notify() when it's interrupted. - * - * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() - */ - public void lock() { - try { - acquire(); - } catch (InterruptedException interruptedexception) { - // Null behavior, as notify() is already called - // by acquire(); - // We may want to log here. - } - } - - /** Pass the buck to tryLock(long), passing zero time as the parameter. - * - * @see #tryLock(long) - */ - public boolean tryLock() { - return tryLock(1); - } - - /** Attempt to acquire the lock, catching the thrown exception to mirror - * the behavior of com.webobjects.foundation.NSRecursiveLock. Note that - * ReentrantLock.attempt(*) performs a notify() when it's interrupted. - * Fail gracefully after the given milliseconds - * - * @param (long) - * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() - */ - public boolean tryLock(long milliseconds) { - try { - return attempt(milliseconds); - } catch (InterruptedException interruptedexception) { - // notify() is already called by attempt(); - // We may want to log here. - return false; - } - } - /** - * Attempt to acquire a lock until the timestamp is reached. Add - * 1 to the recursion count if the calling thread already owns the - * lock. Otherwise block until free or until the given timestamp - * is reached. - * - * @see Timestamp - * @see ReentrantLock.attempt(long); - */ - public boolean tryLock(NSTimestamp nstimestamp) { - return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); - } - - /** Unlock the current lock precisely once. - */ - public synchronized void unlock() { - unlock(1); - } - - /** Unlock the current lock count times. - */ - public synchronized void unlock(long count) { - if (owner_ != null && Thread.currentThread() != owner_) - throw new IllegalStateException("Illegal Lock usage: unlocking thread not owner."); - if (owner_ == null || holds_ == 0L) - throw new IllegalStateException("Illegal Lock usage: unlock() called without a lock()."); + public NSRecursiveLock() { + } + + /** + * Acquire the lock, catching the thrown exception to mirror the behavior of + * com.webobjects.foundation.NSRecursiveLock. Note that ReentrantLock.acquire() + * performs a notify() when it's interrupted. + * + * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() + */ + public void lock() { + try { + acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + /** + * Pass the buck to tryLock(long), passing zero time as the parameter. + * + * @see #tryLock(long) + */ + public boolean tryLock() { + return tryLock(1); + } + + /** + * Attempt to acquire the lock, catching the thrown exception to mirror the + * behavior of com.webobjects.foundation.NSRecursiveLock. Note that + * ReentrantLock.attempt(*) performs a notify() when it's interrupted. Fail + * gracefully after the given milliseconds + * + * @param (long) + * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() + */ + public boolean tryLock(long milliseconds) { + try { + return attempt(milliseconds); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + + /** + * Attempt to acquire a lock until the timestamp is reached. Add 1 to the + * recursion count if the calling thread already owns the lock. Otherwise block + * until free or until the given timestamp is reached. + * + * @see Timestamp + * @see ReentrantLock.attempt(long); + */ + public boolean tryLock(NSTimestamp nstimestamp) { + return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); + } + + /** + * Unlock the current lock precisely once. + */ + public synchronized void unlock() { + unlock(1); + } + + /** + * Unlock the current lock count times. + */ + public synchronized void unlock(long count) { + if (owner_ != null && Thread.currentThread() != owner_) + throw new IllegalStateException("Illegal Lock usage: unlocking thread not owner."); + if (owner_ == null || holds_ == 0L) + throw new IllegalStateException("Illegal Lock usage: unlock() called without a lock()."); release(count); - } + } - public synchronized long recursionCount() { + public synchronized long recursionCount() { return holds(); - } + } - public String toString() { - long holds = holds(); - boolean oneHold = (holds == 1); - boolean noHolds = (holds < 1 || owner_ == null); - return getClass().getName() + " <" + - ((noHolds) ? "Unlocked" : ( "Locked " + holds + " time" + (oneHold ? "" : "s") + " by " + owner_ ) ) + ">"; - } + public String toString() { + long holds = holds(); + boolean oneHold = (holds == 1); + boolean noHolds = (holds < 1 || owner_ == null); + return getClass().getName() + " <" + + ((noHolds) ? "Unlocked" : ("Locked " + holds + " time" + (oneHold ? "" : "s") + " by " + owner_)) + + ">"; + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.5 2002/06/25 19:02:19 cgruber - * I'm a dumbass. + * Revision 1.5 2002/06/25 19:02:19 cgruber I'm a dumbass. * - * Revision 1.4 2002/06/25 18:52:56 cgruber - * Fix javadocs that resulted from bad cut-and-paste of the boilerplate. + * Revision 1.4 2002/06/25 18:52:56 cgruber Fix javadocs that resulted from bad + * cut-and-paste of the boilerplate. * - * Revision 1.3 2002/06/25 18:45:27 cgruber - * Add some javadocs. + * Revision 1.3 2002/06/25 18:45:27 cgruber Add some javadocs. * - * Revision 1.2 2002/06/25 18:06:48 cgruber - * Add implementation of NSRecursiveLock using Doug Lea's concurrent programming APIs. - * Specifically inherit from ReentrantLock, which magically does the exact job we want! + * Revision 1.2 2002/06/25 18:06:48 cgruber Add implementation of + * NSRecursiveLock using Doug Lea's concurrent programming APIs. Specifically + * inherit from ReentrantLock, which magically does the exact job we want! * - * Revision 1.1 2002/06/25 07:52:56 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:56 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java index 2d122aa..c72cd23 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java @@ -28,495 +28,421 @@ import java.util.List; import java.util.ListIterator; /** -* NSRunLoop is provided specifically for EODelayedObserverQueue -* and EOEditingContext, which assume the existence of a -* prioritized event queue that Java does not provide.

-* -* This extends java.awt.EventQueue and does not conform to the -* NSRunLoop specifications. The only supported methods are -* NSRunLoop.currentRunLoop, performSelectorWithOrder, and -* cancelSelectorWithOrder. Note that in Swing there is only -* one AWT thread and one event queue; newly created threads -* will not get their own run loop as in OpenStep.

-* -* That said, this event queue is servicable as a replacement -* for the default event queue and will provide prioritized -* execution of selectors before and after normal AWT events. -*

-* -* Each run loop dispatches the lowest order event from -* the queue. When queued events have the same ordering, -* they are dispatched as first-in, first-out (FIFO). Because -* all AWT events have the same ordering (AWTEventsRunLoopOrdering), -* they are processed FIFO, just like the default event queue.

-* -* Note that because EventQueue is not well-factored for -* subclassing, pushing a new event queue onto the stack -* on top of this one will only copy the existing AWT events -* to the new queue. For this reason, pushing new event -* queues onto the stack is not supported and will throw -* an exception. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSRunLoop extends EventQueue -{ - /** - * This is the ordering at which the conventional AWT event - * queue will be executed. Selectors with this ordering - * or less will be executed before AWT events, and - * selectors with ordering greater than this ordering will be - * be executed after AWT events. - */ - public static final int AWTEventsRunLoopOrdering = 500000; - - /** - * The singleton instance. - */ - protected static NSRunLoop instance; - - private LinkedList earlyQueue; - private LinkedList lateQueue; - - /** - * Needed because JDK1.4 made our lives more difficult. - */ - private static Toolkit toolkit; - - /** - * Because SunToolkit.flushPendingEvents was changed - * to a static method in 1.4, you can't compile the library - * in such a way that it works with both 1.3 and 1.4. - * So we have to rely on dynamic method invocation, - * which is slower, but we try to make it as fast as - * humanly possible. - */ - private static NSSelector flushPendingEvents; - - /** - * Create a new instance of NSRunLoop. - */ - protected NSRunLoop () - { - earlyQueue = new LinkedList(); - lateQueue = new LinkedList(); - } - - /** - * Returns the singleton instance of NSRunLoop. - * This returns the same instance no matter what - * thread calls it, which is different from OpenStep. - * NSRunLoop is limited to a singleton instance - * because there is no way of obtaining the stack - * of event queues from EventQueue because it is - * private state. - */ - public synchronized static NSRunLoop currentRunLoop () - { - if ( instance == null ) - { - // create and initialize - flushPendingEvents = new NSSelector( "flushPendingEvents" ); - toolkit = Toolkit.getDefaultToolkit(); - instance = new NSRunLoop(); - - toolkit.getSystemEventQueue().push( instance ); - } - - return instance; - } - - /** - * Post a 1.1-style event to the EventQueue. If there is an - * existing event on the queue with the same ID and event source, - * the source Component's coalesceEvents method will be called. - * - * @param theEvent an instance of java.awt.AWTEvent, or a - * subclass of it. - */ - public void postEvent(AWTEvent theEvent) - { - if ( theEvent instanceof OrderedInvocationEvent ) - { - OrderedInvocationEvent event = (OrderedInvocationEvent) theEvent; - if ( event.getOrdering() > AWTEventsRunLoopOrdering ) - { - insertEventIntoQueue( event, lateQueue ); - } - else - { - insertEventIntoQueue( event, earlyQueue ); - } - } - else - { - super.postEvent( theEvent ); - } - } - - private synchronized void insertEventIntoQueue( OrderedInvocationEvent e, LinkedList q ) - { - OrderedInvocationEvent o; - int ordering = e.getOrdering(); - ListIterator iterator = - q.listIterator(); - - // iterate forwards until we find a priority - // greater than our priority, - // then insert ourself before that element. - while ( iterator.hasNext() ) - { - o = (OrderedInvocationEvent) iterator.next(); - if ( o.getOrdering() > ordering ) - { - // back up one - iterator.previous(); - break; - } - } - // add after the current element - iterator.add( e ); - } - - /** - * Useful method, but not in the spec. - * Dispatches the next AWT event in the queue. - * Returns whether a selector or an event was executed: - * if the event queue is empty, returns false. - */ - public boolean dispatchNextEvent() - { - // check for empty queue to avoid blocking - if ( peekEvent() == null ) - { - return false; - } - - // queue not empty: dispatch the next event - try - { - dispatchEvent( getNextEvent() ); - } - catch ( InterruptedException exc ) - { - System.out.println( "NSRunLoop: error while dispatching event: " ); - exc.printStackTrace(); - } - return true; - } - - /** - * Useful method, but not in the spec. - * Dispatches all events in the queue before returning. - */ - public void dispatchAllEvents() - { - while ( dispatchNextEvent() ); - } - - /** - * Remove an event from the EventQueue and return it. - * This override will dispatch all selectors up to 5000, - * and then check if there are AWT events on the queue. - * If the queue is empty, all remaining selectors - * are dispatched. Then, this method calls the - * super class' implementation. - * @return the next AWTEvent - * @exception InterruptedException - * if another thread has interrupted this thread. - */ - public AWTEvent getNextEvent() throws InterruptedException - { - //NOTE: it's currently unclear to me whether we should - // be operating as a run loop or as a priority queue. - // I'm opting for priority queue now, but that means that - // selectors that requeue themselves could hang the application. - // In the future, we could fake a run loop by putting a marker - // event on the AWT queue to mark the boundary between loops. - - AWTEvent result; - - while ( true ) - { - //NOTE: as of java 1.4, we have to flush pending events - // using this cheesy undocumented method on suntoolkit. - // Unsurprisingly, java.awt.EventQueue got worse, not better. - // See notes above about our use of an NSSelector. - try - { - flushPendingEvents.invoke( toolkit ); - } - catch ( Throwable t ) - { - System.out.println( "NSRunLoop.getNextEvent: " + Thread.currentThread() ); - System.err.println( "Unexpected error while flushing pending events: " ); - t.printStackTrace(); - }; - - synchronized( this ) - { - result = popNextEarlyEvent(); - if ( result != null ) - { + * NSRunLoop is provided specifically for EODelayedObserverQueue and + * EOEditingContext, which assume the existence of a prioritized event queue + * that Java does not provide.
+ *
+ * + * This extends java.awt.EventQueue and does not conform to the NSRunLoop + * specifications. The only supported methods are NSRunLoop.currentRunLoop, + * performSelectorWithOrder, and cancelSelectorWithOrder. Note that in Swing + * there is only one AWT thread and one event queue; newly created threads will + * not get their own run loop as in OpenStep.
+ *
+ * + * That said, this event queue is servicable as a replacement for the default + * event queue and will provide prioritized execution of selectors before and + * after normal AWT events.
+ *
+ * + * Each run loop dispatches the lowest order event from the queue. When queued + * events have the same ordering, they are dispatched as first-in, first-out + * (FIFO). Because all AWT events have the same ordering + * (AWTEventsRunLoopOrdering), they are processed FIFO, just like the default + * event queue.
+ *
+ * + * Note that because EventQueue is not well-factored for subclassing, pushing a + * new event queue onto the stack on top of this one will only copy the existing + * AWT events to the new queue. For this reason, pushing new event queues onto + * the stack is not supported and will throw an exception. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSRunLoop extends EventQueue { + /** + * This is the ordering at which the conventional AWT event queue will be + * executed. Selectors with this ordering or less will be executed before AWT + * events, and selectors with ordering greater than this ordering will be be + * executed after AWT events. + */ + public static final int AWTEventsRunLoopOrdering = 500000; + + /** + * The singleton instance. + */ + protected static NSRunLoop instance; + + private LinkedList earlyQueue; + private LinkedList lateQueue; + + /** + * Needed because JDK1.4 made our lives more difficult. + */ + private static Toolkit toolkit; + + /** + * Because SunToolkit.flushPendingEvents was changed to a static method in 1.4, + * you can't compile the library in such a way that it works with both 1.3 and + * 1.4. So we have to rely on dynamic method invocation, which is slower, but we + * try to make it as fast as humanly possible. + */ + private static NSSelector flushPendingEvents; + + /** + * Create a new instance of NSRunLoop. + */ + protected NSRunLoop() { + earlyQueue = new LinkedList(); + lateQueue = new LinkedList(); + } + + /** + * Returns the singleton instance of NSRunLoop. This returns the same instance + * no matter what thread calls it, which is different from OpenStep. NSRunLoop + * is limited to a singleton instance because there is no way of obtaining the + * stack of event queues from EventQueue because it is private state. + */ + public synchronized static NSRunLoop currentRunLoop() { + if (instance == null) { + // create and initialize + flushPendingEvents = new NSSelector("flushPendingEvents"); + toolkit = Toolkit.getDefaultToolkit(); + instance = new NSRunLoop(); + + toolkit.getSystemEventQueue().push(instance); + } + + return instance; + } + + /** + * Post a 1.1-style event to the EventQueue. If there is an existing event on + * the queue with the same ID and event source, the source Component's + * coalesceEvents method will be called. + * + * @param theEvent an instance of java.awt.AWTEvent, or a subclass of it. + */ + public void postEvent(AWTEvent theEvent) { + if (theEvent instanceof OrderedInvocationEvent) { + OrderedInvocationEvent event = (OrderedInvocationEvent) theEvent; + if (event.getOrdering() > AWTEventsRunLoopOrdering) { + insertEventIntoQueue(event, lateQueue); + } else { + insertEventIntoQueue(event, earlyQueue); + } + } else { + super.postEvent(theEvent); + } + } + + private synchronized void insertEventIntoQueue(OrderedInvocationEvent e, LinkedList q) { + OrderedInvocationEvent o; + int ordering = e.getOrdering(); + ListIterator iterator = q.listIterator(); + + // iterate forwards until we find a priority + // greater than our priority, + // then insert ourself before that element. + while (iterator.hasNext()) { + o = (OrderedInvocationEvent) iterator.next(); + if (o.getOrdering() > ordering) { + // back up one + iterator.previous(); + break; + } + } + // add after the current element + iterator.add(e); + } + + /** + * Useful method, but not in the spec. Dispatches the next AWT event in the + * queue. Returns whether a selector or an event was executed: if the event + * queue is empty, returns false. + */ + public boolean dispatchNextEvent() { + // check for empty queue to avoid blocking + if (peekEvent() == null) { + return false; + } + + // queue not empty: dispatch the next event + try { + dispatchEvent(getNextEvent()); + } catch (InterruptedException exc) { + System.out.println("NSRunLoop: error while dispatching event: "); + exc.printStackTrace(); + } + return true; + } + + /** + * Useful method, but not in the spec. Dispatches all events in the queue before + * returning. + */ + public void dispatchAllEvents() { + while (dispatchNextEvent()) + ; + } + + /** + * Remove an event from the EventQueue and return it. This override will + * dispatch all selectors up to 5000, and then check if there are AWT events on + * the queue. If the queue is empty, all remaining selectors are dispatched. + * Then, this method calls the super class' implementation. + * + * @return the next AWTEvent + * @exception InterruptedException if another thread has interrupted this + * thread. + */ + public AWTEvent getNextEvent() throws InterruptedException { + // NOTE: it's currently unclear to me whether we should + // be operating as a run loop or as a priority queue. + // I'm opting for priority queue now, but that means that + // selectors that requeue themselves could hang the application. + // In the future, we could fake a run loop by putting a marker + // event on the AWT queue to mark the boundary between loops. + + AWTEvent result; + + while (true) { + // NOTE: as of java 1.4, we have to flush pending events + // using this cheesy undocumented method on suntoolkit. + // Unsurprisingly, java.awt.EventQueue got worse, not better. + // See notes above about our use of an NSSelector. + try { + flushPendingEvents.invoke(toolkit); + } catch (Throwable t) { + System.out.println("NSRunLoop.getNextEvent: " + Thread.currentThread()); + System.err.println("Unexpected error while flushing pending events: "); + t.printStackTrace(); + } + ; + + synchronized (this) { + result = popNextEarlyEvent(); + if (result != null) { //System.out.println( "getNextEvent: early : " + result ); - return result; - } - } - - if ( ( result = peekEvent() ) != null ) - { + return result; + } + } + + if ((result = peekEvent()) != null) { //System.out.println( "getNextEvent: AWT : " + result ); - return super.getNextEvent(); - } - - synchronized( this ) - { - result = popNextLateEvent(); - if ( result != null ) - { + return super.getNextEvent(); + } + + synchronized (this) { + result = popNextLateEvent(); + if (result != null) { //System.out.println( "getNextEvent: late : " + result ); - return result; - } - - // yield + return result; + } + + // yield //System.out.println( "getNextEvent: wait" ); - wait(); + wait(); //System.out.println( "getNextEvent: notified" ); - } - } - } - - private AWTEvent popNextEarlyEvent() - { - if ( earlyQueue == null ) return null; // shouldn't be necessary, but is - if ( earlyQueue.isEmpty() ) return null; - return (AWTEvent) earlyQueue.removeFirst(); - } - - private AWTEvent popNextLateEvent() - { - if ( lateQueue == null ) return null; // shouldn't be necessary, but is - if ( lateQueue.isEmpty() ) return null; - return (AWTEvent) lateQueue.removeFirst(); - } - - /** - * This implementation calls super and then throws an - * UnsupportedOperationException. Catch that exception - * and ignore it if you know what you are doing. - */ - public synchronized void push(EventQueue newEventQueue) - { - super.push( newEventQueue ); - throw new UnsupportedOperationException( - "NSRunLoop may not function properly with push()" ); - } - - /** - * This implementation calls super and then throws an - * UnsupportedOperationException. Catch that exception - * and ignore it if you know what you are doing. - */ - protected void pop() throws EmptyStackException - { - super.pop(); - throw new UnsupportedOperationException( - "NSRunLoop may not function properly with pop()" ); - } - - /** - * Schedules the specified selector with the specified target and parameter - * to be invoked on the next event loop with the specified ordering. - * The selector must be able to be invoked on the target and the target method - * must accept the parameter. aModeList is currently ignored. - */ - public void performSelectorWithOrder( - NSSelector aSelector, Object aTarget, Object aParameter, int anOrdering, List aModeList ) - { - postEvent( new OrderedInvocationEvent( aSelector, aTarget, aParameter, anOrdering, aModeList ) ); - } - - /** - * Cancels the next scheduled invocation of the specified selector, target, and parameter. - * If no such invocation is scheduled, does nothing. - */ - public synchronized void cancelPerformSelectorWithOrder( - NSSelector aSelector, Object aTarget, Object aParameter ) - { - ListIterator i; - i = earlyQueue.listIterator(); - while ( i.hasNext() ) - { - if ( ((OrderedInvocationEvent)i.next()).compareTo( - aSelector, aTarget, aParameter ) ) - { - i.remove(); - return; - } - } - i = lateQueue.listIterator(); - while ( i.hasNext() ) - { - if ( ((OrderedInvocationEvent)i.next()).compareTo( - aSelector, aTarget, aParameter ) ) - { - i.remove(); - return; - } - } - } - - /** - * Causes runnable to have its run() method on the next - * event loop with the specified priority ordering. - */ - public static void invokeLaterWithOrder(Runnable aRunnable, int anOrdering) { - currentRunLoop().postEvent( - new OrderedInvocationEvent( Toolkit.getDefaultToolkit(), aRunnable, anOrdering ) ); - } - - /** - * An invocation event that can specify a priority for execution. - * The prioritization only works if the current event queue is an - * NSRunLoop; otherwise, performs as a normal invocation event. - */ - private static class OrderedInvocationEvent extends InvocationEvent - { - int ordering; - NSSelector selector = null; - Object target = null; - Object parameter = null; - - /** - * Constructs an InvocationEvent with the specified source which will - * execute the runnable's run() method when dispatched at the specified ordering. - */ - public OrderedInvocationEvent(Object source, - Runnable runnable, int anOrdering) - { - super( source, runnable ); - ordering = anOrdering; - } - - /** - * Constructs an InvocationEvent with the specified source which will - * execute the runnable's run() method when dispatched at the specified ordering. - * If notifier is non-null, notifyAll() will be called on it immediately after run() returns. - */ - public OrderedInvocationEvent(Object source, - Runnable runnable, - Object notifier, - boolean catchExceptions, int anOrdering) - { - super( source, runnable, notifier, catchExceptions ); - ordering = anOrdering; - } - - OrderedInvocationEvent( - final NSSelector aSelector, - final Object aTarget, - final Object aParameter, - int anOrdering, List aModeList) - { - this( Toolkit.getDefaultToolkit(), new Runnable() - { - public void run() - { - try - { - aSelector.invoke( aTarget, aParameter ); - } - catch ( Exception exc ) - { - System.out.println( "NSRunLoop: error invoking selector: " ); - exc.printStackTrace(); - } - } - }, anOrdering ); - - selector = aSelector; - target = aTarget; - parameter = aParameter; - } - - /** - * Called by cancelPerformSelectorWithOrder. - * Compares against the specified arguments. - */ - boolean compareTo( NSSelector aSelector, Object aTarget, Object aParameter ) - { - return ( - compareByValue( selector, aSelector ) && - compareByValue( target, aTarget ) && - compareByValue( parameter, aParameter ) ); - } - - private boolean compareByValue( Object first, Object second ) - { - if ( first == second ) return true; - if ( first == null ) return second.equals( first ); - return first.equals( second ); - - } - - /** - * Returns the ordering for this event in the run loop. - */ - public int getOrdering() - { - return ordering; - } - - } - + } + } + } + + private AWTEvent popNextEarlyEvent() { + if (earlyQueue == null) + return null; // shouldn't be necessary, but is + if (earlyQueue.isEmpty()) + return null; + return (AWTEvent) earlyQueue.removeFirst(); + } + + private AWTEvent popNextLateEvent() { + if (lateQueue == null) + return null; // shouldn't be necessary, but is + if (lateQueue.isEmpty()) + return null; + return (AWTEvent) lateQueue.removeFirst(); + } + + /** + * This implementation calls super and then throws an + * UnsupportedOperationException. Catch that exception and ignore it if you know + * what you are doing. + */ + public synchronized void push(EventQueue newEventQueue) { + super.push(newEventQueue); + throw new UnsupportedOperationException("NSRunLoop may not function properly with push()"); + } + + /** + * This implementation calls super and then throws an + * UnsupportedOperationException. Catch that exception and ignore it if you know + * what you are doing. + */ + protected void pop() throws EmptyStackException { + super.pop(); + throw new UnsupportedOperationException("NSRunLoop may not function properly with pop()"); + } + + /** + * Schedules the specified selector with the specified target and parameter to + * be invoked on the next event loop with the specified ordering. The selector + * must be able to be invoked on the target and the target method must accept + * the parameter. aModeList is currently ignored. + */ + public void performSelectorWithOrder(NSSelector aSelector, Object aTarget, Object aParameter, int anOrdering, + List aModeList) { + postEvent(new OrderedInvocationEvent(aSelector, aTarget, aParameter, anOrdering, aModeList)); + } + + /** + * Cancels the next scheduled invocation of the specified selector, target, and + * parameter. If no such invocation is scheduled, does nothing. + */ + public synchronized void cancelPerformSelectorWithOrder(NSSelector aSelector, Object aTarget, Object aParameter) { + ListIterator i; + i = earlyQueue.listIterator(); + while (i.hasNext()) { + if (((OrderedInvocationEvent) i.next()).compareTo(aSelector, aTarget, aParameter)) { + i.remove(); + return; + } + } + i = lateQueue.listIterator(); + while (i.hasNext()) { + if (((OrderedInvocationEvent) i.next()).compareTo(aSelector, aTarget, aParameter)) { + i.remove(); + return; + } + } + } + + /** + * Causes runnable to have its run() method on the next event loop with the + * specified priority ordering. + */ + public static void invokeLaterWithOrder(Runnable aRunnable, int anOrdering) { + currentRunLoop().postEvent(new OrderedInvocationEvent(Toolkit.getDefaultToolkit(), aRunnable, anOrdering)); + } + + /** + * An invocation event that can specify a priority for execution. The + * prioritization only works if the current event queue is an NSRunLoop; + * otherwise, performs as a normal invocation event. + */ + private static class OrderedInvocationEvent extends InvocationEvent { + int ordering; + NSSelector selector = null; + Object target = null; + Object parameter = null; + + /** + * Constructs an InvocationEvent with the specified source which will execute + * the runnable's run() method when dispatched at the specified ordering. + */ + public OrderedInvocationEvent(Object source, Runnable runnable, int anOrdering) { + super(source, runnable); + ordering = anOrdering; + } + + /** + * Constructs an InvocationEvent with the specified source which will execute + * the runnable's run() method when dispatched at the specified ordering. If + * notifier is non-null, notifyAll() will be called on it immediately after + * run() returns. + */ + public OrderedInvocationEvent(Object source, Runnable runnable, Object notifier, boolean catchExceptions, + int anOrdering) { + super(source, runnable, notifier, catchExceptions); + ordering = anOrdering; + } + + OrderedInvocationEvent(final NSSelector aSelector, final Object aTarget, final Object aParameter, + int anOrdering, List aModeList) { + this(Toolkit.getDefaultToolkit(), new Runnable() { + public void run() { + try { + aSelector.invoke(aTarget, aParameter); + } catch (Exception exc) { + System.out.println("NSRunLoop: error invoking selector: "); + exc.printStackTrace(); + } + } + }, anOrdering); + + selector = aSelector; + target = aTarget; + parameter = aParameter; + } + + /** + * Called by cancelPerformSelectorWithOrder. Compares against the specified + * arguments. + */ + boolean compareTo(NSSelector aSelector, Object aTarget, Object aParameter) { + return (compareByValue(selector, aSelector) && compareByValue(target, aTarget) + && compareByValue(parameter, aParameter)); + } + + private boolean compareByValue(Object first, Object second) { + if (first == second) + return true; + if (first == null) + return second.equals(first); + return first.equals(second); + + } + + /** + * Returns the ordering for this event in the run loop. + */ + public int getOrdering() { + return ordering; + } + + } + } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.13 2003/06/06 20:48:19 mpowers - * Fixed race condition when run loop is started from main thread. - * That was causing the dispatch thread to call getNextEvent before the - * static fields had been initialized. + * Revision 1.13 2003/06/06 20:48:19 mpowers Fixed race condition when run loop + * is started from main thread. That was causing the dispatch thread to call + * getNextEvent before the static fields had been initialized. * - * Revision 1.12 2003/06/03 14:52:11 mpowers - * Super constructor was calling getNextEvent before selector was created. + * Revision 1.12 2003/06/03 14:52:11 mpowers Super constructor was calling + * getNextEvent before selector was created. * - * Revision 1.10 2002/05/28 21:59:19 mpowers - * We now can compile against 1.3 and 1.4, as well as run against both too. + * Revision 1.10 2002/05/28 21:59:19 mpowers We now can compile against 1.3 and + * 1.4, as well as run against both too. * - * Revision 1.9 2002/04/09 18:10:45 mpowers - * Fixes for 1.4. Commented out until we start building on 1.4. + * Revision 1.9 2002/04/09 18:10:45 mpowers Fixes for 1.4. Commented out until + * we start building on 1.4. * - * Revision 1.8 2002/02/13 21:20:15 mpowers - * Updated comments. + * Revision 1.8 2002/02/13 21:20:15 mpowers Updated comments. * - * Revision 1.7 2001/11/01 15:48:49 mpowers - * Additional debug code. + * Revision 1.7 2001/11/01 15:48:49 mpowers Additional debug code. * - * Revision 1.6 2001/10/30 22:14:35 mpowers - * Constructor is now protected, not private. + * Revision 1.6 2001/10/30 22:14:35 mpowers Constructor is now protected, not + * private. * - * Revision 1.5 2001/10/29 20:41:49 mpowers - * Improved docs, better support for potential subclassing, invokeLater. + * Revision 1.5 2001/10/29 20:41:49 mpowers Improved docs, better support for + * potential subclassing, invokeLater. * - * Revision 1.4 2001/10/26 18:46:30 mpowers - * Now running AWT events with the appropriate ordering. - * Added invokeLaterWithOrder for java compatibility. + * Revision 1.4 2001/10/26 18:46:30 mpowers Now running AWT events with the + * appropriate ordering. Added invokeLaterWithOrder for java compatibility. * - * Revision 1.3 2001/10/26 14:39:46 mpowers - * Completed implementation. + * Revision 1.3 2001/10/26 14:39:46 mpowers Completed implementation. * - * Revision 1.2 2001/10/25 22:20:21 mpowers - * Got to check in an interim version - this will briefly break the build. + * Revision 1.2 2001/10/25 22:20:21 mpowers Got to check in an interim version - + * this will briefly break the build. * - * Revision 1.1 2001/10/24 19:30:38 mpowers - * Initial check-in: incomplete implementation. + * Revision 1.1 2001/10/24 19:30:38 mpowers Initial check-in: incomplete + * implementation. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java index 965606b..8f5969a 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java @@ -26,366 +26,298 @@ import java.util.Comparator; import net.wotonomy.foundation.internal.PropertyComparator; /** -* A pure java implementation of NSSelector. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ -public class NSSelector implements Comparator, Serializable -{ + * A pure java implementation of NSSelector. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ +public class NSSelector implements Comparator, Serializable { protected NSMutableDictionary methodMap; // map of classes to methods protected String methodName; protected Class[] parameterTypes; /** - * A marker to indicate object not found. - */ + * A marker to indicate object not found. + */ protected static final String NOT_FOUND = "NOT_FOUND"; - + /** - * Saves creating a new class array for parameterless method invocation. - */ + * Saves creating a new class array for parameterless method invocation. + */ protected static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - + /** - * Saves creating a new object array for parameterless method invocation. - */ + * Saves creating a new object array for parameterless method invocation. + */ protected static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - /** - * Constructor specifying a method name and an array of parameter types. - */ - public NSSelector (String aMethodName, Class[] aParameterTypeArray) - { - methodName = aMethodName; - parameterTypes = aParameterTypeArray; - methodMap = new NSMutableDictionary(); - } - - /** - * Constructor specifying a method name with no parameters. - */ - public NSSelector (String aMethodName) - { - this( aMethodName, EMPTY_CLASS_ARRAY ); - } - - /** - * Constructor for custom subclasses that implement specific operators - * and that do not use dynamic method invocation. - */ - protected NSSelector() - { - } - - /** - * Returns the name of the method. - */ - public String name () - { - return methodName; - } - - /** - * Returns the array of parameter types. - */ - public Class[] parameterTypes () - { - return parameterTypes; - } - - /** - * A String description of this selector. - */ - public String toString () - { - StringBuffer result = new StringBuffer(); - result.append( "[" + getClass().getName() + ": name = " + name() + ", parameter types = [" ); - if ( parameterTypes != null ) - { - if ( parameterTypes.length > 0 ) - { - result.append( parameterTypes[0].toString() ); - } - for ( int i = 1; i < parameterTypes.length; i++ ) - { - result.append( ", " ); - result.append( parameterTypes[i].toString() ); - } - } - result.append( "] ]" ); - return result.toString(); - } - - /** - * Returns the appropriate method for the specified class. - */ - public Method methodOnClass (Class aClass) - throws NoSuchMethodException - { - Object result = methodMap.objectForKey( aClass ); - - if ( result == null ) - { - result = getMethodForClass( aClass ); - if ( result == null ) - { - result = NOT_FOUND; - } - methodMap.setObjectForKey( result, aClass ); + + /** + * Constructor specifying a method name and an array of parameter types. + */ + public NSSelector(String aMethodName, Class[] aParameterTypeArray) { + methodName = aMethodName; + parameterTypes = aParameterTypeArray; + methodMap = new NSMutableDictionary(); + } + + /** + * Constructor specifying a method name with no parameters. + */ + public NSSelector(String aMethodName) { + this(aMethodName, EMPTY_CLASS_ARRAY); + } + + /** + * Constructor for custom subclasses that implement specific operators and that + * do not use dynamic method invocation. + */ + protected NSSelector() { + } + + /** + * Returns the name of the method. + */ + public String name() { + return methodName; + } + + /** + * Returns the array of parameter types. + */ + public Class[] parameterTypes() { + return parameterTypes; + } + + /** + * A String description of this selector. + */ + public String toString() { + StringBuffer result = new StringBuffer(); + result.append("[" + getClass().getName() + ": name = " + name() + ", parameter types = ["); + if (parameterTypes != null) { + if (parameterTypes.length > 0) { + result.append(parameterTypes[0].toString()); + } + for (int i = 1; i < parameterTypes.length; i++) { + result.append(", "); + result.append(parameterTypes[i].toString()); + } + } + result.append("] ]"); + return result.toString(); + } + + /** + * Returns the appropriate method for the specified class. + */ + public Method methodOnClass(Class aClass) throws NoSuchMethodException { + Object result = methodMap.objectForKey(aClass); + + if (result == null) { + result = getMethodForClass(aClass); + if (result == null) { + result = NOT_FOUND; + } + methodMap.setObjectForKey(result, aClass); } - - if ( result == NOT_FOUND ) - { + + if (result == NOT_FOUND) { throw new NoSuchMethodException(); } return (Method) result; } - - /** - * Returns the appropriate method, or null if not found. - */ - private Method getMethodForClass( Class aClass ) - { + + /** + * Returns the appropriate method, or null if not found. + */ + private Method getMethodForClass(Class aClass) { Method[] methods = aClass.getMethods(); - for ( int i = 0; i < methods.length; i++ ) - { - if ( methods[i].getName().equals( name() ) ) - { + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals(name())) { Class[] params = methods[i].getParameterTypes(); - if ( params.length == parameterTypes.length ) - { + if (params.length == parameterTypes.length) { boolean pass = true; - for ( int j = 0; j < params.length; j++ ) - { - if ( ! params[j].isAssignableFrom( parameterTypes[j] ) ) - { + for (int j = 0; j < params.length; j++) { + if (!params[j].isAssignableFrom(parameterTypes[j])) { pass = false; } - } - if ( pass ) return methods[i]; + } + if (pass) + return methods[i]; } } } return null; } - /** - * Convenience to get a method for an object. - */ - public Method methodOnObject (Object anObject) - throws NoSuchMethodException - { - Method m = methodOnClass( anObject.getClass() ); - if ( m == null ) throw new NoSuchMethodException( name() ); - return m; + /** + * Convenience to get a method for an object. + */ + public Method methodOnObject(Object anObject) throws NoSuchMethodException { + Method m = methodOnClass(anObject.getClass()); + if (m == null) + throw new NoSuchMethodException(name()); + return m; } - /** - * Returns whether the class implements the method for this selector. - */ - public boolean implementedByClass (Class aClass) - { - try - { - methodOnClass( aClass ); + /** + * Returns whether the class implements the method for this selector. + */ + public boolean implementedByClass(Class aClass) { + try { + methodOnClass(aClass); return true; - } - catch ( NoSuchMethodException exc ) - { - } - return false; - } - - /** - * Returns whether the object's class implements the method - * for this selector. - */ - public boolean implementedByObject (Object anObject) - { - try - { - methodOnObject( anObject ); + } catch (NoSuchMethodException exc) { + } + return false; + } + + /** + * Returns whether the object's class implements the method for this selector. + */ + public boolean implementedByObject(Object anObject) { + try { + methodOnObject(anObject); return true; - } - catch ( NoSuchMethodException exc ) - { - } - return false; - } - - /** - * Invokes this selector's method on the specified object - * using the specified parameters. - */ - public Object invoke (Object anObject, Object[] parameters) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return methodOnObject( anObject ).invoke( anObject, parameters ); + } catch (NoSuchMethodException exc) { + } + return false; + } + + /** + * Invokes this selector's method on the specified object using the specified + * parameters. + */ + public Object invoke(Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return methodOnObject(anObject).invoke(anObject, parameters); + } + + /** + * Invokes this selector's method on the specified object with no parameters. + */ + public Object invoke(Object anObject) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return invoke(anObject, EMPTY_OBJECT_ARRAY); + } + + /** + * Invokes this selector's method on the specified object with the specified + * parameter. + */ + public Object invoke(Object anObject, Object aParameter) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return invoke(anObject, new Object[] { aParameter }); } - /** - * Invokes this selector's method on the specified object - * with no parameters. - */ - public Object invoke (Object anObject) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return invoke( anObject, EMPTY_OBJECT_ARRAY ); + /** + * Invokes this selector's method on the specified object using the specified + * two parameters. + */ + public Object invoke(Object anObject, Object p1, Object p2) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return invoke(anObject, new Object[] { p1, p2 }); + } + + /** + * Invokes the method with the specified signature on the specified object using + * the specified parameters. + */ + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return new NSSelector(methodName, parameterTypes).invoke(anObject, parameters); } - /** - * Invokes this selector's method on the specified object - * with the specified parameter. - */ - public Object invoke (Object anObject, Object aParameter) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return invoke( anObject, new Object[] { aParameter } ); + /** + * Invokes the method with the specified signature on the specified object with + * no parameters. + */ + public static Object invoke(String methodName, Object anObject) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return NSSelector.invoke(methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY); } - /** - * Invokes this selector's method on the specified object - * using the specified two parameters. - */ - public Object invoke (Object anObject, Object p1, Object p2) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return invoke( anObject, new Object[] { p1, p2 } ); + /** + * Invokes the method with the specified signature on the specified object using + * the specified parameter. + */ + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object aParameter) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return NSSelector.invoke(methodName, parameterTypes, anObject, new Object[] { aParameter }); } - - /** - * Invokes the method with the specified signature on the specified - * object using the specified parameters. - */ - public static Object invoke - (String methodName, Class[] parameterTypes, Object anObject, Object[] parameters) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return new NSSelector( methodName, parameterTypes ).invoke( anObject, parameters ); + + /** + * Invokes the method with the specified signature on the specified object using + * the specified two parameters. + */ + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object p1, Object p2) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return NSSelector.invoke(methodName, parameterTypes, anObject, new Object[] { p1, p2 }); } - /** - * Invokes the method with the specified signature on the specified object - * with no parameters. - */ - public static Object invoke - (String methodName, Object anObject) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return NSSelector.invoke( - methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY ); + // interface Comparator + + private Comparator comparator; + + /** + * Constructor specifying a method name and a comparator. This is not in the + * spec. + */ + public NSSelector(String aMethodName, Comparator aComparator) { + this(aMethodName, EMPTY_CLASS_ARRAY); + comparator = aComparator; } - /** - * Invokes the method with the specified signature on the specified - * object using the specified parameter. - */ - public static Object invoke - (String methodName, Class[] parameterTypes, - Object anObject, Object aParameter) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return NSSelector.invoke( - methodName, parameterTypes, anObject, new Object[] { aParameter } ); + /** + * Returns the Comparator used for this selector. This is not in the spec. + */ + public Comparator comparator() { + if (comparator == null) { + comparator = new PropertyComparator(methodName); + } + return comparator; } - /** - * Invokes the method with the specified signature on the specified - * object using the specified two parameters. - */ - public static Object invoke - (String methodName, Class[] parameterTypes, - Object anObject, Object p1, Object p2) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return NSSelector.invoke( - methodName, parameterTypes, anObject, new Object[] { p1, p2 } ); + public int compare(Object o1, Object o2) { + if (comparator == null) { + comparator = new PropertyComparator(methodName); + } + return comparator.compare(o1, o2); } - // interface Comparator - - private Comparator comparator; - - /** - * Constructor specifying a method name and a comparator. - * This is not in the spec. - */ - public NSSelector (String aMethodName, Comparator aComparator) - { - this( aMethodName, EMPTY_CLASS_ARRAY ); - comparator = aComparator; - } - - /** - * Returns the Comparator used for this selector. - * This is not in the spec. - */ - public Comparator comparator() - { - if ( comparator == null ) - { - comparator = new PropertyComparator( methodName ); - } - return comparator; - } - - public int compare(Object o1, Object o2) - { - if ( comparator == null ) - { - comparator = new PropertyComparator( methodName ); - } - return comparator.compare( o1, o2 ); - } - - public boolean equals(Object obj) - { - return ( obj == this ); - } + public boolean equals(Object obj) { + return (obj == this); + } } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.9 2003/02/12 19:34:35 mpowers - * Added accessor for comparator. + * Revision 1.9 2003/02/12 19:34:35 mpowers Added accessor for comparator. * - * Revision 1.8 2003/02/07 20:23:41 mpowers - * Provided backwards compatibility for comparators. + * Revision 1.8 2003/02/07 20:23:41 mpowers Provided backwards compatibility for + * comparators. * - * Revision 1.7 2003/01/22 23:02:25 mpowers - * Fixed a null pointer error in NSSelector.toString. + * Revision 1.7 2003/01/22 23:02:25 mpowers Fixed a null pointer error in + * NSSelector.toString. * - * Revision 1.6 2003/01/18 23:46:58 mpowers - * EOSortOrdering is now correctly using NSSelectors. + * Revision 1.6 2003/01/18 23:46:58 mpowers EOSortOrdering is now correctly + * using NSSelectors. * - * Revision 1.4 2001/10/31 15:24:45 mpowers - * Implicit constructor is now protected. + * Revision 1.4 2001/10/31 15:24:45 mpowers Implicit constructor is now + * protected. * - * Revision 1.3 2001/02/07 19:25:51 mpowers - * Fixed: method matching uses isAssignableFrom rather than ==. + * Revision 1.3 2001/02/07 19:25:51 mpowers Fixed: method matching uses + * isAssignableFrom rather than ==. * - * Revision 1.2 2001/01/08 23:30:16 mpowers - * Fixed major bug - selectors were not supposed to share a method map. + * Revision 1.2 2001/01/08 23:30:16 mpowers Fixed major bug - selectors were not + * supposed to share a method map. * - * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:39 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:39 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java index 5318fe8..38a7ec6 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java @@ -26,187 +26,154 @@ import java.util.Set; import java.util.Vector; /** -* A pure java implementation of NSSet that -* implements Set for greater java interoperability. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NSSet extends HashSet -{ - /** - * Default constructor. - */ - public NSSet () - { - super(); - } - - /** - * Constructs a NSSet containing the objects - * in the specified collection. - */ - public NSSet ( Collection aCollection ) - { - super( aCollection ); - } - - /** - * Constructs a NSSet containing only - * the specified object. - */ - public NSSet ( Object anObject ) - { - super(); - add( anObject ); - } - - /** - * Constructs a NSSet containing the objects - * in the specified array. - */ - public NSSet ( Object[] anObjectArray ) - { - super(); - for ( int i = 0; i < anObjectArray.length; i++ ) - { - add( anObjectArray[i] ); - } - } - - /** - * Returns an NSArray containing all objects in the set. - */ - public NSArray allObjects () - { - return new NSArray( this ); - } - - /** - * - */ - public Object anyObject () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether this set contains the - * specified object. - */ - public boolean containsObject ( Object anObject ) - { - return contains( anObject ); - } - - /** - * Returns the number of elements in this set. - */ - public int count () - { - return size(); - } - - /** - * Returns whether this set has one or more - * elements in common with the specified set. - */ - public boolean intersectsSet ( Set aSet ) - { - Iterator it = aSet.iterator(); - while ( it.hasNext() ) - { - if ( this.containsObject( it.next() ) ) - { - return true; - } - } - return false; - - } - - /** - * Returns whether this set contains the - * same object as the specified set. - */ - public boolean isEqualToSet ( Set aSet ) - { - return equals( aSet ); - } - - /** - * Returns whether this set is a subset - * of the specified set. - */ - public boolean isSubsetOfSet ( Set aSet ) - { - return aSet.containsAll( this ); - } - - /** - * - */ - public Object member ( Object anObject ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns an enumerator over the objects - * in this set. - */ - public Enumeration objectEnumerator () - { - return new Vector( this ).elements(); - } - - /** - * Returns a set that is the intersection - * of this set and the specified set. - */ - public NSSet setByIntersectingSet ( Set aSet ) - { - NSSet result = new NSSet( this ); - result.retainAll( aSet ); - return result; - } - - /** - * Returns a set that contains all elements - * in this set that are not in the specified set. - */ - public NSSet setBySubtractingSet ( Set aSet ) - { - NSSet result = new NSSet( this ); - result.removeAll( aSet ); - return result; - } - - /** - * Returns a set that is the union - * of this set and the specified set. - */ - public NSSet setByUnioningSet ( Set aSet ) - { - NSSet result = new NSSet( this ); - result.addAll( aSet ); - return result; - } - + * A pure java implementation of NSSet that implements Set for greater java + * interoperability. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NSSet extends HashSet { + /** + * Default constructor. + */ + public NSSet() { + super(); + } + + /** + * Constructs a NSSet containing the objects in the specified collection. + */ + public NSSet(Collection aCollection) { + super(aCollection); + } + + /** + * Constructs a NSSet containing only the specified object. + */ + public NSSet(Object anObject) { + super(); + add(anObject); + } + + /** + * Constructs a NSSet containing the objects in the specified array. + */ + public NSSet(Object[] anObjectArray) { + super(); + for (int i = 0; i < anObjectArray.length; i++) { + add(anObjectArray[i]); + } + } + + /** + * Returns an NSArray containing all objects in the set. + */ + public NSArray allObjects() { + return new NSArray(this); + } + + /** + * + */ + public Object anyObject() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether this set contains the specified object. + */ + public boolean containsObject(Object anObject) { + return contains(anObject); + } + + /** + * Returns the number of elements in this set. + */ + public int count() { + return size(); + } + + /** + * Returns whether this set has one or more elements in common with the + * specified set. + */ + public boolean intersectsSet(Set aSet) { + Iterator it = aSet.iterator(); + while (it.hasNext()) { + if (this.containsObject(it.next())) { + return true; + } + } + return false; + + } + + /** + * Returns whether this set contains the same object as the specified set. + */ + public boolean isEqualToSet(Set aSet) { + return equals(aSet); + } + + /** + * Returns whether this set is a subset of the specified set. + */ + public boolean isSubsetOfSet(Set aSet) { + return aSet.containsAll(this); + } + + /** + * + */ + public Object member(Object anObject) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns an enumerator over the objects in this set. + */ + public Enumeration objectEnumerator() { + return new Vector(this).elements(); + } + + /** + * Returns a set that is the intersection of this set and the specified set. + */ + public NSSet setByIntersectingSet(Set aSet) { + NSSet result = new NSSet(this); + result.retainAll(aSet); + return result; + } + + /** + * Returns a set that contains all elements in this set that are not in the + * specified set. + */ + public NSSet setBySubtractingSet(Set aSet) { + NSSet result = new NSSet(this); + result.removeAll(aSet); + return result; + } + + /** + * Returns a set that is the union of this set and the specified set. + */ + public NSSet setByUnioningSet(Set aSet) { + NSSet result = new NSSet(this); + result.addAll(aSet); + return result; + } + } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:39 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:39 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java index 171e756..7c7b4da 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java @@ -26,247 +26,245 @@ import java.io.Serializable; import java.util.Date; import java.util.Locale; import java.util.TimeZone; + /** -* A channel to the database, representing a communication -* stream within a context of an adaptor. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ + * A channel to the database, representing a communication stream within a + * context of an adaptor. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ -public class NSTimeZone extends TimeZone - implements Cloneable, Serializable, NSCoding { - protected static class __NSTZPeriodComparator extends NSComparator { +public class NSTimeZone extends TimeZone implements Cloneable, Serializable, NSCoding { + protected static class __NSTZPeriodComparator extends NSComparator { - protected boolean _ascending = false; + protected boolean _ascending = false; - public int compare(Object obj, Object obj1) throws NSComparator.ComparisonException { + public int compare(Object obj, Object obj1) throws NSComparator.ComparisonException { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public __NSTZPeriodComparator() { + public __NSTZPeriodComparator() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public __NSTZPeriodComparator(boolean flag) { + public __NSTZPeriodComparator(boolean flag) { throw new UnsupportedOperationException("Not Yet Implemented"); - } - } + } + } - protected static class __NSTZPeriod { + protected static class __NSTZPeriod { - protected String _abbreviation = null; - protected int _isdst = 0; - protected int _offset = 0; - protected double _startTime = 0; + protected String _abbreviation = null; + protected int _isdst = 0; + protected int _offset = 0; + protected double _startTime = 0; - protected boolean before(__NSTZPeriod _p_nstzperiod) { + protected boolean before(__NSTZPeriod _p_nstzperiod) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - protected boolean equals(__NSTZPeriod _p_nstzperiod) { + protected boolean equals(__NSTZPeriod _p_nstzperiod) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - protected __NSTZPeriod() { + protected __NSTZPeriod() { throw new UnsupportedOperationException("Not Yet Implemented"); - } - } - + } + } - public static final String SystemTimeZoneDidChangeNotification = "NSSystemTimeZoneDidChangeNotification"; - protected NSData _data = null; - protected transient int _hashCode = 0; - protected transient boolean _initialized = false; - protected transient TimeZone _jdkTimeZone = null; - protected String _name = null; - protected transient int _rawOffset = 0; - protected transient NSMutableArray _timeZonePeriods = null; - protected transient int _timeZonePeriodsCount = 0; - protected transient boolean _useDaylightTime = false; + public static final String SystemTimeZoneDidChangeNotification = "NSSystemTimeZoneDidChangeNotification"; + protected NSData _data = null; + protected transient int _hashCode = 0; + protected transient boolean _initialized = false; + protected transient TimeZone _jdkTimeZone = null; + protected String _name = null; + protected transient int _rawOffset = 0; + protected transient NSMutableArray _timeZonePeriods = null; + protected transient int _timeZonePeriodsCount = 0; + protected transient boolean _useDaylightTime = false; - public NSTimeZone() { + public NSTimeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - protected NSTimeZone(String s, NSData nsdata) { + protected NSTimeZone(String s, NSData nsdata) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static NSDictionary abbreviationDictionary() { + public static NSDictionary abbreviationDictionary() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public Class classForCoder() { - return getClass(); - } + public Class classForCoder() { + return getClass(); + } - public Object clone() { + public Object clone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static Object decodeObject(NSCoder nscoder) { + public static Object decodeObject(NSCoder nscoder) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void encodeWithCoder(NSCoder nscoder) { + public void encodeWithCoder(NSCoder nscoder) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized NSTimeZone defaultTimeZone() { + public static synchronized NSTimeZone defaultTimeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static String[] getAvailableIDs() { + public static String[] getAvailableIDs() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static TimeZone getDefault() { + public static TimeZone getDefault() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static NSArray knownTimeZoneNames() { + public static NSArray knownTimeZoneNames() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static NSTimeZone localTimeZone() { + public static NSTimeZone localTimeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized void resetSystemTimeZone() { + public static synchronized void resetSystemTimeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized void setDefault(TimeZone timezone) { + public static synchronized void setDefault(TimeZone timezone) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized void setDefaultTimeZone(NSTimeZone nstimezone) { + public static synchronized void setDefaultTimeZone(NSTimeZone nstimezone) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void setID(String s) { - throw new IllegalStateException(getClass().getName() + " is immutable."); - } + public void setID(String s) { + throw new IllegalStateException(getClass().getName() + " is immutable."); + } - public void setRawOffset(int i) { - throw new IllegalStateException(getClass().getName() + " is immutable."); - } + public void setRawOffset(int i) { + throw new IllegalStateException(getClass().getName() + " is immutable."); + } - public static synchronized NSTimeZone systemTimeZone() { + public static synchronized NSTimeZone systemTimeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized NSTimeZone timeZoneForSecondsFromGMT(int i) { + public static synchronized NSTimeZone timeZoneForSecondsFromGMT(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized NSTimeZone timeZoneWithName(String s, boolean flag) { + public static synchronized NSTimeZone timeZoneWithName(String s, boolean flag) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static synchronized NSTimeZone timeZoneWithNameAndData(String s, NSData nsdata) { + public static synchronized NSTimeZone timeZoneWithNameAndData(String s, NSData nsdata) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public static NSTimeZone _nstimeZoneWithTimeZone(TimeZone timezone) { + public static NSTimeZone _nstimeZoneWithTimeZone(TimeZone timezone) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String abbreviation() { + public String abbreviation() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String abbreviationForTimestamp(NSTimestamp nstimestamp) { + public String abbreviationForTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSData data() { + public NSData data() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean equals(Object obj) { + public boolean equals(Object obj) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String getDisplayName(boolean flag, int i, Locale locale) { + public String getDisplayName(boolean flag, int i, Locale locale) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String getID() { + public String getID() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int getOffset(int i, int j, int k, int l, int i1, int j1) { + public int getOffset(int i, int j, int k, int l, int i1, int j1) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int getRawOffset() { + public int getRawOffset() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public synchronized int hashCode() { + public synchronized int hashCode() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean hasSameRules(TimeZone timezone) { + public boolean hasSameRules(TimeZone timezone) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean inDaylightTime(Date date) { + public boolean inDaylightTime(Date date) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean isDaylightSavingTime() { + public boolean isDaylightSavingTime() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean isDaylightSavingTimeForTimestamp(NSTimestamp nstimestamp) { + public boolean isDaylightSavingTimeForTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean isEqualToTimeZone(NSTimeZone nstimezone) { + public boolean isEqualToTimeZone(NSTimeZone nstimezone) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String name() { + public String name() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int secondsFromGMT() { + public int secondsFromGMT() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int secondsFromGMTForTimestamp(NSTimestamp nstimestamp) { + public int secondsFromGMTForTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String toString() { + public String toString() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public boolean useDaylightTime() { + public boolean useDaylightTime() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - protected Object readResolve() throws ObjectStreamException { + protected Object readResolve() throws ObjectStreamException { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.1 2002/06/25 07:52:57 cgruber - * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:57 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java index bd246e0..03590f2 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java @@ -26,260 +26,255 @@ import java.util.Date; import java.util.TimeZone; /** -* A channel to the database, representing a communication -* stream within a context of an adaptor. -* -* @author cgruber@israfil.net -* @author $Author: cgruber $ -* @version $Revision: 892 $ -*/ + * A channel to the database, representing a communication stream within a + * context of an adaptor. + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + */ -public class NSTimestamp extends Timestamp - implements NSCoding { +public class NSTimestamp extends Timestamp implements NSCoding { - public static class IntRef { + public static class IntRef { - public int value = 0; + public int value = 0; - public String toString() { - return getClass().getName() + " < value = " + value + " >"; - } + public String toString() { + return getClass().getName() + " < value = " + value + " >"; + } - public IntRef() { - } - } + public IntRef() { + } + } + public static final NSTimestamp DistantPast = new NSTimestamp(0xffffc77f2e9b6800L); + public static final NSTimestamp DistantFuture = new NSTimestamp(0x7fffffffffffffffL); - public static final NSTimestamp DistantPast = new NSTimestamp(0xffffc77f2e9b6800L); - public static final NSTimestamp DistantFuture = new NSTimestamp(0x7fffffffffffffffL); - public static long currentTimeIntervalSinceReferenceDate() { - return System.currentTimeMillis() / 1000L; - } + public static long currentTimeIntervalSinceReferenceDate() { + return System.currentTimeMillis() / 1000L; + } - public static NSTimestamp distantFuture() { - return DistantFuture; - } + public static NSTimestamp distantFuture() { + return DistantFuture; + } - public static NSTimestamp distantPast() { - return DistantPast; - } + public static NSTimestamp distantPast() { + return DistantPast; + } - public static long millisecondsToTimeInterval(long l) { - return l / 1000L; - } + public static long millisecondsToTimeInterval(long l) { + return l / 1000L; + } - public static long timeIntervalToMilliseconds(long l) { - return l * 1000L; - } + public static long timeIntervalToMilliseconds(long l) { + return l * 1000L; + } - public Class classForCoder() { - return getClass(); - } + public Class classForCoder() { + return getClass(); + } - public static Object decodeObject(NSCoder nscoder) { + public static Object decodeObject(NSCoder nscoder) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void encodeWithCoder(NSCoder nscoder) { + public void encodeWithCoder(NSCoder nscoder) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp() { - super(0); + public NSTimestamp() { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(long l) { - super(l); + public NSTimestamp(long l) { + super(l); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(long l, int i) { - super(0); + public NSTimestamp(long l, int i) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(long l, NSTimestamp nstimestamp) { - super(0); + public NSTimestamp(long l, NSTimestamp nstimestamp) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(long l, TimeZone timezone) { - super(0); + public NSTimestamp(long l, TimeZone timezone) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(long l, int i, TimeZone timezone) { - super(0); + public NSTimestamp(long l, int i, TimeZone timezone) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(int i, int j, int k, int l, int i1, int j1, TimeZone timezone) { - super(0); + public NSTimestamp(int i, int j, int k, int l, int i1, int j1, TimeZone timezone) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(Date date) { - super(0); + public NSTimestamp(Date date) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp(Timestamp timestamp) { - super(0); + public NSTimestamp(Timestamp timestamp) { + super(0); throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp timestampByAddingGregorianUnits(int i, int j, int k, int l, int i1, int j1) { + public NSTimestamp timestampByAddingGregorianUnits(int i, int j, int k, int l, int i1, int j1) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp timestampByAddingTimeInterval(long l) { + public NSTimestamp timestampByAddingTimeInterval(long l) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long dayOfCommonEra() { + public long dayOfCommonEra() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int dayOfMonth() { + public int dayOfMonth() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int dayOfWeek() { + public int dayOfWeek() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int dayOfYear() { + public int dayOfYear() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int hourOfDay() { + public int hourOfDay() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int microsecondOfSecond() { + public int microsecondOfSecond() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int minuteOfHour() { + public int minuteOfHour() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int monthOfYear() { + public int monthOfYear() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int secondOfMinute() { + public int secondOfMinute() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int yearOfCommonEra() { + public int yearOfCommonEra() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void gregorianUnitsSinceTimestamp(IntRef intref, IntRef intref1, IntRef intref2, IntRef intref3, IntRef intref4, IntRef intref5, NSTimestamp nstimestamp) { + public void gregorianUnitsSinceTimestamp(IntRef intref, IntRef intref1, IntRef intref2, IntRef intref3, + IntRef intref4, IntRef intref5, NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long timeIntervalSinceTimestamp(NSTimestamp nstimestamp) { + public long timeIntervalSinceTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long timeIntervalSinceNow() { + public long timeIntervalSinceNow() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long timeIntervalSinceReferenceDate() { + public long timeIntervalSinceReferenceDate() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public int compare(NSTimestamp nstimestamp) { + public int compare(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp earlierTimestamp(NSTimestamp nstimestamp) { + public NSTimestamp earlierTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimestamp laterTimestamp(NSTimestamp nstimestamp) { + public NSTimestamp laterTimestamp(NSTimestamp nstimestamp) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public String toString() { + public String toString() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public NSTimeZone timeZone() { + public NSTimeZone timeZone() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long _getTimeInMillis() { + public long _getTimeInMillis() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void setNanos(int i) { + public void setNanos(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setDate(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setDate(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setHours(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setHours(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setMinutes(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setMinutes(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setMonth(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setMonth(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setSeconds(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setSeconds(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public void setTime(long l) { + public void setTime(long l) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - public long getTime() { + public long getTime() { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } - /** @deprecated This method deprecated in parent java.util.Date */ - public void setYear(int i) { + /** @deprecated This method deprecated in parent java.util.Date */ + public void setYear(int i) { throw new UnsupportedOperationException("Not Yet Implemented"); - } + } } /* - * $Log$ - * Revision 1.1 2006/02/16 12:47:16 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 12:47:16 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2002/07/14 21:56:16 mpowers - * Contributions from cgruber. + * Revision 1.1 2002/07/14 21:56:16 mpowers Contributions from cgruber. * - * Revision 1.3 2002/06/25 19:06:13 cgruber - * Comment fix. + * Revision 1.3 2002/06/25 19:06:13 cgruber Comment fix. * - * Revision 1.2 2002/06/25 19:05:27 cgruber - * Add deprecation statements to remove warnings - * about java.util.Date's deprecated APIs. + * Revision 1.2 2002/06/25 19:05:27 cgruber Add deprecation statements to remove + * warnings about java.util.Date's deprecated APIs. * - * Revision 1.1 2002/06/25 07:52:56 cgruber - * Add quite a few abstract classes, interfaces, and classes. All - * API consistent with WebObjects, but with no implementation, nor - * any private or package access members from the original. + * Revision 1.1 2002/06/25 07:52:56 cgruber Add quite a few abstract classes, + * interfaces, and classes. All API consistent with WebObjects, but with no + * implementation, nor any private or package access members from the original. * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java index ecc67ca..c8fbc44 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java @@ -25,41 +25,34 @@ import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; /** -* A Format that accepts C-style date formatting syntax. -* Not currently implemented, included for compile compatibility. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A Format that accepts C-style date formatting syntax. Not currently + * implemented, included for compile compatibility. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ + +public class NSTimestampFormatter extends SimpleDateFormat { + public NSTimestampFormatter() { + super(); + } + + public NSTimestampFormatter(String aPattern) { + super(aPattern); + } -public class NSTimestampFormatter extends SimpleDateFormat -{ - public NSTimestampFormatter() - { - super(); - } - - public NSTimestampFormatter(String aPattern) - { - super( aPattern ); - } + public NSTimestampFormatter(String aPattern, DateFormatSymbols symbols) { + super(aPattern, symbols); + } - public NSTimestampFormatter(String aPattern, - DateFormatSymbols symbols) - { - super( aPattern, symbols ); - } - } /* - * $Log$ - * Revision 1.2 2006/02/16 13:15:00 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:15:00 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/17 14:40:51 mpowers - * Adding files to fix build. + * Revision 1.1 2003/01/17 14:40:51 mpowers Adding files to fix build. * * */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java index ddf347d..16ef393 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java @@ -26,274 +26,219 @@ import java.io.*; import java.util.*; //collections /** -* Duplicator makes use of Introspector to duplicate objects, -* either by shallow copy, deep copy, or by copying properties -* from one object to apply to another object. You may find this -* class useful because java.lang.Object.clone() only supports -* shallow copying. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 895 $ -*/ + * Duplicator makes use of Introspector to duplicate objects, either by shallow + * copy, deep copy, or by copying properties from one object to apply to another + * object. You may find this class useful because java.lang.Object.clone() only + * supports shallow copying. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 895 $ + */ + +public class Duplicator { + /** + * Used to represent null values for properties in the maps returned by + * readProperties and cloneProperties and in the parameter to writeProperties. + * This actually references the NSNull instance. + */ + public static final Object NULL = NSNull.nullValue(); + private static NSSelector clone = new NSSelector("clone"); + + /** + * Returns a list of properties for the specified class that are both readable + * and writable. + */ + static public List editablePropertiesForObject(Object anObject) { + List readProperties = new ArrayList(); + String[] read = Introspector.getReadPropertiesForObject(anObject); + for (int i = 0; i < read.length; i++) { + readProperties.add(read[i]); + } + + List properties = new ArrayList(); + String[] write = Introspector.getWritePropertiesForObject(anObject); + for (int i = 0; i < write.length; i++) { + properties.add(write[i]); + } + + // only use properties on both lists: read/write + properties.retainAll(readProperties); + + return properties; + } -public class Duplicator -{ - /** - * Used to represent null values for properties in the - * maps returned by readProperties and cloneProperties - * and in the parameter to writeProperties. - * This actually references the NSNull instance. - */ - public static final Object NULL = NSNull.nullValue(); - private static NSSelector clone = new NSSelector( "clone" ); + /** + * Returns a Map containing only the mutable properties for the specified object + * and their values. Any null values for properties will be represented with the + * NULL object. + */ + static public Map readPropertiesForObject(Object anObject) { + NSMutableDictionary result = new NSMutableDictionary(); - /** - * Returns a list of properties for the specified class - * that are both readable and writable. - */ - static public List editablePropertiesForObject( - Object anObject ) - { - List readProperties = new ArrayList(); - String[] read = Introspector.getReadPropertiesForObject( anObject ); - for ( int i = 0; i < read.length; i++ ) - { - readProperties.add( read[i] ); - } + String key; + Object value; + Iterator it = editablePropertiesForObject(anObject).iterator(); + while (it.hasNext()) { + key = it.next().toString(); + value = Introspector.get(anObject, key); + if (value == null) + value = NULL; + result.setObjectForKey(value, key); + } + return result; + } - List properties = new ArrayList(); - String[] write = Introspector.getWritePropertiesForObject( anObject ); - for ( int i = 0; i < write.length; i++ ) - { - properties.add( write[i] ); - } - - // only use properties on both lists: read/write - properties.retainAll( readProperties ); + /** + * Returns a Map containing only the mutable properties for the specified object + * and deep clones of their values. Nulls are represented by the NULL object. + */ + static public Map clonePropertiesForObject(Object anObject) { + Object key, value; + Map result = readPropertiesForObject(anObject); + Iterator it = result.keySet().iterator(); + while (it.hasNext()) { + key = it.next(); + value = result.get(key); + value = deepClone(value); + result.put(key, value); + } + return result; + } - return properties; - } - - /** - * Returns a Map containing only the mutable properties - * for the specified object and their values. - * Any null values for properties will be represented with - * the NULL object. - */ - static public Map readPropertiesForObject( - Object anObject ) - { - NSMutableDictionary result = new NSMutableDictionary(); - - String key; - Object value; - Iterator it = editablePropertiesForObject( anObject ).iterator(); - while ( it.hasNext() ) - { - key = it.next().toString(); - value = Introspector.get( anObject, key ); - if ( value == null ) value = NULL; - result.setObjectForKey( value, key ); - } - return result; - } - - /** - * Returns a Map containing only the mutable properties - * for the specified object and deep clones of their values. - * Nulls are represented by the NULL object. - */ - static public Map clonePropertiesForObject( - Object anObject ) - { - Object key, value; - Map result = readPropertiesForObject( anObject ); - Iterator it = result.keySet().iterator(); - while ( it.hasNext() ) - { - key = it.next(); - value = result.get( key ); - value = deepClone( value ); - result.put( key, value ); - } - return result; - } - - /** - * Applies the map of properties and values to the - * specified object. Null values for properties must - * be represented by the NULL object. - */ - static public void writePropertiesForObject( - Map aMap, Object anObject ) - { - String key; - Object value; - Iterator it = aMap.keySet().iterator(); - while ( it.hasNext() ) - { - key = it.next().toString(); - value = aMap.get( key ); - if ( NULL.equals( value ) ) value = null; - Introspector.set( anObject, key, value ); - } - } - - /** - * Creates a new copy of the specified object. - * This implementation tries to call clone(), - * and failing that, calls newInstance - * and then calls copy() to transfer the values. - * @throws WotonomyException if any operation fails. - */ - static public Object clone( - Object aSource ) - { - Object result = null; - if ( clone.implementedByObject( aSource ) ) - { - try - { - result = clone.invoke( aSource ); - return result; - } - catch ( Exception exc ) - { - // fall back on newInstance() - } - } - - Class c = aSource.getClass(); - try - { - result = c.newInstance(); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - return copy( aSource, result ); - } + /** + * Applies the map of properties and values to the specified object. Null values + * for properties must be represented by the NULL object. + */ + static public void writePropertiesForObject(Map aMap, Object anObject) { + String key; + Object value; + Iterator it = aMap.keySet().iterator(); + while (it.hasNext()) { + key = it.next().toString(); + value = aMap.get(key); + if (NULL.equals(value)) + value = null; + Introspector.set(anObject, key, value); + } + } - /** - * Creates a deep copy of the specified object. - * Every object in this objects graph will be - * duplicated with new instances. - * @throws WotonomyException if any operation fails. - */ - static public Object deepClone( - Object aSource ) - { - // the only known way to deep copy in - // java without native code is serialization - - try - { - ByteArrayOutputStream byteOutput = - new ByteArrayOutputStream(); - ObjectOutputStream objectOutput = - new ObjectOutputStream( byteOutput ); - - objectOutput.writeObject( aSource ); - objectOutput.flush(); - objectOutput.close(); - - ByteArrayInputStream byteInput = - new ByteArrayInputStream( byteOutput.toByteArray() ); - ObjectInputStream objectInput = - new ObjectInputStream( byteInput ); - return objectInput.readObject(); - } - catch ( Exception exc ) - { - throw new WotonomyException( "Error cloning object: " + aSource, exc ); - } - } + /** + * Creates a new copy of the specified object. This implementation tries to call + * clone(), and failing that, calls newInstance and then calls copy() to + * transfer the values. + * + * @throws WotonomyException if any operation fails. + */ + static public Object clone(Object aSource) { + Object result = null; + if (clone.implementedByObject(aSource)) { + try { + result = clone.invoke(aSource); + return result; + } catch (Exception exc) { + // fall back on newInstance() + } + } - /** - * Copies values from one object to another. - * Returns the destination object. - * @throws WotonomyException if any operation fails. - */ - static public Object copy( - Object aSource, Object aDestination ) - { - try - { - writePropertiesForObject( - readPropertiesForObject( aSource ), aDestination ); - } - catch ( RuntimeException exc ) - { - throw new WotonomyException( exc ); - } - return aDestination; - } - - /** - * Deeply clones the values from one object and applies them - * to another object. - * Returns the destination object. - * @throws WotonomyException if any operation fails. - */ - static public Object deepCopy( - Object aSource, Object aDestination ) - { - try - { - writePropertiesForObject( - clonePropertiesForObject( aSource ), aDestination ); - } - catch ( RuntimeException exc ) - { - throw new WotonomyException( exc ); - } - return aDestination; - } + Class c = aSource.getClass(); + try { + result = c.newInstance(); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + return copy(aSource, result); + } + + /** + * Creates a deep copy of the specified object. Every object in this objects + * graph will be duplicated with new instances. + * + * @throws WotonomyException if any operation fails. + */ + static public Object deepClone(Object aSource) { + // the only known way to deep copy in + // java without native code is serialization + + try { + ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); + ObjectOutputStream objectOutput = new ObjectOutputStream(byteOutput); + + objectOutput.writeObject(aSource); + objectOutput.flush(); + objectOutput.close(); + + ByteArrayInputStream byteInput = new ByteArrayInputStream(byteOutput.toByteArray()); + ObjectInputStream objectInput = new ObjectInputStream(byteInput); + return objectInput.readObject(); + } catch (Exception exc) { + throw new WotonomyException("Error cloning object: " + aSource, exc); + } + } + + /** + * Copies values from one object to another. Returns the destination object. + * + * @throws WotonomyException if any operation fails. + */ + static public Object copy(Object aSource, Object aDestination) { + try { + writePropertiesForObject(readPropertiesForObject(aSource), aDestination); + } catch (RuntimeException exc) { + throw new WotonomyException(exc); + } + return aDestination; + } + + /** + * Deeply clones the values from one object and applies them to another object. + * Returns the destination object. + * + * @throws WotonomyException if any operation fails. + */ + static public Object deepCopy(Object aSource, Object aDestination) { + try { + writePropertiesForObject(clonePropertiesForObject(aSource), aDestination); + } catch (RuntimeException exc) { + throw new WotonomyException(exc); + } + return aDestination; + } } /* - * $Log$ - * Revision 1.1 2006/02/16 16:52:12 cgruber - * Add cvsignore crap to find off checking in binary crap. + * $Log$ Revision 1.1 2006/02/16 16:52:12 cgruber Add cvsignore crap to find off + * checking in binary crap. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.11 2001/08/22 19:24:26 mpowers - * Providing a more helpful error message for cloning exceptions. + * Revision 1.11 2001/08/22 19:24:26 mpowers Providing a more helpful error + * message for cloning exceptions. * - * Revision 1.10 2001/03/29 03:30:36 mpowers - * Refactored duplicator a bit. + * Revision 1.10 2001/03/29 03:30:36 mpowers Refactored duplicator a bit. * Disabled MissingPropertyExceptions for now. * - * Revision 1.9 2001/03/28 14:11:23 mpowers - * Removed debugging printlns. + * Revision 1.9 2001/03/28 14:11:23 mpowers Removed debugging printlns. * - * Revision 1.8 2001/03/27 23:25:48 mpowers - * Basically reverting to the previous version. + * Revision 1.8 2001/03/27 23:25:48 mpowers Basically reverting to the previous + * version. * - * Revision 1.7 2001/03/06 23:18:13 mpowers - * Clarified some comments. + * Revision 1.7 2001/03/06 23:18:13 mpowers Clarified some comments. * - * Revision 1.6 2001/03/01 20:36:35 mpowers - * Better error handling and better handling of nulls. + * Revision 1.6 2001/03/01 20:36:35 mpowers Better error handling and better + * handling of nulls. * - * Revision 1.5 2001/02/27 21:43:40 mpowers - * Removed NullMarker class in favor of NSNull. + * Revision 1.5 2001/02/27 21:43:40 mpowers Removed NullMarker class in favor of + * NSNull. * - * Revision 1.4 2001/02/26 22:41:51 mpowers - * Implemented null placeholder classes. - * Duplicator now uses NSNull. - * No longer catching base exception class. + * Revision 1.4 2001/02/26 22:41:51 mpowers Implemented null placeholder + * classes. Duplicator now uses NSNull. No longer catching base exception class. * - * Revision 1.3 2001/02/23 21:07:46 mpowers - * Documented the NULL object. + * Revision 1.3 2001/02/23 21:07:46 mpowers Documented the NULL object. * - * Revision 1.1 2001/02/16 22:51:29 mpowers - * Now deep-cloning objects passed between editing contexts. + * Revision 1.1 2001/02/16 22:51:29 mpowers Now deep-cloning objects passed + * between editing contexts. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java index 2b313d0..09687e2 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java @@ -30,912 +30,750 @@ import java.util.Map; import java.util.Set; /** -* This Introspector is a static utility class written to work -* around limitations in PropertyDescriptor and Introspector.

-* -* Of particular note are the get() and set() methods, which will attempt -* to get and set artibrary values on arbitrary objects to the best of its -* ability, converting values as appropriate. Properties of the form -* "property.nestedproperty.anotherproperty" are supported to get and set -* values on property values directly.

-* -* Note that for naming getter methods, this class supports "get", "is", -* and also the property name itself, which supports NeXT-style properties. -* Introspector supports Maps by treating the keys a property names, -* supports Lists by treating the indexes as property names.

-* -* Numeric and boolean types can be inverted by prepending a "!" before -* the name of the property, like "manager.!active" or "task.!lag". -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * This Introspector is a static utility class written to work around + * limitations in PropertyDescriptor and Introspector.
+ *
+ * + * Of particular note are the get() and set() methods, which will attempt to get + * and set artibrary values on arbitrary objects to the best of its ability, + * converting values as appropriate. Properties of the form + * "property.nestedproperty.anotherproperty" are supported to get and set values + * on property values directly.
+ *
+ * + * Note that for naming getter methods, this class supports "get", "is", and + * also the property name itself, which supports NeXT-style properties. + * Introspector supports Maps by treating the keys a property names, supports + * Lists by treating the indexes as property names.
+ *
+ * + * Numeric and boolean types can be inverted by prepending a "!" before the name + * of the property, like "manager.!active" or "task.!lag". + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ + +public class Introspector { + // allows "hasProperty" or "property" forms + public static boolean strict = false; -public class Introspector -{ - // allows "hasProperty" or "property" forms - public static boolean strict = false; - // print exception stack traces private static boolean debug = true; - + // path separator public static final String SEPARATOR = "."; - // method cache - use hashtables for thread safety - private static Map getterMethods = new Hashtable(); - private static Map setterMethods = new Hashtable(); - - // wildcard value - using this class to represent a "wildcard" generic class. - // we have to do this when matching methods by parameter types and a - // null value is passed in - can't tell what class the null should be. - public static Class WILD = Introspector.class; - - // empty class array - prevents having to create one every time - private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - - // use OGNL for property access - private static boolean useOGNL; - - static - { - try - { - useOGNL = ( Class.forName( "ognl.Ognl" ) != null ); - } - catch ( ClassNotFoundException t ) - { - useOGNL = false; - } - } + // method cache - use hashtables for thread safety + private static Map getterMethods = new Hashtable(); + private static Map setterMethods = new Hashtable(); -/** -* Utility method to get the read method for a property belonging to a class. -* Will search for methods in the form of "getProperty" and failing that -* "isProperty" (to handle booleans). -* @param objectClass the class whose property methods will be retrieved. -* @param aProperty The property whose method will be retrieved. -* @param paramTypes An array of class objects representing the types of parameters. -* @return The appropriate method for the class, or null if not found. -*/ - static public Method getPropertyReadMethod( - Class objectClass, String aProperty, Class[] paramTypes ) - { - Method result = null; - - result = getMethodFromClass( objectClass, aProperty, paramTypes, true ); + // wildcard value - using this class to represent a "wildcard" generic class. + // we have to do this when matching methods by parameter types and a + // null value is passed in - can't tell what class the null should be. + public static Class WILD = Introspector.class; - return result; - } + // empty class array - prevents having to create one every time + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; -/** -* Utility method to get the write method for a property belonging to a class. -* Will search for methods in the form of "setProperty". -* @param objectClass the class whose property methods will be retrieved. -* @param aProperty The property whose method will be retrieved. -* @param paramTypes An array of class objects representing the types of parameters. -* @return The appropriate method for the class, or null if not found. -*/ - static public Method getPropertyWriteMethod( - Class objectClass, String aProperty, Class[] paramTypes ) - { - Method result = null; - - result = getMethodFromClass( objectClass, aProperty, paramTypes, false ); + // use OGNL for property access + private static boolean useOGNL; - return result; - } + static { + try { + useOGNL = (Class.forName("ognl.Ognl") != null); + } catch (ClassNotFoundException t) { + useOGNL = false; + } + } -/** -* Gets a named method from a class. Using this method is preferred because -* the results are cached and should be faster than calling Class.getMethod(). -* Note that if an object has a "get" getter method and an "is" getter method -* with the same signature defined for a given property. The "get" method -* is called. -* @param objectClass the Class whose property methods will be retrieved. -* @param aMethodName A String containing the name of the desired method. -* @param paramTypes An array of class objects representing the types of parameters. -* @return The appropriate Method from the Class, or null if not found. -*/ - static private Method getMethodFromClass( - Class objectClass, String aProperty, Class[] paramTypes, boolean doGetter) - { // System.out.print( "Introspector.getMethodFromClass: " + aMethodName + " : " ); - - Map classesToMethods = (doGetter ? - getterMethods : - setterMethods); - - Map allMethods = (Map) classesToMethods.get( objectClass ); - if (allMethods == null) - { - // need to build maps for this class - mapPropertiesForClass( objectClass ); - // now the map should exist - allMethods = (Map) classesToMethods.get( objectClass ); - } - - Method[] methods = (Method[]) allMethods.get( aProperty ); - if ( methods == null ) - { - return null; // property doesn't exist - } - - methods_loop: // walks through all methods for name - for ( int i = 0; i < methods.length; i++ ) - { - Class[] types = methods[i].getParameterTypes(); - - // if parameter lengths don't match - if ( types.length != paramTypes.length ) - { - // System.out.println( aMethodName + " : " + types.length + " != " + paramTypes.length ); - continue methods_loop; // continue with outer loop - } - - // match up each parameter - for ( int j = 0; j < types.length; j++ ) - { - // convert primitives so they'll match - ugly - // (would have thought isAssignableFrom() would catch this) - if ( types[j].isPrimitive() ) - { - if ( types[j] == Boolean.TYPE ) - { - types[j] = Boolean.class; - } - else - if ( types[j] == Character.TYPE ) - { - types[j] = Character.class; - } - else - if ( types[j] == Byte.TYPE ) - { - types[j] = Byte.class; - } - else - if ( types[j] == Short.TYPE ) - { - types[j] = Short.class; - } - else - if ( types[j] == Integer.TYPE ) - { - types[j] = Integer.class; - } - else - if ( types[j] == Long.TYPE ) - { - types[j] = Long.class; - } - else - if ( types[j] == Float.TYPE ) - { - types[j] = Float.class; - } - else - if ( types[j] == Double.TYPE ) - { - types[j] = Double.class; - } - } - - // if parameters don't match - if ( ( paramTypes[j] != WILD ) && ( ! types[j].isAssignableFrom( paramTypes[j] ) ) ) - { + /** + * Utility method to get the read method for a property belonging to a class. + * Will search for methods in the form of "getProperty" and failing that + * "isProperty" (to handle booleans). + * + * @param objectClass the class whose property methods will be retrieved. + * @param aProperty The property whose method will be retrieved. + * @param paramTypes An array of class objects representing the types of + * parameters. + * @return The appropriate method for the class, or null if not found. + */ + static public Method getPropertyReadMethod(Class objectClass, String aProperty, Class[] paramTypes) { + Method result = null; + + result = getMethodFromClass(objectClass, aProperty, paramTypes, true); + + return result; + } + + /** + * Utility method to get the write method for a property belonging to a class. + * Will search for methods in the form of "setProperty". + * + * @param objectClass the class whose property methods will be retrieved. + * @param aProperty The property whose method will be retrieved. + * @param paramTypes An array of class objects representing the types of + * parameters. + * @return The appropriate method for the class, or null if not found. + */ + static public Method getPropertyWriteMethod(Class objectClass, String aProperty, Class[] paramTypes) { + Method result = null; + + result = getMethodFromClass(objectClass, aProperty, paramTypes, false); + + return result; + } + + /** + * Gets a named method from a class. Using this method is preferred because the + * results are cached and should be faster than calling Class.getMethod(). Note + * that if an object has a "get" getter method and an "is" getter method with + * the same signature defined for a given property. The "get" method is called. + * + * @param objectClass the Class whose property methods will be retrieved. + * @param aMethodName A String containing the name of the desired method. + * @param paramTypes An array of class objects representing the types of + * parameters. + * @return The appropriate Method from the Class, or null if not found. + */ + static private Method getMethodFromClass(Class objectClass, String aProperty, Class[] paramTypes, + boolean doGetter) { // System.out.print( "Introspector.getMethodFromClass: " + aMethodName + " : " + // ); + + Map classesToMethods = (doGetter ? getterMethods : setterMethods); + + Map allMethods = (Map) classesToMethods.get(objectClass); + if (allMethods == null) { + // need to build maps for this class + mapPropertiesForClass(objectClass); + // now the map should exist + allMethods = (Map) classesToMethods.get(objectClass); + } + + Method[] methods = (Method[]) allMethods.get(aProperty); + if (methods == null) { + return null; // property doesn't exist + } + + methods_loop: // walks through all methods for name + for (int i = 0; i < methods.length; i++) { + Class[] types = methods[i].getParameterTypes(); + + // if parameter lengths don't match + if (types.length != paramTypes.length) { + // System.out.println( aMethodName + " : " + types.length + " != " + + // paramTypes.length ); + continue methods_loop; // continue with outer loop + } + + // match up each parameter + for (int j = 0; j < types.length; j++) { + // convert primitives so they'll match - ugly + // (would have thought isAssignableFrom() would catch this) + if (types[j].isPrimitive()) { + if (types[j] == Boolean.TYPE) { + types[j] = Boolean.class; + } else if (types[j] == Character.TYPE) { + types[j] = Character.class; + } else if (types[j] == Byte.TYPE) { + types[j] = Byte.class; + } else if (types[j] == Short.TYPE) { + types[j] = Short.class; + } else if (types[j] == Integer.TYPE) { + types[j] = Integer.class; + } else if (types[j] == Long.TYPE) { + types[j] = Long.class; + } else if (types[j] == Float.TYPE) { + types[j] = Float.class; + } else if (types[j] == Double.TYPE) { + types[j] = Double.class; + } + } + + // if parameters don't match + if ((paramTypes[j] != WILD) && (!types[j].isAssignableFrom(paramTypes[j]))) { // System.out.println( "Introspector.getMethodFromClass: " + // aProperty + " : " + types[j] + " != " + paramTypes[j] ); - continue methods_loop; // continue with outer loop - } - } - - // all params match - return methods[i]; - } - - // no match - return null; - } - - static private final Method[] getAllMethodsForClass( Class aClass ) - { - Method[] local = aClass.getDeclaredMethods(); // only local - Method[] all = aClass.getMethods(); // all public - Method[] result = new Method[ local.length + all.length ]; - System.arraycopy( local, 0, result, 0, local.length ); - System.arraycopy( all, 0, result, local.length, all.length ); - return result; - } - - /** - * Generates a map of properties to both getter or setter methods for the given class. - * Then assigned those maps into the appropriate getterMethods and setterMethods maps - * keyed by the specified class. Even on error, this method will at least place empty - * property maps into each of the methods maps. - */ - static private void mapPropertiesForClass( Class objectClass ) - { - try - { - Map readProperties = new HashMap(); - getterMethods.put( objectClass, readProperties ); - Map writeProperties = new HashMap(); - setterMethods.put( objectClass, writeProperties ); - - String name, property; - Method[] methods = getAllMethodsForClass( objectClass ); // throws SecurityException - for ( int i = 0; i < methods.length; i++ ) - { - name = methods[i].getName(); - methods[i].setAccessible( true ); // throws SecurityException - if ( name.startsWith( "set" ) ) - { - name = name.substring( 3 ); - if ( ! "".equals( name ) ) // excludes "set()" - { - putMethodIntoPropertyMap( name, methods[i], writeProperties ); - } - } - else - if ( methods[i].getReturnType() != void.class ) - { - String fullname = name; - if ( name.startsWith( "get" ) ) - { - name = name.substring( 3 ); - } - else - if ( name.startsWith( "is" ) ) - { - name = name.substring( 2 ); - } - else - if ( name.startsWith( "has" ) && ( !strict ) ) // what about hashCode()? - { - name = name.substring( 3 ); - } - - if ( ! "".equals( name ) && ( !strict ) ) // excludes "get()", "has()", and "is()" - { - putMethodIntoPropertyMap( name, methods[i], readProperties ); - if ( fullname != name ) - { // allows us to match properties that include the get/set prefix as well - putMethodIntoPropertyMap( fullname, methods[i], readProperties ); - } - } - } - } - } - catch ( SecurityException se ) - { - System.out.println( "Introspector.getMethodFromClass: " + se ); - // this class will show up with empty getter/setter maps - } - } - - /** - * Places a property-method pair into one of the properties maps. - * This in effect maps a property to an array of methods. - */ - private static void putMethodIntoPropertyMap( String aProperty, Method aMethod, Map aMap ) - { - // ensure first character is lower case - StringBuffer buffer = new StringBuffer( aProperty ); - buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0))); - String key = buffer.toString(); - - // build array of methods for property - Method[] result = (Method[]) aMap.get( key ); - if ( result == null ) - { - result = new Method[] { aMethod }; - } - else - { - // create new array that's larger by one and copy - int i; - Method[] enlarged = new Method[ result.length + 1 ]; - for ( i = 0; i < result.length; i ++ ) - { - enlarged[i] = result[i]; - } - // add the new method to end - enlarged[i] = aMethod; - result = enlarged; - } - aMap.put( key, result ); - } + continue methods_loop; // continue with outer loop + } + } -/** -* Utility method to get a method for a property belonging to a class. -* Use this if you don't feel like making the Class array from the parameters -* you will be using - pass in the parameters themselves. -* @param objectClass the Class whose property methods will be retrieved. -* @param aProperty The property whose method will be retrieved. -* @param params An array of parameters to be used. -* @return The appropriate method for the class, or null if not found. -*/ - static public Method getPropertyReadMethod( - Class objectClass, String aProperty, Object[] params ) - { - // optimization: avoid allocating class array for common case - if ( params.length == 0 ) - { - return getPropertyReadMethod( - objectClass, aProperty, EMPTY_CLASS_ARRAY ); - } - - Class[] paramList = new Class[ params.length ]; - for ( int i = 0; i < params.length; i++ ) - { - if ( params[i] != null ) - { - paramList[i] = params[i].getClass(); - } - else - { - paramList[i] = WILD; - } - } - return getPropertyReadMethod( objectClass, aProperty, paramList ); - } + // all params match + return methods[i]; + } -/** -* Utility method to get a method for a property belonging to a class. -* Use this if you don't feel like making the Class array from the parameters -* you will be using - pass in the parameters themselves. -* @param objectClass the Class whose property methods will be retrieved. -* @param aProperty The property whose method will be retrieved. -* @param params An array of parameters to be used. -* @return The appropriate method for the class, or null if not found. -*/ - static public Method getPropertyWriteMethod( - Class objectClass, String aProperty, Object[] params ) - { - Class[] paramList = new Class[ params.length ]; - for ( int i = 0; i < params.length; i++ ) - { - if ( params[i] != null ) - { - paramList[i] = params[i].getClass(); - } - else - { - paramList[i] = WILD; - } - } - return getPropertyWriteMethod( objectClass, aProperty, paramList ); - } - - /** - * Gets a list of the readable properties for the given class. - * Note that readable properties may not be writable - see getWriteProperties(). - * @return An array of property names in no particular order - * where each name is a string with the first character in lower case. - */ - public static String[] getReadPropertiesForClass( Class objectClass ) - { - Map properties = (Map) getterMethods.get( objectClass ); - if ( properties == null ) - { - // need to build maps for this class - mapPropertiesForClass( objectClass ); - // now the map should exist - properties = (Map) getterMethods.get( objectClass ); - } - - // put property names into string array - Set keys = properties.keySet(); - Iterator it = keys.iterator(); - int len = keys.size(); - String[] result = new String[ len ]; - for ( int i = 0; i < len; i++ ) - { - result[i] = (String) it.next(); - } - return result; - } - - /** - * Gets a list of the writable properties for the given class. - * Note that writable properties may not be writable - see getReadProperties(). - * @return An array of property names in no particular order - * where each name is a string with the first character in lower case. - */ - public static String[] getWritePropertiesForClass( Class objectClass ) - { - Map properties = (Map) setterMethods.get( objectClass ); - if ( properties == null ) - { - // need to build maps for this class - mapPropertiesForClass( objectClass ); - // now the map should exist - properties = (Map) setterMethods.get( objectClass ); - } - - // put property names into string array - Set keys = properties.keySet(); - Iterator it = keys.iterator(); - int len = keys.size(); - String[] result = new String[ len ]; - for ( int i = 0; i < len; i++ ) - { - result[i] = (String) it.next(); - } - return result; - } - - /** - * Gets a list of the readable properties for the given object, which may - * not be null. This method is more useful than getReadPropertiesForClass - * in that Maps will return their keys as properties and Lists will return - * their element indices as properties. - * Note that readable properties may not be writable - see getWriteProperties(). - * @return An array of property names in no particular order - * where each name is a string with the first character in lower case. - */ - public static String[] getReadPropertiesForObject( Object anObject ) - { - List properties = new ArrayList(); - String[] classProperties = - getReadPropertiesForClass( anObject.getClass() ); - if ( anObject instanceof List ) - { - properties.addAll( getPropertiesForList( (List) anObject ) ); - } - if ( anObject instanceof Map ) - { - properties.addAll( getPropertiesForMap( (Map) anObject ) ); - } - int i; - int len = classProperties.length + properties.size(); - String[] result = new String[ len ]; - for ( i = 0; i < classProperties.length; i++ ) - { - result[i] = classProperties[i]; - } - Iterator it = properties.iterator(); - while ( it.hasNext() ) - { - result[i++] = it.next().toString(); - } - return result; - } - - /** - * Gets a list of the writable properties for the given object, which may - * not be null. This method is more useful than getWritePropertiesForClass - * in that Maps will return their keys as properties and Lists will return - * their element indices as properties. - * Note that writable properties may not be writable - see getReadProperties(). - * @return An array of property names in no particular order - * where each name is a string with the first character in lower case. - */ - public static String[] getWritePropertiesForObject( Object anObject ) - { - List properties = new ArrayList(); - String[] classProperties = - getWritePropertiesForClass( anObject.getClass() ); - if ( anObject instanceof List ) - { - properties.addAll( getPropertiesForList( (List) anObject ) ); - } - if ( anObject instanceof Map ) - { - properties.addAll( getPropertiesForMap( (Map) anObject ) ); - } - - int i; - int len = classProperties.length + properties.size(); - String[] result = new String[ len ]; - for ( i = 0; i < classProperties.length; i++ ) - { - result[i] = classProperties[i]; - } - Iterator it = properties.iterator(); - while ( it.hasNext() ) - { - result[i++] = it.next().toString(); - } - return result; - } - - private static List getPropertiesForList( List aList ) - { - List result = new ArrayList(); - int len = aList.size(); - for ( int i = 0; i < len; i++ ) - { - result.add( new Integer( i ).toString() ); - } - return result; - } - - private static List getPropertiesForMap( Map aMap ) - { - List result = new ArrayList(); - Iterator it = ((Map)aMap).keySet().iterator(); - while ( it.hasNext() ) - { - result.add( it.next().toString() ); - } - return result; - } - - private static Object[] EMPTY_ARRAY = new Object[0]; - - /** - * Convenience to get a value for a property from an object. - * An empty property string is considered the identity property - * and simply returns the object. - * @throws MissingPropertyException if the property cannot be - * found on the object. - */ - public static Object getValueForObject( Object anObject, String aProperty ) - { - if ( ( aProperty == null ) || ( "".equals( aProperty ) ) ) - { - return anObject; - } - - if ( useOGNL && aProperty.startsWith( "ognl:" ) ) - { - try - { - return ognl.Ognl.getValue( aProperty, anObject ); - } - catch ( Throwable t ) - { - if ( debug ) - { - System.err.println( - "Introspector.getValueForObject: " - + anObject + "' ( " + anObject.getClass() + " )" - + ", ognl:" + aProperty ); - System.err.println( t ); - } - return null; - } - } - - boolean invert = false; - if ( aProperty.startsWith( "!" ) ) - { - aProperty = aProperty.substring(1); - invert = true; - } - - Object result = null; - try - { - Method m = Introspector.getPropertyReadMethod( - anObject.getClass(), aProperty, EMPTY_ARRAY ); - if ( m != null ) - { - result = m.invoke( anObject, EMPTY_ARRAY ); + // no match + return null; + } + + static private final Method[] getAllMethodsForClass(Class aClass) { + Method[] local = aClass.getDeclaredMethods(); // only local + Method[] all = aClass.getMethods(); // all public + Method[] result = new Method[local.length + all.length]; + System.arraycopy(local, 0, result, 0, local.length); + System.arraycopy(all, 0, result, local.length, all.length); + return result; + } + + /** + * Generates a map of properties to both getter or setter methods for the given + * class. Then assigned those maps into the appropriate getterMethods and + * setterMethods maps keyed by the specified class. Even on error, this method + * will at least place empty property maps into each of the methods maps. + */ + static private void mapPropertiesForClass(Class objectClass) { + try { + Map readProperties = new HashMap(); + getterMethods.put(objectClass, readProperties); + Map writeProperties = new HashMap(); + setterMethods.put(objectClass, writeProperties); + + String name, property; + Method[] methods = getAllMethodsForClass(objectClass); // throws SecurityException + for (int i = 0; i < methods.length; i++) { + name = methods[i].getName(); + methods[i].setAccessible(true); // throws SecurityException + if (name.startsWith("set")) { + name = name.substring(3); + if (!"".equals(name)) // excludes "set()" + { + putMethodIntoPropertyMap(name, methods[i], writeProperties); + } + } else if (methods[i].getReturnType() != void.class) { + String fullname = name; + if (name.startsWith("get")) { + name = name.substring(3); + } else if (name.startsWith("is")) { + name = name.substring(2); + } else if (name.startsWith("has") && (!strict)) // what about hashCode()? + { + name = name.substring(3); + } + + if (!"".equals(name) && (!strict)) // excludes "get()", "has()", and "is()" + { + putMethodIntoPropertyMap(name, methods[i], readProperties); + if (fullname != name) { // allows us to match properties that include the get/set prefix as well + putMethodIntoPropertyMap(fullname, methods[i], readProperties); + } + } + } } - else // no method, try for field - { - try - { - Field field = anObject.getClass().getDeclaredField( aProperty ); - if ( field != null ) - { - field.setAccessible( true ); // throws SecurityException - result = field.get( anObject ); - } - } - catch ( Throwable t ) - { - // ignore for now - } - } - - if ( result == null ) - { - if ( anObject instanceof Map ) - { - result = ((Map)anObject).get( aProperty ); - } - else - if ( anObject instanceof List ) - { - result = ((List)anObject).get( Integer.parseInt( aProperty ) ); - } - } - - if ( invert ) - { - Object inverted = ValueConverter.invert( result ); - if ( inverted != null ) result = inverted; - } - //System.out.println( "getValueForObject: " + anObject + " : " + aProperty + " : " + result ); - return result; - } - catch ( Throwable exc ) - { - if ( exc instanceof InvocationTargetException ) - { - exc = ((InvocationTargetException)exc).getTargetException(); - } - if ( exc instanceof RuntimeException ) - { - throw (RuntimeException)exc; - } - if ( debug ) + } catch (SecurityException se) { + System.out.println("Introspector.getMethodFromClass: " + se); + // this class will show up with empty getter/setter maps + } + } + + /** + * Places a property-method pair into one of the properties maps. This in effect + * maps a property to an array of methods. + */ + private static void putMethodIntoPropertyMap(String aProperty, Method aMethod, Map aMap) { + // ensure first character is lower case + StringBuffer buffer = new StringBuffer(aProperty); + buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0))); + String key = buffer.toString(); + + // build array of methods for property + Method[] result = (Method[]) aMap.get(key); + if (result == null) { + result = new Method[] { aMethod }; + } else { + // create new array that's larger by one and copy + int i; + Method[] enlarged = new Method[result.length + 1]; + for (i = 0; i < result.length; i++) { + enlarged[i] = result[i]; + } + // add the new method to end + enlarged[i] = aMethod; + result = enlarged; + } + aMap.put(key, result); + } + + /** + * Utility method to get a method for a property belonging to a class. Use this + * if you don't feel like making the Class array from the parameters you will be + * using - pass in the parameters themselves. + * + * @param objectClass the Class whose property methods will be retrieved. + * @param aProperty The property whose method will be retrieved. + * @param params An array of parameters to be used. + * @return The appropriate method for the class, or null if not found. + */ + static public Method getPropertyReadMethod(Class objectClass, String aProperty, Object[] params) { + // optimization: avoid allocating class array for common case + if (params.length == 0) { + return getPropertyReadMethod(objectClass, aProperty, EMPTY_CLASS_ARRAY); + } + + Class[] paramList = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + if (params[i] != null) { + paramList[i] = params[i].getClass(); + } else { + paramList[i] = WILD; + } + } + return getPropertyReadMethod(objectClass, aProperty, paramList); + } + + /** + * Utility method to get a method for a property belonging to a class. Use this + * if you don't feel like making the Class array from the parameters you will be + * using - pass in the parameters themselves. + * + * @param objectClass the Class whose property methods will be retrieved. + * @param aProperty The property whose method will be retrieved. + * @param params An array of parameters to be used. + * @return The appropriate method for the class, or null if not found. + */ + static public Method getPropertyWriteMethod(Class objectClass, String aProperty, Object[] params) { + Class[] paramList = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + if (params[i] != null) { + paramList[i] = params[i].getClass(); + } else { + paramList[i] = WILD; + } + } + return getPropertyWriteMethod(objectClass, aProperty, paramList); + } + + /** + * Gets a list of the readable properties for the given class. Note that + * readable properties may not be writable - see getWriteProperties(). + * + * @return An array of property names in no particular order where each name is + * a string with the first character in lower case. + */ + public static String[] getReadPropertiesForClass(Class objectClass) { + Map properties = (Map) getterMethods.get(objectClass); + if (properties == null) { + // need to build maps for this class + mapPropertiesForClass(objectClass); + // now the map should exist + properties = (Map) getterMethods.get(objectClass); + } + + // put property names into string array + Set keys = properties.keySet(); + Iterator it = keys.iterator(); + int len = keys.size(); + String[] result = new String[len]; + for (int i = 0; i < len; i++) { + result[i] = (String) it.next(); + } + return result; + } + + /** + * Gets a list of the writable properties for the given class. Note that + * writable properties may not be writable - see getReadProperties(). + * + * @return An array of property names in no particular order where each name is + * a string with the first character in lower case. + */ + public static String[] getWritePropertiesForClass(Class objectClass) { + Map properties = (Map) setterMethods.get(objectClass); + if (properties == null) { + // need to build maps for this class + mapPropertiesForClass(objectClass); + // now the map should exist + properties = (Map) setterMethods.get(objectClass); + } + + // put property names into string array + Set keys = properties.keySet(); + Iterator it = keys.iterator(); + int len = keys.size(); + String[] result = new String[len]; + for (int i = 0; i < len; i++) { + result[i] = (String) it.next(); + } + return result; + } + + /** + * Gets a list of the readable properties for the given object, which may not be + * null. This method is more useful than getReadPropertiesForClass in that Maps + * will return their keys as properties and Lists will return their element + * indices as properties. Note that readable properties may not be writable - + * see getWriteProperties(). + * + * @return An array of property names in no particular order where each name is + * a string with the first character in lower case. + */ + public static String[] getReadPropertiesForObject(Object anObject) { + List properties = new ArrayList(); + String[] classProperties = getReadPropertiesForClass(anObject.getClass()); + if (anObject instanceof List) { + properties.addAll(getPropertiesForList((List) anObject)); + } + if (anObject instanceof Map) { + properties.addAll(getPropertiesForMap((Map) anObject)); + } + int i; + int len = classProperties.length + properties.size(); + String[] result = new String[len]; + for (i = 0; i < classProperties.length; i++) { + result[i] = classProperties[i]; + } + Iterator it = properties.iterator(); + while (it.hasNext()) { + result[i++] = it.next().toString(); + } + return result; + } + + /** + * Gets a list of the writable properties for the given object, which may not be + * null. This method is more useful than getWritePropertiesForClass in that Maps + * will return their keys as properties and Lists will return their element + * indices as properties. Note that writable properties may not be writable - + * see getReadProperties(). + * + * @return An array of property names in no particular order where each name is + * a string with the first character in lower case. + */ + public static String[] getWritePropertiesForObject(Object anObject) { + List properties = new ArrayList(); + String[] classProperties = getWritePropertiesForClass(anObject.getClass()); + if (anObject instanceof List) { + properties.addAll(getPropertiesForList((List) anObject)); + } + if (anObject instanceof Map) { + properties.addAll(getPropertiesForMap((Map) anObject)); + } + + int i; + int len = classProperties.length + properties.size(); + String[] result = new String[len]; + for (i = 0; i < classProperties.length; i++) { + result[i] = classProperties[i]; + } + Iterator it = properties.iterator(); + while (it.hasNext()) { + result[i++] = it.next().toString(); + } + return result; + } + + private static List getPropertiesForList(List aList) { + List result = new ArrayList(); + int len = aList.size(); + for (int i = 0; i < len; i++) { + result.add(new Integer(i).toString()); + } + return result; + } + + private static List getPropertiesForMap(Map aMap) { + List result = new ArrayList(); + Iterator it = ((Map) aMap).keySet().iterator(); + while (it.hasNext()) { + result.add(it.next().toString()); + } + return result; + } + + private static Object[] EMPTY_ARRAY = new Object[0]; + + /** + * Convenience to get a value for a property from an object. An empty property + * string is considered the identity property and simply returns the object. + * + * @throws MissingPropertyException if the property cannot be found on the + * object. + */ + public static Object getValueForObject(Object anObject, String aProperty) { + if ((aProperty == null) || ("".equals(aProperty))) { + return anObject; + } + + if (useOGNL && aProperty.startsWith("ognl:")) { + try { + return ognl.Ognl.getValue(aProperty, anObject); + } catch (Throwable t) { + if (debug) { + System.err.println("Introspector.getValueForObject: " + anObject + "' ( " + anObject.getClass() + + " )" + ", ognl:" + aProperty); + System.err.println(t); + } + return null; + } + } + + boolean invert = false; + if (aProperty.startsWith("!")) { + aProperty = aProperty.substring(1); + invert = true; + } + + Object result = null; + try { + Method m = Introspector.getPropertyReadMethod(anObject.getClass(), aProperty, EMPTY_ARRAY); + if (m != null) { + result = m.invoke(anObject, EMPTY_ARRAY); + } else // no method, try for field { - System.out.println( - "Introspector.getValueForObject: " - + anObject + "' ( " + anObject.getClass() + " )" - + ", " + aProperty + ": " ); - } - throw new WotonomyException( exc ); - } + try { + Field field = anObject.getClass().getDeclaredField(aProperty); + if (field != null) { + field.setAccessible(true); // throws SecurityException + result = field.get(anObject); + } + } catch (Throwable t) { + // ignore for now + } + } + + if (result == null) { + if (anObject instanceof Map) { + result = ((Map) anObject).get(aProperty); + } else if (anObject instanceof List) { + result = ((List) anObject).get(Integer.parseInt(aProperty)); + } + } + + if (invert) { + Object inverted = ValueConverter.invert(result); + if (inverted != null) + result = inverted; + } + // System.out.println( "getValueForObject: " + anObject + " : " + aProperty + " + // : " + result ); + return result; + } catch (Throwable exc) { + if (exc instanceof InvocationTargetException) { + exc = ((InvocationTargetException) exc).getTargetException(); + } + if (exc instanceof RuntimeException) { + throw (RuntimeException) exc; + } + if (debug) { + System.out.println("Introspector.getValueForObject: " + anObject + "' ( " + anObject.getClass() + " )" + + ", " + aProperty + ": "); + } + throw new WotonomyException(exc); + } //! throw new MissingPropertyException(); - } - - /** - * Convenience to set a value for a property from an object. - * Returns the return value from executing the specified method, - * or null if the method returns type void. - * @throws MissingPropertyException if the property cannot be - * found on the object. - * @throws NullPrimitiveException if the property is of primitive - * type and the value is null. - */ - public static Object setValueForObject( - Object anObject, String aProperty, Object aValue ) - { - if ( useOGNL && aProperty.startsWith( "ognl:" ) ) - { - try - { - ognl.Ognl.setValue( aProperty, anObject, aValue ); - } - catch ( Throwable t ) - { - if ( debug ) - { - System.err.println( - "Introspector.setValueForObject: " - + anObject + "' ( " + anObject.getClass() + " )" - + ", ognl:" + aProperty + " : " + aValue ); - System.err.println( t ); - } - } - return null; - } - - try - { - if ( aProperty.startsWith( "!" ) ) - { - aProperty = aProperty.substring(1); - Object inverted = ValueConverter.invert( aValue ); - if ( inverted != null ) aValue = inverted; - } - - Method m = null; - if ( aValue != null ) - { - m = Introspector.getPropertyWriteMethod( - anObject.getClass(), aProperty, new Class[] { aValue.getClass() } ); - } - if ( m == null ) - { - m = Introspector.getPropertyWriteMethod( - anObject.getClass(), aProperty, new Class[] { WILD } ); - if ( ( m != null ) && ( aValue != null ) ) - { - // check for null primitive - if ( ( aValue == null ) && - ( m.getParameterTypes()[0].isPrimitive() ) ) - { - throw new NullPrimitiveException(); - } - - // convert if possible - Object o = ValueConverter.convertObjectToClass( - aValue, m.getParameterTypes()[0] ); - if ( o != null ) - { - aValue = o; - } - } - } - if ( m != null ) - { - return m.invoke( anObject, new Object[] { aValue } ); + } + + /** + * Convenience to set a value for a property from an object. Returns the return + * value from executing the specified method, or null if the method returns type + * void. + * + * @throws MissingPropertyException if the property cannot be found on the + * object. + * @throws NullPrimitiveException if the property is of primitive type and the + * value is null. + */ + public static Object setValueForObject(Object anObject, String aProperty, Object aValue) { + if (useOGNL && aProperty.startsWith("ognl:")) { + try { + ognl.Ognl.setValue(aProperty, anObject, aValue); + } catch (Throwable t) { + if (debug) { + System.err.println("Introspector.setValueForObject: " + anObject + "' ( " + anObject.getClass() + + " )" + ", ognl:" + aProperty + " : " + aValue); + System.err.println(t); + } } - else // no method, try for field - { - try - { - Field field = anObject.getClass().getDeclaredField( aProperty ); - if ( field != null ) - { - field.setAccessible( true ); // throws SecurityException - field.set( anObject, aValue ); - return null; - } - } - catch ( Throwable t ) - { - // ignore for now - } - } - - if ( anObject instanceof Map ) - { - return ((Map)anObject).put( aProperty, aValue ); + return null; + } + + try { + if (aProperty.startsWith("!")) { + aProperty = aProperty.substring(1); + Object inverted = ValueConverter.invert(aValue); + if (inverted != null) + aValue = inverted; + } + + Method m = null; + if (aValue != null) { + m = Introspector.getPropertyWriteMethod(anObject.getClass(), aProperty, + new Class[] { aValue.getClass() }); } - if ( anObject instanceof List ) + if (m == null) { + m = Introspector.getPropertyWriteMethod(anObject.getClass(), aProperty, new Class[] { WILD }); + if ((m != null) && (aValue != null)) { + // check for null primitive + if ((aValue == null) && (m.getParameterTypes()[0].isPrimitive())) { + throw new NullPrimitiveException(); + } + + // convert if possible + Object o = ValueConverter.convertObjectToClass(aValue, m.getParameterTypes()[0]); + if (o != null) { + aValue = o; + } + } + } + if (m != null) { + return m.invoke(anObject, new Object[] { aValue }); + } else // no method, try for field { - List list = (List) anObject; - int i = Integer.parseInt( aProperty ); - if ( list.size() < i+1 ) - { - // expand list as necessary - for ( int j = list.size(); j <= i; j++ ) - { - list.add( new Object() ); // placeholder - } - } - return list.set( i, aValue ); + try { + Field field = anObject.getClass().getDeclaredField(aProperty); + if (field != null) { + field.setAccessible(true); // throws SecurityException + field.set(anObject, aValue); + return null; + } + } catch (Throwable t) { + // ignore for now + } + } + + if (anObject instanceof Map) { + return ((Map) anObject).put(aProperty, aValue); + } + if (anObject instanceof List) { + List list = (List) anObject; + int i = Integer.parseInt(aProperty); + if (list.size() < i + 1) { + // expand list as necessary + for (int j = list.size(); j <= i; j++) { + list.add(new Object()); // placeholder + } + } + return list.set(i, aValue); } - } - catch ( Throwable exc ) - { - if ( exc instanceof IllegalArgumentException ) - { + } catch (Throwable exc) { + if (exc instanceof IllegalArgumentException) { System.out.println( - "Introspector.setValueForObject: " - + anObject + " , " + aProperty + " , '" - + aValue + "' ):" ); - System.out.println( exc ); - } - else - if ( exc instanceof InvocationTargetException ) - { - exc = ((InvocationTargetException)exc).getTargetException(); - } - if ( exc instanceof RuntimeException ) - { - throw (RuntimeException)exc; - } - if ( debug ) - { + "Introspector.setValueForObject: " + anObject + " , " + aProperty + " , '" + aValue + "' ):"); + System.out.println(exc); + } else if (exc instanceof InvocationTargetException) { + exc = ((InvocationTargetException) exc).getTargetException(); + } + if (exc instanceof RuntimeException) { + throw (RuntimeException) exc; + } + if (debug) { System.out.println( - "Introspector.setValueForObject: " - + anObject + " , " + aProperty + " , '" - + aValue + "' ):" ); + "Introspector.setValueForObject: " + anObject + " , " + aProperty + " , '" + aValue + "' ):"); } - throw new WotonomyException( exc ); - } - return null; + throw new WotonomyException(exc); + } + return null; //! throw new MissingPropertyException(); - } - - /** - * Gets a value from an object or any of its child objects. - * This will parse the property string for "."'s and get - * values for each successive object's property in the path. - * An empty property string is considered the identity property - * and simply returns the object. - */ - public static Object get( Object anObject, String aProperty ) - { - int i = aProperty.indexOf( SEPARATOR ); - if ( i == -1 ) return getValueForObject( anObject, aProperty ); - - String pathElement = aProperty.substring( 0, i ); - String remainder = aProperty.substring( i+1 ); - - Object result = getValueForObject( anObject, pathElement ); - if ( result == null ) return null; - return get( result, remainder ); - } - - /** - * Sets a value in an object or any of its child objects. - * This will parse the property string for "."'s and set - * values for each successive object's property in the path.

- * - * If a property is not found, this method will try to - * implicitly create hash maps (if possible) to fill out the path. - * This is useful when dealing with trees of nested maps. - */ - public static Object set( Object anObject, String aProperty, Object aValue ) - { - int i = aProperty.indexOf( SEPARATOR ); - if ( i == -1 ) return setValueForObject( anObject, aProperty, aValue ); - - String pathElement = aProperty.substring( 0, i ); - String remainder = aProperty.substring( i+1 ); - - Object result = getValueForObject( anObject, pathElement ); - if ( result == null ) - { + } + + /** + * Gets a value from an object or any of its child objects. This will parse the + * property string for "."'s and get values for each successive object's + * property in the path. An empty property string is considered the identity + * property and simply returns the object. + */ + public static Object get(Object anObject, String aProperty) { + int i = aProperty.indexOf(SEPARATOR); + if (i == -1) + return getValueForObject(anObject, aProperty); + + String pathElement = aProperty.substring(0, i); + String remainder = aProperty.substring(i + 1); + + Object result = getValueForObject(anObject, pathElement); + if (result == null) + return null; + return get(result, remainder); + } + + /** + * Sets a value in an object or any of its child objects. This will parse the + * property string for "."'s and set values for each successive object's + * property in the path.
+ *
+ * + * If a property is not found, this method will try to implicitly create hash + * maps (if possible) to fill out the path. This is useful when dealing with + * trees of nested maps. + */ + public static Object set(Object anObject, String aProperty, Object aValue) { + int i = aProperty.indexOf(SEPARATOR); + if (i == -1) + return setValueForObject(anObject, aProperty, aValue); + + String pathElement = aProperty.substring(0, i); + String remainder = aProperty.substring(i + 1); + + Object result = getValueForObject(anObject, pathElement); + if (result == null) { result = new HashMap(2); - setValueForObject( anObject, pathElement, result ); + setValueForObject(anObject, pathElement, result); } - return set( result, remainder, aValue ); - } - + return set(result, remainder, aValue); + } + /** - * If set to true, exceptions printed to System.out.println. - * Defaults to true. - */ - public void setDebug( boolean isDebug ) - { + * If set to true, exceptions printed to System.out.println. Defaults to true. + */ + public void setDebug(boolean isDebug) { debug = isDebug; - } + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:11:47 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:11:47 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.19 2004/02/05 02:20:34 mpowers - * Added experimental ognl support (if ognl is present). + * Revision 1.19 2004/02/05 02:20:34 mpowers Added experimental ognl support (if + * ognl is present). * - * Revision 1.18 2003/03/26 16:44:35 mpowers - * Now correctly reflecting on all methods, not just locally declared ones. + * Revision 1.18 2003/03/26 16:44:35 mpowers Now correctly reflecting on all + * methods, not just locally declared ones. * - * Revision 1.17 2003/02/21 21:10:51 mpowers - * Now reaching package, protected, and private methods and fields. + * Revision 1.17 2003/02/21 21:10:51 mpowers Now reaching package, protected, + * and private methods and fields. * - * Revision 1.16 2003/01/28 22:11:59 mpowers - * Now more lenient in resolving properties starting with "is" "get" or "has". + * Revision 1.16 2003/01/28 22:11:59 mpowers Now more lenient in resolving + * properties starting with "is" "get" or "has". * - * Revision 1.15 2003/01/27 15:10:54 mpowers - * Better handling for illegal argument exceptions. + * Revision 1.15 2003/01/27 15:10:54 mpowers Better handling for illegal + * argument exceptions. * - * Revision 1.14 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.14 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.13 2002/10/11 15:35:12 mpowers - * Removed printlns. + * Revision 1.13 2002/10/11 15:35:12 mpowers Removed printlns. * - * Revision 1.11 2001/05/02 17:58:41 mpowers - * Removed debugging code, added comments. + * Revision 1.11 2001/05/02 17:58:41 mpowers Removed debugging code, added + * comments. * - * Revision 1.10 2001/04/08 21:00:54 mpowers - * Changes to support new objectsForFetchSpecification scheme. + * Revision 1.10 2001/04/08 21:00:54 mpowers Changes to support new + * objectsForFetchSpecification scheme. * - * Revision 1.9 2001/03/29 03:30:36 mpowers - * Refactored duplicator a bit. + * Revision 1.9 2001/03/29 03:30:36 mpowers Refactored duplicator a bit. * Disabled MissingPropertyExceptions for now. * - * Revision 1.8 2001/03/28 17:52:45 mpowers - * Corrected the throws in the docs. + * Revision 1.8 2001/03/28 17:52:45 mpowers Corrected the throws in the docs. * - * Revision 1.7 2001/03/28 17:49:13 mpowers - * Better exception handling in Introspector. + * Revision 1.7 2001/03/28 17:49:13 mpowers Better exception handling in + * Introspector. * - * Revision 1.6 2001/03/13 21:40:20 mpowers - * Improved handling of runtime exceptions. + * Revision 1.6 2001/03/13 21:40:20 mpowers Improved handling of runtime + * exceptions. * - * Revision 1.5 2001/03/09 22:06:35 mpowers - * Now extracting the wrapped exception from InvocationTargetExceptions. + * Revision 1.5 2001/03/09 22:06:35 mpowers Now extracting the wrapped exception + * from InvocationTargetExceptions. * - * Revision 1.4 2001/03/01 20:36:35 mpowers - * Better error handling and better handling of nulls. + * Revision 1.4 2001/03/01 20:36:35 mpowers Better error handling and better + * handling of nulls. * - * Revision 1.3 2001/01/17 16:20:57 mpowers - * Introspector now handles the identity property. + * Revision 1.3 2001/01/17 16:20:57 mpowers Introspector now handles the + * identity property. * - * Revision 1.2 2001/01/09 20:08:17 mpowers - * Slight optimization. + * Revision 1.2 2001/01/09 20:08:17 mpowers Slight optimization. * - * Revision 1.1.1.1 2000/12/21 15:52:04 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:04 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java index b1ad824..45080a2 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java @@ -19,14 +19,13 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation.internal; /** -* A WotonomyException that is thrown by Introspector. -* This class serves as a base class for other exceptions. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A WotonomyException that is thrown by Introspector. This class serves as a + * base class for other exceptions. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ -public class IntrospectorException extends WotonomyException -{ +public class IntrospectorException extends WotonomyException { } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java index c1e30d3..c89742f 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java @@ -19,14 +19,13 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation.internal; /** -* A IntrospectorException that is thrown by Introspector when -* a property does not exist for an object. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A IntrospectorException that is thrown by Introspector when a property does + * not exist for an object. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ -public class MissingPropertyException extends IntrospectorException -{ +public class MissingPropertyException extends IntrospectorException { } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java index 43c14a5..7761876 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java @@ -47,7 +47,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== - */ + */ package net.wotonomy.foundation.internal; @@ -59,310 +59,284 @@ import java.util.Enumeration; import java.util.Hashtable; /** - * The correct name for this class should be URLClassLoader. - * But there is already a class by that name in JDK1.2. + * The correct name for this class should be URLClassLoader. But there is + * already a class by that name in JDK1.2. * - * I have had quite a few problems with URLClassLoader in - * past, so I ended up writing this ClassLoader. I found that - * the Java 2's URLClassLoader, does not close the Jar file once - * opened. It is a pretty good optimization step, but if you - * modify the class in the jar file, it does not pick it up. Some - * operating systems may not let you modify the jar file while it is - * still open. IMHO, it does make sense to close the jar file - * after you are done reading the class data. But this approach may not - * get you the performance of the URLClassLoader, but it works in all - * cases and also runs on JDK1.1. I have enhanced this class loader - * to read all the zip/jar entries once & cache the data, so that - * there is no overhead of opening/closing jar file to pick up + * I have had quite a few problems with URLClassLoader in past, so I ended up + * writing this ClassLoader. I found that the Java 2's URLClassLoader, does not + * close the Jar file once opened. It is a pretty good optimization step, but if + * you modify the class in the jar file, it does not pick it up. Some operating + * systems may not let you modify the jar file while it is still open. IMHO, it + * does make sense to close the jar file after you are done reading the class + * data. But this approach may not get you the performance of the + * URLClassLoader, but it works in all cases and also runs on JDK1.1. I have + * enhanced this class loader to read all the zip/jar entries once & cache the + * data, so that there is no overhead of opening/closing jar file to pick up * each entry. * * * @author Harish Prabandham */ public class NetworkClassLoader extends ClassLoader { - private ClassLoader parent = null; // parent classloader - private Hashtable classCache = new Hashtable(); - private Hashtable urlset = new Hashtable(); - - /** - * Creates a new instance of the class loader. - * @param delegate/parent class loader. - */ - public NetworkClassLoader(ClassLoader parent) { - setParent(parent); - } - - /** - * Sets the parent/delegate class loader. - * @param delegate/parent class loader. - */ - protected final void setParent(ClassLoader parent) { - this.parent = parent; - } - - /** - * Adds the given URL to this class loader. If the URL - * ends with "/", then it is assumed to be a directory - * otherwise, it is assumed to be a zip/jar file. If the - * same URL is added again, the URL is re-opened and this - * zip/jar file is used for serving any future class requests. - * @param URL where to look for the classes. - */ - public synchronized void addURL(URL url) { - // System.out.println("Adding url: " + url); - if(!urlset.containsKey(url)) { - try { - urlset.put(url, new URLResourceReader(url)); - }catch(IOException ioe){ - // Probably a bad url... - } - } else { - // remove the old one & add a new one... - try{ - URLResourceReader newu = new URLResourceReader(url); - URLResourceReader oldu = (URLResourceReader) urlset.get(url); - oldu.close(); - urlset.remove(url); - urlset.put(url, newu); - } catch (IOException ioe) { - } - } - } - - /** - * @return An enumeration of URLs where this class loader - * looks for classes. - */ - public Enumeration getURLs() { - return urlset.keys(); - } - - /** - * Call this to bypass the implementation of loadClass. - */ - public Class findClass(String name) { - byte[] b = loadClassData(name); - if ( b == null ) return null; - return defineClass(name, b, 0, b.length); - } - - protected byte[] loadResource(URL url, String resourceName) - throws IOException { - URLResourceReader urr = (URLResourceReader) urlset.get(url); - // System.out.println("Loading from " + urr + " " + resourceName); - if(urr != null) { - return urr.getResource(resourceName); - } - - return null; - } - - protected byte[] loadResource(String resource) { - byte[] barray = null; - for(Enumeration e = urlset.keys(); e.hasMoreElements();) { - URL url = (URL) e.nextElement(); - - try { - barray = loadResource(url, resource); - } catch(Exception ex) { - } finally { - if(barray != null) - break; - } - } - - return barray; - } - - protected byte[] loadClassData(String classname) { - String resourceName = classname.replace('.', '/') + ".class"; - return loadResource(resourceName); - } - - /** - * Overridden to search for a resource and return - * a "jar"-style URL or normal "file" URL as necessary. - */ - protected URL findResource(String name) - { //System.out.println( "findResource: " + name ); - URL url; - byte[] barray = null; - - for ( Enumeration e = urlset.keys(); e.hasMoreElements(); ) - { - url = (URL) e.nextElement(); - try - { - barray = loadResource(url, name); // loads fully: wasteful - } - catch(Exception ex) - { - // do nothing - } - if( barray != null ) - { - try - { - String ref = url.toString(); - if ( ref.endsWith( ".jar" ) ) - { - //System.out.println( "jar:" + ref + "!/" + name ); - return new URL( "jar:" + ref + "!/" + name ); - } - else - { - //System.out.println( new URL( url, name ).toString() ); - return new URL( url, name ); - } - } - catch ( Throwable t ) - { - t.printStackTrace(); - } - } - } - - return null; - } - - /** - * @return The resource as the input stream if such a resource - * exists, otherwise returns null. - */ - public InputStream getResourceAsStream(String name) { - //System.out.println( "getResourceAsStream: " + name ); - InputStream istream = null; - - // Algorithm: - // - // 1. first check the system path for the resource - // 2. next check the delegate/parent class loader for the resource - // 3. then attempt to get the resource from the url set. - // - - // Lets check the system path for the resource. - istream = getSystemResourceAsStream(name); - if(istream != null) - return istream; - - // Lets check the parent/delegate class loader for the resource. - if(parent != null) { - istream = parent.getResourceAsStream(name); - if(istream != null) - return istream; - } - - // Lets load it ourselves. - byte[] data = loadResource(name); - if(data != null) { - istream = new ByteArrayInputStream(data); - } - - return istream; - } - - /** - * java.lang.ClassLoader's defineClass method is final, so the - * its subclasses cannot override this method. But, this class - * calls this method in the loadClass() instead. - * @param The name of the class without ".class" extension. - * @param The class data bytes. - * @return The class object. - */ - protected Class defineClass(String classname, byte[] classdata) { - return defineClass(classname, classdata, 0, classdata.length); - } - - public synchronized Class loadClass(String name, boolean resolve) - throws ClassNotFoundException { - Class c = null; - - // Algorithm: (Please do not change the order; unless you - // have a good reason to do so). - // - // 1. first check the system class loader. - // 2. next check the delegate/parent class loader. - // 3. next check the class cache - // 4. then attempt to load classes from the URL set. - // - - // Lets see if the class is in system class loader. - try { - c = findSystemClass(name); - }catch(ClassNotFoundException cnfe) { - }finally { - if(c != null) - return c; - } - - // Lets see if the class is in parent class loader. - try { - if(parent != null) - c = parent.loadClass(name); - }catch(ClassNotFoundException cnfe) { - }finally { - if(c != null) - return c; - } - - // Lets see if the class is in the cache.. - c = (Class) classCache.get(name); - - if(c != null) - return c; - - - // Lets see if we find the class all by ourselves. - byte[] data = loadClassData(name); - - if(data != null) { - // we did !! - c = defineClass(name, data); - classCache.put(name, c); - if(resolve) - resolveClass(c); - } else { - // We are out of luck at this point... - throw new ClassNotFoundException(name); - } - - return c; - } - - /** - * This method resets this ClassLoader's state. It completely - * removes all the URLs and classes in this class loader cache. - */ - public final void clear() { - urlset.clear(); - classCache.clear(); - } - - /** - * This method resets this ClassLoader's state and resets the - * references for garbage collection. - */ - protected void finalize() throws Throwable { - // Cleanup real well. Otherwise, this can be - // a major source of memory leaks... - - // remove all the urls & class entries. - clear(); - - parent = null; - urlset = null; - classCache = null; - } + private ClassLoader parent = null; // parent classloader + private Hashtable classCache = new Hashtable(); + private Hashtable urlset = new Hashtable(); + + /** + * Creates a new instance of the class loader. + * + * @param delegate/parent class loader. + */ + public NetworkClassLoader(ClassLoader parent) { + setParent(parent); + } + + /** + * Sets the parent/delegate class loader. + * + * @param delegate/parent class loader. + */ + protected final void setParent(ClassLoader parent) { + this.parent = parent; + } + + /** + * Adds the given URL to this class loader. If the URL ends with "/", then it is + * assumed to be a directory otherwise, it is assumed to be a zip/jar file. If + * the same URL is added again, the URL is re-opened and this zip/jar file is + * used for serving any future class requests. + * + * @param URL where to look for the classes. + */ + public synchronized void addURL(URL url) { + // System.out.println("Adding url: " + url); + if (!urlset.containsKey(url)) { + try { + urlset.put(url, new URLResourceReader(url)); + } catch (IOException ioe) { + // Probably a bad url... + } + } else { + // remove the old one & add a new one... + try { + URLResourceReader newu = new URLResourceReader(url); + URLResourceReader oldu = (URLResourceReader) urlset.get(url); + oldu.close(); + urlset.remove(url); + urlset.put(url, newu); + } catch (IOException ioe) { + } + } + } + + /** + * @return An enumeration of URLs where this class loader looks for classes. + */ + public Enumeration getURLs() { + return urlset.keys(); + } + + /** + * Call this to bypass the implementation of loadClass. + */ + public Class findClass(String name) { + byte[] b = loadClassData(name); + if (b == null) + return null; + return defineClass(name, b, 0, b.length); + } + + protected byte[] loadResource(URL url, String resourceName) throws IOException { + URLResourceReader urr = (URLResourceReader) urlset.get(url); + // System.out.println("Loading from " + urr + " " + resourceName); + if (urr != null) { + return urr.getResource(resourceName); + } + + return null; + } + + protected byte[] loadResource(String resource) { + byte[] barray = null; + for (Enumeration e = urlset.keys(); e.hasMoreElements();) { + URL url = (URL) e.nextElement(); + + try { + barray = loadResource(url, resource); + } catch (Exception ex) { + } finally { + if (barray != null) + break; + } + } + + return barray; + } + + protected byte[] loadClassData(String classname) { + String resourceName = classname.replace('.', '/') + ".class"; + return loadResource(resourceName); + } + + /** + * Overridden to search for a resource and return a "jar"-style URL or normal + * "file" URL as necessary. + */ + protected URL findResource(String name) { // System.out.println( "findResource: " + name ); + URL url; + byte[] barray = null; + + for (Enumeration e = urlset.keys(); e.hasMoreElements();) { + url = (URL) e.nextElement(); + try { + barray = loadResource(url, name); // loads fully: wasteful + } catch (Exception ex) { + // do nothing + } + if (barray != null) { + try { + String ref = url.toString(); + if (ref.endsWith(".jar")) { + // System.out.println( "jar:" + ref + "!/" + name ); + return new URL("jar:" + ref + "!/" + name); + } else { + // System.out.println( new URL( url, name ).toString() ); + return new URL(url, name); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + + return null; + } + + /** + * @return The resource as the input stream if such a resource exists, otherwise + * returns null. + */ + public InputStream getResourceAsStream(String name) { + // System.out.println( "getResourceAsStream: " + name ); + InputStream istream = null; + + // Algorithm: + // + // 1. first check the system path for the resource + // 2. next check the delegate/parent class loader for the resource + // 3. then attempt to get the resource from the url set. + // + + // Lets check the system path for the resource. + istream = getSystemResourceAsStream(name); + if (istream != null) + return istream; + + // Lets check the parent/delegate class loader for the resource. + if (parent != null) { + istream = parent.getResourceAsStream(name); + if (istream != null) + return istream; + } + + // Lets load it ourselves. + byte[] data = loadResource(name); + if (data != null) { + istream = new ByteArrayInputStream(data); + } + + return istream; + } + + /** + * java.lang.ClassLoader's defineClass method is final, so the its subclasses + * cannot override this method. But, this class calls this method in the + * loadClass() instead. + * + * @param The name of the class without ".class" extension. + * @param The class data bytes. + * @return The class object. + */ + protected Class defineClass(String classname, byte[] classdata) { + return defineClass(classname, classdata, 0, classdata.length); + } + + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class c = null; + + // Algorithm: (Please do not change the order; unless you + // have a good reason to do so). + // + // 1. first check the system class loader. + // 2. next check the delegate/parent class loader. + // 3. next check the class cache + // 4. then attempt to load classes from the URL set. + // + + // Lets see if the class is in system class loader. + try { + c = findSystemClass(name); + } catch (ClassNotFoundException cnfe) { + } finally { + if (c != null) + return c; + } + + // Lets see if the class is in parent class loader. + try { + if (parent != null) + c = parent.loadClass(name); + } catch (ClassNotFoundException cnfe) { + } finally { + if (c != null) + return c; + } + + // Lets see if the class is in the cache.. + c = (Class) classCache.get(name); + + if (c != null) + return c; + + // Lets see if we find the class all by ourselves. + byte[] data = loadClassData(name); + + if (data != null) { + // we did !! + c = defineClass(name, data); + classCache.put(name, c); + if (resolve) + resolveClass(c); + } else { + // We are out of luck at this point... + throw new ClassNotFoundException(name); + } + + return c; + } + + /** + * This method resets this ClassLoader's state. It completely removes all the + * URLs and classes in this class loader cache. + */ + public final void clear() { + urlset.clear(); + classCache.clear(); + } + + /** + * This method resets this ClassLoader's state and resets the references for + * garbage collection. + */ + protected void finalize() throws Throwable { + // Cleanup real well. Otherwise, this can be + // a major source of memory leaks... + + // remove all the urls & class entries. + clear(); + + parent = null; + urlset = null; + classCache = null; + } } - - - - - - - - - - - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java index e367211..bf1dffd 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java @@ -19,14 +19,13 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.foundation.internal; /** -* A IntrospectorException that is thrown by Introspector when -* trying to set a primitive type to null. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * A IntrospectorException that is thrown by Introspector when trying to set a + * primitive type to null. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ -public class NullPrimitiveException extends IntrospectorException -{ +public class NullPrimitiveException extends IntrospectorException { } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java index abdc82f..c804893 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java @@ -21,80 +21,61 @@ package net.wotonomy.foundation.internal; import java.io.Serializable; import java.util.Comparator; - /** -* A Comparator that will sort elements based on the -* property specified in the constructor. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class PropertyComparator implements Comparator, Serializable -{ - private String property; + * A Comparator that will sort elements based on the property specified in the + * constructor. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class PropertyComparator implements Comparator, Serializable { + private String property; -/** -* Standard constructor to configure the comparator. -* @param aProperty A property whose value is used to sort elements. -*/ - public PropertyComparator( String aProperty ) - { + /** + * Standard constructor to configure the comparator. + * + * @param aProperty A property whose value is used to sort elements. + */ + public PropertyComparator(String aProperty) { property = aProperty; - } + } - // interface Comparator + // interface Comparator - public int compare(Object o1, Object o2) - { - Object v1 = Introspector.get( o1, property ); - Object v2 = Introspector.get( o2, property ); - if ( v1 instanceof Comparable ) - { - return ((Comparable)v1).compareTo( v2 ); - } - else - if ( v2 instanceof Comparable ) - { - return ((Comparable)v2).compareTo( v1 ); - } - else - { - if ( v1 == null ) - { - if ( v2 == null ) - { + public int compare(Object o1, Object o2) { + Object v1 = Introspector.get(o1, property); + Object v2 = Introspector.get(o2, property); + if (v1 instanceof Comparable) { + return ((Comparable) v1).compareTo(v2); + } else if (v2 instanceof Comparable) { + return ((Comparable) v2).compareTo(v1); + } else { + if (v1 == null) { + if (v2 == null) { return 0; // both nulls are equal } return -1; // null is less than any object - } - else - if ( v2 == null ) - { + } else if (v2 == null) { return 1; // any object is greater than null } } // last resort: compare string conversions - return v1.toString().compareTo( v2.toString() ); + return v1.toString().compareTo(v2.toString()); } - - public boolean equals( Object obj ) - { - return ( obj instanceof PropertyComparator ); + + public boolean equals(Object obj) { + return (obj instanceof PropertyComparator); } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:11:47 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:11:47 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1.1.1 2000/12/21 15:52:07 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:07 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java index 03231c7..3698325 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java @@ -23,23 +23,26 @@ import java.io.*; /** * PropertyListParser can parse a property list (plist) file or string, and - * return the top-level object represented by the plist.

+ * return the top-level object represented by the plist. + *

* - * A property list is a heirarchical data structure containing only Maps, - * Lists, and Strings -- nothing else. In other words, a property list is - * either a Map, List, or String instance, with the restrictions that the - * collections may only contain Map, List, or String instances.

+ * A property list is a heirarchical data structure containing only Maps, Lists, + * and Strings -- nothing else. In other words, a property list is either a Map, + * List, or String instance, with the restrictions that the collections may only + * contain Map, List, or String instances. + *

* - * This class can read a particularly-formatted string or file, and create - * the property list structure described. It provides a convenient means - * for having a structured data file, letting programs simply deal with the - * structure rather than having to do a lot of string parsing work as well. - * The concept is similar to Properties files, except that the values can - * be nested Maps or Lists instead of only Strings.

+ * This class can read a particularly-formatted string or file, and create the + * property list structure described. It provides a convenient means for having + * a structured data file, letting programs simply deal with the structure + * rather than having to do a lot of string parsing work as well. The concept is + * similar to Properties files, except that the values can be nested Maps or + * Lists instead of only Strings. + *

* * A Map is specified in a file by key/value pairs surrounded by brace - * characters. An equal sign (=) must be between the key and value, and - * there must be a semicolon (;) following the value. + * characters. An equal sign (=) must be between the key and value, and there + * must be a semicolon (;) following the value. * *

  *     {
@@ -49,35 +52,42 @@ import java.io.*;
  *     }
  * 
* - * A List is specified by a comma-separated list of values surrounded by parentheses, like: + * A List is specified by a comma-separated list of values surrounded by + * parentheses, like: + * *
  *     ( value1, value2, value3, etc... )
  * 
* - * A String can either be quoted in the manner of a constant string in - * Java, or unquoted. If unquoted, the string can only contain - * alphanumerics, underscores (_), periods (.), dollar signs ($), colons - * (:), or forward slashes (/). If any other character appears in the - * string, it must be quoted (i.e., surrounded by " characters). - * Quoted strings may also contain \n, \t, \f, \v, \b, and \a escapes, - * octal escapes of the form \000, and unicode escapes of the form of \U - * followed by four hexadecimal characters. Any other character escaped - * by a backslash will be treated as that character, and the escaping - * backslash character will be omitted. Thus, to represent an actual - * backslash, it must appear as \\ in the quoted string.

+ * A String can either be quoted in the manner of a constant string in Java, or + * unquoted. If unquoted, the string can only contain alphanumerics, underscores + * (_), periods (.), dollar signs ($), colons (:), or forward slashes (/). If + * any other character appears in the string, it must be quoted (i.e., + * surrounded by " characters). Quoted strings may also contain \n, \t, \f, + * \v, \b, and \a escapes, octal escapes of the form \000, and unicode escapes + * of the form of \U followed by four hexadecimal characters. Any other + * character escaped by a backslash will be treated as that character, and the + * escaping backslash character will be omitted. Thus, to represent an actual + * backslash, it must appear as \\ in the quoted string. + *

+ * + * All whitespace between elements is ignored, and both //-style and /*-style + * comments are allowed to appear anywhere between elements. + *

* - * All whitespace between elements is ignored, and both //-style and - * /*-style comments are allowed to appear anywhere between elements.

+ * If there are any syntax errors encountered while parsing, RuntimeExceptions + * are thrown with the line number and column of the problem. + *

* - * If there are any syntax errors encountered while parsing, - * RuntimeExceptions are thrown with the line number and column of the - * problem.

+ * Currenty, HashMaps and ArrayLists are the actual Map and List classes used + * when creating the property list. + *

* - * Currenty, HashMaps and ArrayLists are the actual Map and List classes - * used when creating the property list.

+ * Examples: + *

+ *

* - * Examples:

-
+ * 
    // This plist file represents a Map, since it starts with a '{'.
    {
        Map1 = { subkey1 = "foo"; };
@@ -104,443 +114,425 @@ import java.io.*;
            }
        );
    }
- 
-
- * For those wondering, this is essentially a re-implementation of + * + * + *
For those wondering, this is essentially a re-implementation of * NeXT/Apple's property lists, except that data values are not supported. * * @author clindberg@blacksmith.com * @version $Revision: 899 $ */ -public class PropertyListParser -{ - private char buffer[]; - private int currIndex; - private int lineNumber; - private int currLineStartIndex; - - /** Reads an object (String, List, or Map) from plistString and returns it. - * RuntimeExceptions are raised if there are parse problems. - */ - public static Object propertyListFromString(String plistString) - { - PropertyListParser parser = new PropertyListParser(plistString); - return parser.readTopLevelObject(); - } - - /** - * Reads all remaining characters from the Reader, and returns the - * result of propertyListFromString(). RuntimeExceptions are raised if - * there are parse problems - */ - public static Object propertyListFromReader(Reader reader) throws IOException - { - char charBuffer[] = new char[2048]; - StringBuffer stringBuffer = new StringBuffer(); - int numRead = 0; - - while (numRead >= 0) - { - numRead = reader.read(charBuffer); - if (numRead > 0) stringBuffer.append(charBuffer, 0, numRead); - } - - return propertyListFromString(stringBuffer.toString()); - } - - /** - * Reads the contents of the specified file, and parses the contents. - * If any error occurs, prints out a message using System.out.println() - * and returns null. - */ - public static Object propertyListFromFile(String filename) - { - try { - FileInputStream stream = new FileInputStream(filename); - return propertyListFromReader(new InputStreamReader(stream)); - } catch (Exception exception) { - String errorMessage = exception.getMessage(); - System.out.println("Error parsing property list from "+filename+": "+errorMessage); - } - - return null; - } - - /** - * Creates a new PropertyListParser to parse the contents of the - * specified String. - */ - public PropertyListParser(String plistString) - { - this(plistString.toCharArray()); - } - - /** - * Creates a new PropertyListParser to parse the specified char array. - */ - public PropertyListParser(char[] charArray) - { - buffer = charArray; - lineNumber = 1; - currLineStartIndex = 1; - currIndex = 0; - } - - public Object readTopLevelObject() - { - Object plist = readObject(); - - skipCommentWhitespace(); - if (!isAtEnd()) - { - throwParseException("Extra characters in plist string after parsing object. A plist should only contain one top-level object."); - } - - return plist; - } - - private void throwParseException(String errorMessage) - { - int column = currIndex - currLineStartIndex + 1; - throw new RuntimeException(errorMessage + " (Line " + lineNumber + ", column " + column + ")"); - } - - private void updateLineNumberWithIndex(int lineStartIndex) - { - lineNumber++; - currLineStartIndex = lineStartIndex; - } - - private boolean isAtEnd() - { - return currIndex >= buffer.length; - } - - private void skipDoubleslashComment() - { - while (!isAtEnd() && buffer[currIndex] != '\n') { - currIndex++; - } - } - - private void skipStandardCComment() - { - currIndex++; //skip over the starting '/' - - while (!isAtEnd()) - { - if (buffer[currIndex] == '\n') - updateLineNumberWithIndex(currIndex+1); - - currIndex++; - - if (buffer[currIndex-2] == '*' && buffer[currIndex-1] == '/') - { - return; - } - } - - throwParseException("Input exhausted while parsing comment"); - } - - private void skipWhitespace() - { - while (!isAtEnd() && isWhitespace(buffer[currIndex])) - { - if (buffer[currIndex] == '\n') - updateLineNumberWithIndex(currIndex+1); - currIndex++; - } - } - - private void skipCommentWhitespace() - { - boolean done = false; - - while (!done) - { - done = true; - - skipWhitespace(); - if ((buffer.length - currIndex) > 1 && buffer[currIndex] == '/') - { - if (buffer[currIndex+1] == '/') { - done = false; //iterate again - skipDoubleslashComment(); - } - else if (buffer[currIndex+1] == '*') { - done = false; //iterate again - skipStandardCComment(); - } - } - } - } - - private Object readObject() - { - skipCommentWhitespace(); - if (isAtEnd()) return null; - - // Data (i.e. byte[]) not supported - if (buffer[currIndex] == '"') - return readQuotedString(); - if (buffer[currIndex] == '(') - return readList(); - if (buffer[currIndex] == '{') - return readMap(); - - return readUnquotedString(); - } - - private static final byte valueForHexDigit(char c) - { - if(c >= '0' && c <= '9') return (byte)(c - '0'); - if(c >= 'a' && c <= 'f') return (byte)((c - 'a') + 10); - if(c >= 'A' && c <= 'F') return (byte)((c - 'A') + 10); - - return 0; - } - - private static final boolean isOctalDigit(char c) - { - return c >= '0' && c <= '7'; - } - - private static final boolean isHexDigit(char c) - { - return (c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); - } - - private static String unquotedStringChars = "._$:/"; // chars allowed in unquoted strings - private static String whitespaceChars = " \t\n\r\f"; - - private static final boolean isWhitespace(char c) - { - return whitespaceChars.indexOf(c) >= 0; - } - - private static final boolean isValidUnquotedStringChar(char c) - { - return ((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - unquotedStringChars.indexOf(c) >= 0); - } - - private String readUnquotedString() - { - int startIndex = currIndex; - - while (!isAtEnd() && isValidUnquotedStringChar(buffer[currIndex])) - currIndex++; - - if (startIndex == currIndex) - throwParseException("No allowable characters found to parse unquoted string"); - - return new String(buffer, startIndex, currIndex - startIndex); - } - - private String readQuotedString() - { - currIndex++; //skip over '"' - - StringBuffer stringBuffer = new StringBuffer(); - int startIndex = currIndex; - - while (!isAtEnd() && buffer[currIndex] != '"') - { - if (buffer[currIndex] != '\\') - { - if (buffer[currIndex] == '\n') - updateLineNumberWithIndex(currIndex+1); - - /* - * Just increment the index -- all these characters will be - * appended in chunks, either before an escape sequence or - * at the end. - */ - currIndex++; - } - else // it's an escape - { - /* Append anything scanned past before the '\\' */ - if (startIndex < currIndex) - stringBuffer.append(buffer, startIndex, currIndex - startIndex); - currIndex++; // skip over '\\' - - if (isAtEnd()) - throwParseException("Input exhausted while parsing escape sequence"); - - switch (buffer[currIndex]) - { - case 't': stringBuffer.append('\t'); currIndex++; break; // tab - case 'n': stringBuffer.append('\n'); currIndex++; break; // newline - case 'r': stringBuffer.append('\r'); currIndex++; break; // carriage return - case 'f': stringBuffer.append('\f'); currIndex++; break; // form feed - case 'b': stringBuffer.append('\b'); currIndex++; break; // backspace - case 'a': stringBuffer.append('\007'); currIndex++; break; // bell - case 'v': stringBuffer.append('\013'); currIndex++; break; // vertical tab - case 'U': - case 'u': - { - /* A Unicode escape. Always followed by 4 hex digits. */ - currIndex++; // skip past the 'U' - if ((currIndex+4) > buffer.length) - throwParseException("Not enough chars to parse \\U sequence"); - - if(!isHexDigit(buffer[currIndex]) || !isHexDigit(buffer[currIndex+1]) || - !isHexDigit(buffer[currIndex+2]) || !isHexDigit(buffer[currIndex+3])) - { - throwParseException("Four hex digits not found for \\U sequence"); - } - - byte byte3 = valueForHexDigit(buffer[currIndex]); - byte byte2 = valueForHexDigit(buffer[currIndex+1]); - byte byte1 = valueForHexDigit(buffer[currIndex+2]); - byte byte0 = valueForHexDigit(buffer[currIndex+3]); - char theChar = (char)((byte3 << 12) + (byte2 << 8) + (byte1 << 4) + byte0); - stringBuffer.append(theChar); - currIndex += 4; - break; - } - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - { - /* An octal escape. Expect 1, 2, or 3 octal digits. */ - int digits = 0; - int value = 0; - - do { - value *= 8; - value += (int)(buffer[currIndex] - '0'); - currIndex++; - digits++; - } while (digits <= 3 && !isAtEnd() && isOctalDigit(buffer[currIndex])); - - if (value > 255) - throwParseException("Value too large in octal escape sequence (> 0377)"); - - // This assumes value is in ISO Latin 1 encoding - stringBuffer.append((char)value); - break; - } - /* I guess plists can't have the \x{HEX}{HEX} escapes */ - default: - { - // Unknown escape sequence, just add the character. - // GCC warns if this isn't a '"', '\'', or '\\'... - stringBuffer.append(buffer[currIndex]); - if (buffer[currIndex] == '\n') - updateLineNumberWithIndex(currIndex+1); - currIndex++; - break; - } - } // end case - - /* Reset startIndex, so a verbatim copy will now start from this index */ - startIndex = currIndex; - - } //end '\\' escape - } - - if (isAtEnd()) - throwParseException("Input exhausted while parsing quoted string"); - if (startIndex < currIndex) - stringBuffer.append(buffer, startIndex, currIndex - startIndex); - currIndex++; //skip past '"' - - return stringBuffer.toString(); - } - - private List readList() - { - List newList = new ArrayList(); - - currIndex++; //skip over '(' - skipCommentWhitespace(); - while (!isAtEnd() && buffer[currIndex] != ')') - { - /* A comma is required between list elements */ - if (newList.size() > 0) - { - if (buffer[currIndex] != ',') - throwParseException("List parsing failed: expecting ','"); - currIndex++; - skipCommentWhitespace(); - if (isAtEnd()) - throwParseException("Input exhausted while parsing list"); - } - - if (buffer[currIndex] != ')') - { - Object plistObject = readObject(); - if (plistObject == null) - throwParseException("List parsing failed: could not read contained object."); - newList.add(plistObject); - skipCommentWhitespace(); - } - } - - if (isAtEnd()) - throwParseException("Input exhausted while parsing list"); - currIndex++; //skip past ')' - - return newList; - } - - private Map readMap() - { - HashMap newMap = new HashMap(); - - currIndex++; // skip over open brace - skipCommentWhitespace(); - - while (!isAtEnd() && buffer[currIndex] != '}') - { - Object key; - Object value; - - key = readObject(); - if (key == null || !(key instanceof String)) - throwParseException("Map parsing failed: could not parse key or key is not a String"); - - skipCommentWhitespace(); - if (isAtEnd() || buffer[currIndex] != '=') - throwParseException("Map parsing failed: expecting '='"); - currIndex++; //skip over '=' - skipCommentWhitespace(); - if (isAtEnd()) - throwParseException("Input exhausted while parsing map"); - - value = readObject(); - if (value == null) - throwParseException("Map parsing failed: could not parse value object"); - - skipCommentWhitespace(); - if (isAtEnd() || buffer[currIndex] != ';') - throwParseException("Map parsing failed: expecting ';'"); - currIndex++; //skip over ';' - skipCommentWhitespace(); - - newMap.put(key, value); - } - - if (isAtEnd()) - throwParseException("Input exhausted while parsing map"); - currIndex++; //skip past '}' - - return newMap; - } - - - public static void main(String[] args) - { - String filename = args[0]; - Object plist = PropertyListParser.propertyListFromFile(filename); - System.out.println(plist); - } +public class PropertyListParser { + private char buffer[]; + private int currIndex; + private int lineNumber; + private int currLineStartIndex; + + /** + * Reads an object (String, List, or Map) from plistString and returns it. + * RuntimeExceptions are raised if there are parse problems. + */ + public static Object propertyListFromString(String plistString) { + PropertyListParser parser = new PropertyListParser(plistString); + return parser.readTopLevelObject(); + } + + /** + * Reads all remaining characters from the Reader, and returns the result of + * propertyListFromString(). RuntimeExceptions are raised if there are parse + * problems + */ + public static Object propertyListFromReader(Reader reader) throws IOException { + char charBuffer[] = new char[2048]; + StringBuffer stringBuffer = new StringBuffer(); + int numRead = 0; + + while (numRead >= 0) { + numRead = reader.read(charBuffer); + if (numRead > 0) + stringBuffer.append(charBuffer, 0, numRead); + } + + return propertyListFromString(stringBuffer.toString()); + } + + /** + * Reads the contents of the specified file, and parses the contents. If any + * error occurs, prints out a message using System.out.println() and returns + * null. + */ + public static Object propertyListFromFile(String filename) { + try { + FileInputStream stream = new FileInputStream(filename); + return propertyListFromReader(new InputStreamReader(stream)); + } catch (Exception exception) { + String errorMessage = exception.getMessage(); + System.out.println("Error parsing property list from " + filename + ": " + errorMessage); + } + + return null; + } + + /** + * Creates a new PropertyListParser to parse the contents of the specified + * String. + */ + public PropertyListParser(String plistString) { + this(plistString.toCharArray()); + } + + /** + * Creates a new PropertyListParser to parse the specified char array. + */ + public PropertyListParser(char[] charArray) { + buffer = charArray; + lineNumber = 1; + currLineStartIndex = 1; + currIndex = 0; + } + + public Object readTopLevelObject() { + Object plist = readObject(); + + skipCommentWhitespace(); + if (!isAtEnd()) { + throwParseException( + "Extra characters in plist string after parsing object. A plist should only contain one top-level object."); + } + + return plist; + } + + private void throwParseException(String errorMessage) { + int column = currIndex - currLineStartIndex + 1; + throw new RuntimeException(errorMessage + " (Line " + lineNumber + ", column " + column + ")"); + } + + private void updateLineNumberWithIndex(int lineStartIndex) { + lineNumber++; + currLineStartIndex = lineStartIndex; + } + + private boolean isAtEnd() { + return currIndex >= buffer.length; + } + + private void skipDoubleslashComment() { + while (!isAtEnd() && buffer[currIndex] != '\n') { + currIndex++; + } + } + + private void skipStandardCComment() { + currIndex++; // skip over the starting '/' + + while (!isAtEnd()) { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex + 1); + + currIndex++; + + if (buffer[currIndex - 2] == '*' && buffer[currIndex - 1] == '/') { + return; + } + } + + throwParseException("Input exhausted while parsing comment"); + } + + private void skipWhitespace() { + while (!isAtEnd() && isWhitespace(buffer[currIndex])) { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex + 1); + currIndex++; + } + } + + private void skipCommentWhitespace() { + boolean done = false; + + while (!done) { + done = true; + + skipWhitespace(); + if ((buffer.length - currIndex) > 1 && buffer[currIndex] == '/') { + if (buffer[currIndex + 1] == '/') { + done = false; // iterate again + skipDoubleslashComment(); + } else if (buffer[currIndex + 1] == '*') { + done = false; // iterate again + skipStandardCComment(); + } + } + } + } + + private Object readObject() { + skipCommentWhitespace(); + if (isAtEnd()) + return null; + + // Data (i.e. byte[]) not supported + if (buffer[currIndex] == '"') + return readQuotedString(); + if (buffer[currIndex] == '(') + return readList(); + if (buffer[currIndex] == '{') + return readMap(); + + return readUnquotedString(); + } + + private static final byte valueForHexDigit(char c) { + if (c >= '0' && c <= '9') + return (byte) (c - '0'); + if (c >= 'a' && c <= 'f') + return (byte) ((c - 'a') + 10); + if (c >= 'A' && c <= 'F') + return (byte) ((c - 'A') + 10); + + return 0; + } + + private static final boolean isOctalDigit(char c) { + return c >= '0' && c <= '7'; + } + + private static final boolean isHexDigit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + + private static String unquotedStringChars = "._$:/"; // chars allowed in unquoted strings + private static String whitespaceChars = " \t\n\r\f"; + + private static final boolean isWhitespace(char c) { + return whitespaceChars.indexOf(c) >= 0; + } + + private static final boolean isValidUnquotedStringChar(char c) { + return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || unquotedStringChars.indexOf(c) >= 0); + } + + private String readUnquotedString() { + int startIndex = currIndex; + + while (!isAtEnd() && isValidUnquotedStringChar(buffer[currIndex])) + currIndex++; + + if (startIndex == currIndex) + throwParseException("No allowable characters found to parse unquoted string"); + + return new String(buffer, startIndex, currIndex - startIndex); + } + + private String readQuotedString() { + currIndex++; // skip over '"' + + StringBuffer stringBuffer = new StringBuffer(); + int startIndex = currIndex; + + while (!isAtEnd() && buffer[currIndex] != '"') { + if (buffer[currIndex] != '\\') { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex + 1); + + /* + * Just increment the index -- all these characters will be appended in chunks, + * either before an escape sequence or at the end. + */ + currIndex++; + } else // it's an escape + { + /* Append anything scanned past before the '\\' */ + if (startIndex < currIndex) + stringBuffer.append(buffer, startIndex, currIndex - startIndex); + currIndex++; // skip over '\\' + + if (isAtEnd()) + throwParseException("Input exhausted while parsing escape sequence"); + + switch (buffer[currIndex]) { + case 't': + stringBuffer.append('\t'); + currIndex++; + break; // tab + case 'n': + stringBuffer.append('\n'); + currIndex++; + break; // newline + case 'r': + stringBuffer.append('\r'); + currIndex++; + break; // carriage return + case 'f': + stringBuffer.append('\f'); + currIndex++; + break; // form feed + case 'b': + stringBuffer.append('\b'); + currIndex++; + break; // backspace + case 'a': + stringBuffer.append('\007'); + currIndex++; + break; // bell + case 'v': + stringBuffer.append('\013'); + currIndex++; + break; // vertical tab + case 'U': + case 'u': { + /* A Unicode escape. Always followed by 4 hex digits. */ + currIndex++; // skip past the 'U' + if ((currIndex + 4) > buffer.length) + throwParseException("Not enough chars to parse \\U sequence"); + + if (!isHexDigit(buffer[currIndex]) || !isHexDigit(buffer[currIndex + 1]) + || !isHexDigit(buffer[currIndex + 2]) || !isHexDigit(buffer[currIndex + 3])) { + throwParseException("Four hex digits not found for \\U sequence"); + } + + byte byte3 = valueForHexDigit(buffer[currIndex]); + byte byte2 = valueForHexDigit(buffer[currIndex + 1]); + byte byte1 = valueForHexDigit(buffer[currIndex + 2]); + byte byte0 = valueForHexDigit(buffer[currIndex + 3]); + char theChar = (char) ((byte3 << 12) + (byte2 << 8) + (byte1 << 4) + byte0); + stringBuffer.append(theChar); + currIndex += 4; + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* An octal escape. Expect 1, 2, or 3 octal digits. */ + int digits = 0; + int value = 0; + + do { + value *= 8; + value += (int) (buffer[currIndex] - '0'); + currIndex++; + digits++; + } while (digits <= 3 && !isAtEnd() && isOctalDigit(buffer[currIndex])); + + if (value > 255) + throwParseException("Value too large in octal escape sequence (> 0377)"); + + // This assumes value is in ISO Latin 1 encoding + stringBuffer.append((char) value); + break; + } + /* I guess plists can't have the \x{HEX}{HEX} escapes */ + default: { + // Unknown escape sequence, just add the character. + // GCC warns if this isn't a '"', '\'', or '\\'... + stringBuffer.append(buffer[currIndex]); + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex + 1); + currIndex++; + break; + } + } // end case + + /* Reset startIndex, so a verbatim copy will now start from this index */ + startIndex = currIndex; + + } // end '\\' escape + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing quoted string"); + if (startIndex < currIndex) + stringBuffer.append(buffer, startIndex, currIndex - startIndex); + currIndex++; // skip past '"' + + return stringBuffer.toString(); + } + + private List readList() { + List newList = new ArrayList(); + + currIndex++; // skip over '(' + skipCommentWhitespace(); + while (!isAtEnd() && buffer[currIndex] != ')') { + /* A comma is required between list elements */ + if (newList.size() > 0) { + if (buffer[currIndex] != ',') + throwParseException("List parsing failed: expecting ','"); + currIndex++; + skipCommentWhitespace(); + if (isAtEnd()) + throwParseException("Input exhausted while parsing list"); + } + + if (buffer[currIndex] != ')') { + Object plistObject = readObject(); + if (plistObject == null) + throwParseException("List parsing failed: could not read contained object."); + newList.add(plistObject); + skipCommentWhitespace(); + } + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing list"); + currIndex++; // skip past ')' + + return newList; + } + + private Map readMap() { + HashMap newMap = new HashMap(); + + currIndex++; // skip over open brace + skipCommentWhitespace(); + + while (!isAtEnd() && buffer[currIndex] != '}') { + Object key; + Object value; + + key = readObject(); + if (key == null || !(key instanceof String)) + throwParseException("Map parsing failed: could not parse key or key is not a String"); + + skipCommentWhitespace(); + if (isAtEnd() || buffer[currIndex] != '=') + throwParseException("Map parsing failed: expecting '='"); + currIndex++; // skip over '=' + skipCommentWhitespace(); + if (isAtEnd()) + throwParseException("Input exhausted while parsing map"); + + value = readObject(); + if (value == null) + throwParseException("Map parsing failed: could not parse value object"); + + skipCommentWhitespace(); + if (isAtEnd() || buffer[currIndex] != ';') + throwParseException("Map parsing failed: expecting ';'"); + currIndex++; // skip over ';' + skipCommentWhitespace(); + + newMap.put(key, value); + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing map"); + currIndex++; // skip past '}' + + return newMap; + } + + public static void main(String[] args) { + String filename = args[0]; + Object plist = PropertyListParser.propertyListFromFile(filename); + System.out.println(plist); + } } - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java index 6d35b7b..59104e5 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java @@ -21,518 +21,488 @@ package net.wotonomy.foundation.internal; import java.util.*; //collections /** -* A queue based implementation of the Map interface. This class provides for -* all the opertions of a map, but keeps the entries in the same sequence as -* originally added. The first entry placed in the map will be the first -* entry retreived during iteration: first-in, first-out (FIFO).

-* -* Keys cannot be duplicated. If an entry is made using a key that already -* exists, the value for that key will be replaced with that new value. There -* are no such restrictions on the values. The values may be null.

-* -* Some convenience methods are provided for the queue type operations. The -* first key can be retreived as well as the last key. A key can be used -* to retreive its corresponding value from the map. A value can also be used -* to retreive its key from the map, however, since there may be multiple values -* of the same equality, the first key found will be returned.

-* -* @author rglista@blacksmith.com -* @author mpowers@blacksmith.com -* @date $Date: 2006-02-18 17:41:36 -0500 (Sat, 18 Feb 2006) $ -* @revision $Revision: 899 $ -*/ -public class QueueMap implements Map -{ - List values; - List keys; - Map keyToValue; - -/** -* Creates an empty QueueMap. -*/ - public QueueMap() - { - values = new LinkedList(); - keys = new LinkedList(); - keyToValue = new HashMap(); - } - -/** -* Creates a QueueMap and places the entries from the passed in map into this map. -* The order of the entries is based on however the iterator iteratated through -* the map entries. -* @param t A map object. -*/ - public QueueMap( Map t ) - { - values = new ArrayList(); - keys = new ArrayList(); - keyToValue = new HashMap(); - - putAll( t ); - } - -/** -* Removes all the entries from this map. -*/ - public void clear() - { - values.clear(); - keys.clear(); - keyToValue.clear(); - } - -/** -* Tests to see if the key is contained in the map. If the key is present in -* the map, then TRUE is returned, otherwise FALSE is returned. The equals() -* operation is used to determine equality. -* @return True if the map contains the given key, false otherwise. -*/ - public boolean containsKey( Object key ) - { - return keyToValue.containsKey( key ); - } - -/** -* Tests to see if the value is contained in the map. If the value is present in -* the map, then TRUE is returned, otherwise FALSE is returned. The equals() -* operation is used to determine equality. The value can be null and will -* return TRUE if null is a value in the map. If TRUE is returned, then there -* may be more than one values in the map. -* @return True if the map contains the given value, false otherwise. -*/ - public boolean containsValue( Object value ) - { - return keyToValue.containsValue( value ); - } - -/** -* Returns a set view of the mappings contained in this map. Each element -* in the returned set is a Map.Entry. The returned set is NOT backed -* by the map, so changes to the map are NOT reflected in the set, and vice-versa. -* The returned set is independent of this Map and its underlying structure. -* -* @return a set view of the mappings contained in this map. -*/ - public Set entrySet() - { - Set result = new HashSet(keys.size(), 1F); - - for ( int i = 0; i < keys.size(); i++ ) - { - result.add( new KeyValuePair( keys.get(i), values.get(i) ) ); - } - - return result; - } - -/** -* Compares the specified object with this map for equality. Returns -* true if the given object is also a map and the two Maps -* represent the same mappings. More formally, two maps t1 and -* t2 represent the same mappings if -* t1.entrySet().equals(t2.entrySet()). This ensures that the -* equals method works properly across different implementations -* of the Map interface. -* -* @param o object to be compared for equality with this map. -* @return true if the specified object is equal to this map. -*/ - public boolean equals( Object o ) - { - return keyToValue.equals( o ); - } - -/** -* Returns the corresponding value for the given key. The returned value may be -* null as that is a legal value in this map. However, if the key is not -* contained in this map then null is also returned. Use the containsKey() -* method to distinguish between the two cases. -* @param key A key into the map. -* @return The value corresponding to the key (can be null), or null if the key -* is not contained in the map. -*/ - public Object get( Object key ) - { - return keyToValue.get( key ); - } - -/** -* Returns the hash code value for this map. The hash code of a map -* is defined to be the sum of the hashCodes of each entry in the map's -* entrySet view. This ensures that t1.equals(t2) implies -* that t1.hashCode()==t2.hashCode() for any two maps -* t1 and t2, as required by the general -* contract of Object.hashCode. -* -* @return the hash code value for this map. -*/ - public int hashCode() - { - return keyToValue.hashCode(); - } - -/** -* Returns true is this map contains no key-value mappings. -* @return True is this map contains no entries, false otherwise. -*/ - public boolean isEmpty() - { - return keyToValue.isEmpty(); - } - -/** -* Returns the keys used in the map. There is no order implied in the returned -* set and may be different than the the order in which they were inserted. -* @return A Set containing all the keys used in the map. -*/ - public Set keySet() - { - Set result = new HashSet(keys.size(), 1F); - - for ( int i = 0; i < keys.size(); i++ ) - { - result.add( keys.get(i) ); - } - - return result; - } - -/** -* Places the key/value entry into the map at the end position. If the key -* already exists in the map, then its value is replaced by the new given -* value. The mapping does not change order in this case. The specified -* key cannot be null. -* @param key The key to place into the map, cannot be null. -* @param value The value to associate with the key, may be null. -* @return Null if the key is new, the replaced value if the key already -* existed. (Note: If the key was null, then null is returned.) -*/ - public Object put( Object key, Object value ) - { - if ( key == null ) return null; - - if ( keys.contains( key ) ) - { - values.remove( keys.indexOf( key ) ); - values.add( keys.indexOf( key ), value ); - } - else - { - values.add( value ); - keys.add( key ); - } - - return keyToValue.put( key, value ); - } - -/** -* Places all the entries from this given map into this map. If the keys -* already exist, then there values are replaced. -* @param t A map of key/value entries. -*/ - public void putAll( Map t ) - { - if ( t == null ) - { - // Nothing to do! - return; - } - - // Place the entries from the passed in map into this map. - for ( Iterator it = t.keySet().iterator(); it.hasNext(); ) - { - Object aKey = it.next(); - put( aKey, t.get( aKey ) ); - } - } - -/** -* Remove the mapping of the key and associated value from this map. -* Note: null is a valid value in the map. -* @param key A key to remove from the map, cannot be null. -* @return The value of the removed mapping, null if the mapping did not exist. -* Null could also be returned if the associated value of the key was null. -*/ - public Object remove( Object key ) - { - if ( key == null ) return null; - - Object value = null; - - if ( containsKey( key ) ) - { - value = keyToValue.remove( key ); - int i = values.indexOf( value ); - if ( i != -1 ) - { - keys.remove( i ); - values.remove( i ); - } - } - - return value; - } - -/** -* Returns the number of key/value pairs in this map. -* @return The number of pairs. -*/ - public int size() - { - return values.size(); - } - -/** -* Returns an ordered list of keys from the map. The order is the same -* as the added order of the mapped items.
-* NOTE: The returned list is the underlying keys list used by this class. -* If modification are to be made to this list, it should be cloned first. -* @return An ordered list of keys. -*/ - public List keys() - { - return keys; - } - -/** -* Returns an ordered list of values from the map. The order is the same -* as the added order of the mapped items. -* NOTE: The returned list is the underlying keys list used by this class. -* If modification are to be made to this list, it should be cloned first. -* @return An ordered list of values. -*/ - public Collection values() - { - return values; - } - -/** -* Returns the corresponding value for the given key. The returned value may be -* null as that is a legal value in this map. However, if the key is not -* contained in this map then null is also returned. Use the containsKey() -* method to distinguish between the two cases. -* @param key A key into the map. -* @return The value corresponding to the key (can be null), or null if the key -* is not contained in the map. -*/ - public Object getValueForKey( Object key ) - { - return keyToValue.get( key ); - } - -/** -* Returns the associated key for this value. Since there may be more than one -* of the same value in the map, this returns the first key associated with this -* value. Null is returned if the value is not in the map. -* @param value A value that is contained in this map. -* @return A first key that corresponds to this value. -*/ - public Object getKeyForValue( Object value ) - { - int i = values.indexOf( value ); - if ( i != -1 ) - { - return keys.get( i ); - } - return null; - } - -/** -* Returns the first key in the map. If the map is empty, then null is -* returned. -* @return The first key in the map, null if there are no mappings. -*/ - public Object getFirstKey() - { - if ( keys.size() < 1 ) - { - return null; - } - return keys.get(0); - } - -/** -* Returns the last key in the map. If the map is empty, then null is -* returned. -* @return The last key in the map, null if there are no mappings. -*/ - public Object getLastKey() - { - if ( keys.size() < 1 ) - { - return null; - } - return keys.get( keys.size() -1 ); - } - -/** -* This class contains a key/value pair. The key must be a valid object, -* it cannot be null. The value can be a valid object or null. -*/ - static public class KeyValuePair implements Map.Entry - { - Object key; - Object value; - - /** - * Default constructor. The constructor takes the key and value as parameters. - * Since the key cannot be null, it must be specified during initialization. - * The value can be null. - * @param key The key object of this pair. The key cannot be null. - * @param value The value object of this pair. The value can be null. - */ - public KeyValuePair( Object aKey, Object aValue ) - { - key = aKey; - value = aValue; - } - - /** - * Returns the key object of this pair. - * @return The key object. - */ - public Object getKey() - { - return key; - } - - /** - * Returns the the value object of this pair. May be null. - * @return The value object. - */ - public Object getValue() - { - return value; - } - - /** - * Sets the value object of this pair. May be an object or null. - * @param aValue The value object to place into this pair. - */ - public Object setValue( Object aValue ) - { - Object result = value; - value = aValue; - return result; - } - - public boolean equals( Object o ) - { - if ( o instanceof KeyValuePair ) - { - KeyValuePair p = (KeyValuePair) o; - if ( ( key.equals( p.getKey() ) ) && ( value.equals( p.getValue() ) ) ) - { - return true; - } - } - return false; - } - } - - /** - * Returns a string reprsentation of this class. The contents of the - * map are placed in the string in its proper order. - */ - public String toString() - { - int max = size() - 1; - StringBuffer buf = new StringBuffer(); - - buf.append("{"); - for (int j = 0; j <= max; j++) - { - buf.append(keys.get(j) + "=" + values.get(j)); - if (j < max) - { - buf.append(", "); - } - } - buf.append("}"); - - return buf.toString(); - } - - // unit test - public static void main( String[] argv ) - { - QueueMap qMap; - - qMap = new QueueMap(); - for (int i = 0; i < 5; i++) - { - qMap.put(Integer.toString(i), Integer.toString(i)); - } - System.out.println("\nMap = " + qMap); - System.out.println("Keys = " + qMap.keys()); - System.out.println("Values = " + qMap.values()); - - qMap = new QueueMap(); - for (int i = 0; i < 5; i++) - { - qMap.put(Integer.toString(i), "A"); - } - System.out.println("\nMap = " + qMap); - System.out.println("Keys = " + qMap.keys()); - System.out.println("Values = " + qMap.values()); - - qMap = new QueueMap(); - for (int i = 0; i < 5; i++) - { - qMap.put(Integer.toString(i), null); - } - System.out.println("\nMap = " + qMap); - System.out.println("Keys = " + qMap.keys()); - System.out.println("Values = " + qMap.values()); - - Map aMap = new HashMap(); - for (int i = 0; i < 5; i++) - { - aMap.put(Integer.toString(i), Integer.toString(i)); - } - qMap = new QueueMap( aMap ); - System.out.println("\nHashMap = " + aMap); - System.out.println("Map = " + qMap); - System.out.println("Keys = " + qMap.keys()); - System.out.println("Values = " + qMap.values()); - - qMap = new QueueMap(); - qMap.put( "Test1", "String1" ); - qMap.put( "Test2", "String2" ); - qMap.put( "Test3", "String3" ); - qMap.put( "Test4", "String4" ); - qMap.put( "Test5", "String5" ); - System.out.println("\nStandard Test, Map = " + qMap); - qMap.put( "Test6", "String6" ); - qMap.put( "Test7", "String7" ); - System.out.println("Put New Test, Map = " + qMap); - qMap.put( "Test2", "New String2" ); - qMap.put( "Test6", "New String6" ); - System.out.println("Put Existing Test, Map = " + qMap); - qMap.put( "Test5", null ); - qMap.put( "Test8", null ); - System.out.println("Put Null Test, Map = " + qMap); - qMap.remove( "Test1" ); - qMap.remove( "Test3" ); - qMap.remove( "Test5" ); - qMap.remove( "Test9" ); - System.out.println("Remove Test, Map = " + qMap); - System.out.println(" Keys = " + qMap.keys()); - System.out.println(" Values = " + qMap.values()); - qMap.clear(); - qMap.put( "Test10", "String10" ); - qMap.put( "Test11", "String11" ); - qMap.put( "Test12", "String12" ); - System.out.println("Clear Test, Map = " + qMap); - - aMap = new HashMap(); - aMap.put( "Test10", "String10" ); - aMap.put( "Test11", "String11" ); - aMap.put( "Test12", "String12" ); - System.out.println("Equality Test, Equal = " + qMap.equals( aMap )); - } + * A queue based implementation of the Map interface. This class provides for + * all the opertions of a map, but keeps the entries in the same sequence as + * originally added. The first entry placed in the map will be the first entry + * retreived during iteration: first-in, first-out (FIFO).
+ *
+ * + * Keys cannot be duplicated. If an entry is made using a key that already + * exists, the value for that key will be replaced with that new value. There + * are no such restrictions on the values. The values may be null.
+ *
+ * + * Some convenience methods are provided for the queue type operations. The + * first key can be retreived as well as the last key. A key can be used to + * retreive its corresponding value from the map. A value can also be used to + * retreive its key from the map, however, since there may be multiple values of + * the same equality, the first key found will be returned.
+ *
+ * + * @author rglista@blacksmith.com + * @author mpowers@blacksmith.com + * @date $Date: 2006-02-18 17:41:36 -0500 (Sat, 18 Feb 2006) $ + * @revision $Revision: 899 $ + */ +public class QueueMap implements Map { + List values; + List keys; + Map keyToValue; + + /** + * Creates an empty QueueMap. + */ + public QueueMap() { + values = new LinkedList(); + keys = new LinkedList(); + keyToValue = new HashMap(); + } + + /** + * Creates a QueueMap and places the entries from the passed in map into this + * map. The order of the entries is based on however the iterator iteratated + * through the map entries. + * + * @param t A map object. + */ + public QueueMap(Map t) { + values = new ArrayList(); + keys = new ArrayList(); + keyToValue = new HashMap(); + + putAll(t); + } + + /** + * Removes all the entries from this map. + */ + public void clear() { + values.clear(); + keys.clear(); + keyToValue.clear(); + } + + /** + * Tests to see if the key is contained in the map. If the key is present in the + * map, then TRUE is returned, otherwise FALSE is returned. The equals() + * operation is used to determine equality. + * + * @return True if the map contains the given key, false otherwise. + */ + public boolean containsKey(Object key) { + return keyToValue.containsKey(key); + } + + /** + * Tests to see if the value is contained in the map. If the value is present in + * the map, then TRUE is returned, otherwise FALSE is returned. The equals() + * operation is used to determine equality. The value can be null and will + * return TRUE if null is a value in the map. If TRUE is returned, then there + * may be more than one values in the map. + * + * @return True if the map contains the given value, false otherwise. + */ + public boolean containsValue(Object value) { + return keyToValue.containsValue(value); + } + + /** + * Returns a set view of the mappings contained in this map. Each element in the + * returned set is a Map.Entry. The returned set is NOT backed by the + * map, so changes to the map are NOT reflected in the set, and vice-versa. The + * returned set is independent of this Map and its underlying structure. + * + * @return a set view of the mappings contained in this map. + */ + public Set entrySet() { + Set result = new HashSet(keys.size(), 1F); + + for (int i = 0; i < keys.size(); i++) { + result.add(new KeyValuePair(keys.get(i), values.get(i))); + } + + return result; + } + + /** + * Compares the specified object with this map for equality. Returns + * true if the given object is also a map and the two Maps represent + * the same mappings. More formally, two maps t1 and t2 + * represent the same mappings if t1.entrySet().equals(t2.entrySet()). + * This ensures that the equals method works properly across different + * implementations of the Map interface. + * + * @param o object to be compared for equality with this map. + * @return true if the specified object is equal to this map. + */ + public boolean equals(Object o) { + return keyToValue.equals(o); + } + + /** + * Returns the corresponding value for the given key. The returned value may be + * null as that is a legal value in this map. However, if the key is not + * contained in this map then null is also returned. Use the containsKey() + * method to distinguish between the two cases. + * + * @param key A key into the map. + * @return The value corresponding to the key (can be null), or null if the key + * is not contained in the map. + */ + public Object get(Object key) { + return keyToValue.get(key); + } + + /** + * Returns the hash code value for this map. The hash code of a map is defined + * to be the sum of the hashCodes of each entry in the map's entrySet view. This + * ensures that t1.equals(t2) implies that + * t1.hashCode()==t2.hashCode() for any two maps t1 and + * t2, as required by the general contract of Object.hashCode. + * + * @return the hash code value for this map. + */ + public int hashCode() { + return keyToValue.hashCode(); + } + + /** + * Returns true is this map contains no key-value mappings. + * + * @return True is this map contains no entries, false otherwise. + */ + public boolean isEmpty() { + return keyToValue.isEmpty(); + } + + /** + * Returns the keys used in the map. There is no order implied in the returned + * set and may be different than the the order in which they were inserted. + * + * @return A Set containing all the keys used in the map. + */ + public Set keySet() { + Set result = new HashSet(keys.size(), 1F); + + for (int i = 0; i < keys.size(); i++) { + result.add(keys.get(i)); + } + + return result; + } + + /** + * Places the key/value entry into the map at the end position. If the key + * already exists in the map, then its value is replaced by the new given value. + * The mapping does not change order in this case. The specified key cannot be + * null. + * + * @param key The key to place into the map, cannot be null. + * @param value The value to associate with the key, may be null. + * @return Null if the key is new, the replaced value if the key already + * existed. (Note: If the key was null, then null is returned.) + */ + public Object put(Object key, Object value) { + if (key == null) + return null; + + if (keys.contains(key)) { + values.remove(keys.indexOf(key)); + values.add(keys.indexOf(key), value); + } else { + values.add(value); + keys.add(key); + } + + return keyToValue.put(key, value); + } + + /** + * Places all the entries from this given map into this map. If the keys already + * exist, then there values are replaced. + * + * @param t A map of key/value entries. + */ + public void putAll(Map t) { + if (t == null) { + // Nothing to do! + return; + } + + // Place the entries from the passed in map into this map. + for (Iterator it = t.keySet().iterator(); it.hasNext();) { + Object aKey = it.next(); + put(aKey, t.get(aKey)); + } + } + + /** + * Remove the mapping of the key and associated value from this map. Note: null + * is a valid value in the map. + * + * @param key A key to remove from the map, cannot be null. + * @return The value of the removed mapping, null if the mapping did not exist. + * Null could also be returned if the associated value of the key was + * null. + */ + public Object remove(Object key) { + if (key == null) + return null; + + Object value = null; + + if (containsKey(key)) { + value = keyToValue.remove(key); + int i = values.indexOf(value); + if (i != -1) { + keys.remove(i); + values.remove(i); + } + } + + return value; + } + + /** + * Returns the number of key/value pairs in this map. + * + * @return The number of pairs. + */ + public int size() { + return values.size(); + } + + /** + * Returns an ordered list of keys from the map. The order is the same as the + * added order of the mapped items.
+ * NOTE: The returned list is the underlying keys list used by this class. If + * modification are to be made to this list, it should be cloned first. + * + * @return An ordered list of keys. + */ + public List keys() { + return keys; + } + + /** + * Returns an ordered list of values from the map. The order is the same as the + * added order of the mapped items. NOTE: The returned list is the underlying + * keys list used by this class. If modification are to be made to this list, it + * should be cloned first. + * + * @return An ordered list of values. + */ + public Collection values() { + return values; + } + + /** + * Returns the corresponding value for the given key. The returned value may be + * null as that is a legal value in this map. However, if the key is not + * contained in this map then null is also returned. Use the containsKey() + * method to distinguish between the two cases. + * + * @param key A key into the map. + * @return The value corresponding to the key (can be null), or null if the key + * is not contained in the map. + */ + public Object getValueForKey(Object key) { + return keyToValue.get(key); + } + + /** + * Returns the associated key for this value. Since there may be more than one + * of the same value in the map, this returns the first key associated with this + * value. Null is returned if the value is not in the map. + * + * @param value A value that is contained in this map. + * @return A first key that corresponds to this value. + */ + public Object getKeyForValue(Object value) { + int i = values.indexOf(value); + if (i != -1) { + return keys.get(i); + } + return null; + } + + /** + * Returns the first key in the map. If the map is empty, then null is returned. + * + * @return The first key in the map, null if there are no mappings. + */ + public Object getFirstKey() { + if (keys.size() < 1) { + return null; + } + return keys.get(0); + } + + /** + * Returns the last key in the map. If the map is empty, then null is returned. + * + * @return The last key in the map, null if there are no mappings. + */ + public Object getLastKey() { + if (keys.size() < 1) { + return null; + } + return keys.get(keys.size() - 1); + } + + /** + * This class contains a key/value pair. The key must be a valid object, it + * cannot be null. The value can be a valid object or null. + */ + static public class KeyValuePair implements Map.Entry { + Object key; + Object value; + + /** + * Default constructor. The constructor takes the key and value as parameters. + * Since the key cannot be null, it must be specified during initialization. The + * value can be null. + * + * @param key The key object of this pair. The key cannot be null. + * @param value The value object of this pair. The value can be null. + */ + public KeyValuePair(Object aKey, Object aValue) { + key = aKey; + value = aValue; + } + + /** + * Returns the key object of this pair. + * + * @return The key object. + */ + public Object getKey() { + return key; + } + + /** + * Returns the the value object of this pair. May be null. + * + * @return The value object. + */ + public Object getValue() { + return value; + } + + /** + * Sets the value object of this pair. May be an object or null. + * + * @param aValue The value object to place into this pair. + */ + public Object setValue(Object aValue) { + Object result = value; + value = aValue; + return result; + } + + public boolean equals(Object o) { + if (o instanceof KeyValuePair) { + KeyValuePair p = (KeyValuePair) o; + if ((key.equals(p.getKey())) && (value.equals(p.getValue()))) { + return true; + } + } + return false; + } + } + + /** + * Returns a string reprsentation of this class. The contents of the map are + * placed in the string in its proper order. + */ + public String toString() { + int max = size() - 1; + StringBuffer buf = new StringBuffer(); + + buf.append("{"); + for (int j = 0; j <= max; j++) { + buf.append(keys.get(j) + "=" + values.get(j)); + if (j < max) { + buf.append(", "); + } + } + buf.append("}"); + + return buf.toString(); + } + + // unit test + public static void main(String[] argv) { + QueueMap qMap; + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) { + qMap.put(Integer.toString(i), Integer.toString(i)); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) { + qMap.put(Integer.toString(i), "A"); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) { + qMap.put(Integer.toString(i), null); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + Map aMap = new HashMap(); + for (int i = 0; i < 5; i++) { + aMap.put(Integer.toString(i), Integer.toString(i)); + } + qMap = new QueueMap(aMap); + System.out.println("\nHashMap = " + aMap); + System.out.println("Map = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + qMap.put("Test1", "String1"); + qMap.put("Test2", "String2"); + qMap.put("Test3", "String3"); + qMap.put("Test4", "String4"); + qMap.put("Test5", "String5"); + System.out.println("\nStandard Test, Map = " + qMap); + qMap.put("Test6", "String6"); + qMap.put("Test7", "String7"); + System.out.println("Put New Test, Map = " + qMap); + qMap.put("Test2", "New String2"); + qMap.put("Test6", "New String6"); + System.out.println("Put Existing Test, Map = " + qMap); + qMap.put("Test5", null); + qMap.put("Test8", null); + System.out.println("Put Null Test, Map = " + qMap); + qMap.remove("Test1"); + qMap.remove("Test3"); + qMap.remove("Test5"); + qMap.remove("Test9"); + System.out.println("Remove Test, Map = " + qMap); + System.out.println(" Keys = " + qMap.keys()); + System.out.println(" Values = " + qMap.values()); + qMap.clear(); + qMap.put("Test10", "String10"); + qMap.put("Test11", "String11"); + qMap.put("Test12", "String12"); + System.out.println("Clear Test, Map = " + qMap); + + aMap = new HashMap(); + aMap.put("Test10", "String10"); + aMap.put("Test11", "String11"); + aMap.put("Test12", "String12"); + System.out.println("Equality Test, Equal = " + qMap.equals(aMap)); + } } - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java index 9133a8d..8694564 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java @@ -47,7 +47,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== - */ + */ package net.wotonomy.foundation.internal; @@ -61,147 +61,134 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** - * This implementation of URL Resource Reader assumes 2 types - * of base urls. A base url that ends with / is considered a - * resource folder, whereas a resource that does not end with - * / is considered a zip/jar resource folder. + * This implementation of URL Resource Reader assumes 2 types of base urls. A + * base url that ends with / is considered a resource folder, whereas a resource + * that does not end with / is considered a zip/jar resource folder. * - * If the resource folder happens is a zip/jar archive, the - * entries are always cached. - * For non-zip base urls, one could specify whether or not it should - * be cached. + * If the resource folder happens is a zip/jar archive, the entries are always + * cached. For non-zip base urls, one could specify whether or not it should be + * cached. * * @author Harish Prabandham */ -public class URLResourceReader { - private Hashtable resourceCache = new Hashtable(); - private boolean iszip = true; - private URL url = null; - private boolean cache = true; - - /** - * Creates a new URLResourceReader object. You can either give - * the URL of the zip/jar file or a base url where to - * look for additional resources. If the url ends with - * "/" then it is assumed to be a Base URL. - * @param The base url to look for the resources. - * @param If the base url is not a zip/jar, then true indicates - * that entries should be cached, false otherwise. - */ - public URLResourceReader(URL baseurl, boolean cache) throws IOException { - this.url = baseurl; - this.cache = cache; - this.iszip = !url.getFile().endsWith("/"); - if(this.iszip) - this.cache = true; - initialize(); - } - - /** - * equivalent to URLResourceReader(baseurl, false) - */ - public URLResourceReader(URL baseurl) throws IOException { - this(baseurl, false); - } - - /** - * Creates a new URLResourceReader object with the given - * input stream. The stream is assumed to be a zip/jar - * stream. - */ - public URLResourceReader(InputStream is) throws IOException { - init(is); - } - - private void initialize() throws IOException { - if(iszip) { - InputStream is = url.openStream(); - init(is); - is.close(); - } - } - - private byte[] readFully(InputStream is) throws IOException { - byte[] buf = new byte[1024]; - int num = 0; - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - - while( (num = is.read(buf)) != -1) { - bout.write(buf, 0, num); - } - - return bout.toByteArray(); - } - - private void init(InputStream is) throws IOException { - ZipInputStream zstream = new ZipInputStream(is); - ZipEntry entry; - - while( (entry = zstream.getNextEntry()) != null) { - byte[] entryData = readFully(zstream); - if(cache) - resourceCache.put(entry.getName(), entryData); - zstream.closeEntry(); - } - - zstream.close(); - } - - /** - * Returns an Enumeration of all "known" resource names. - */ - public Enumeration getResourceNames() { - return resourceCache.keys(); - } - - /** - * Returns an array of bytes read for this resource if the - * resource exists. This method blocks until the resource - * has been fully read. If the resource does not exist, - * this method returns null. - */ - public byte[] getResource(String resource) { - // lookup the data in the cache... - byte[] data = (byte[]) resourceCache.get(resource); - if(data != null) { - return data; - } - - // if the data was to come from a zip file that we - // already read fully & cached , then it is probably - // not there. - if(iszip) { - return null; - } - - // Now the only choice left is to make a url connection. - try { - URL realURL = new URL(url.getProtocol(), url.getHost(), - url.getFile() + resource); - data = readFully(realURL.openStream()); - // add it to cache if needed... - if(cache) - resourceCache.put(resource, data); - return data; - } catch(Exception e) { - return null; - } - } - - public void close() { - resourceCache.clear(); - resourceCache = null; - } - - public String toString() { - return url.toString(); - } +public class URLResourceReader { + private Hashtable resourceCache = new Hashtable(); + private boolean iszip = true; + private URL url = null; + private boolean cache = true; + + /** + * Creates a new URLResourceReader object. You can either give the URL of the + * zip/jar file or a base url where to look for additional resources. If the url + * ends with "/" then it is assumed to be a Base URL. + * + * @param The base url to look for the resources. + * @param If the base url is not a zip/jar, then true indicates that entries + * should be cached, false otherwise. + */ + public URLResourceReader(URL baseurl, boolean cache) throws IOException { + this.url = baseurl; + this.cache = cache; + this.iszip = !url.getFile().endsWith("/"); + if (this.iszip) + this.cache = true; + initialize(); + } + + /** + * equivalent to URLResourceReader(baseurl, false) + */ + public URLResourceReader(URL baseurl) throws IOException { + this(baseurl, false); + } + + /** + * Creates a new URLResourceReader object with the given input stream. The + * stream is assumed to be a zip/jar stream. + */ + public URLResourceReader(InputStream is) throws IOException { + init(is); + } + + private void initialize() throws IOException { + if (iszip) { + InputStream is = url.openStream(); + init(is); + is.close(); + } + } + + private byte[] readFully(InputStream is) throws IOException { + byte[] buf = new byte[1024]; + int num = 0; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + while ((num = is.read(buf)) != -1) { + bout.write(buf, 0, num); + } + + return bout.toByteArray(); + } + + private void init(InputStream is) throws IOException { + ZipInputStream zstream = new ZipInputStream(is); + ZipEntry entry; + + while ((entry = zstream.getNextEntry()) != null) { + byte[] entryData = readFully(zstream); + if (cache) + resourceCache.put(entry.getName(), entryData); + zstream.closeEntry(); + } + + zstream.close(); + } + + /** + * Returns an Enumeration of all "known" resource names. + */ + public Enumeration getResourceNames() { + return resourceCache.keys(); + } + + /** + * Returns an array of bytes read for this resource if the resource exists. This + * method blocks until the resource has been fully read. If the resource does + * not exist, this method returns null. + */ + public byte[] getResource(String resource) { + // lookup the data in the cache... + byte[] data = (byte[]) resourceCache.get(resource); + if (data != null) { + return data; + } + + // if the data was to come from a zip file that we + // already read fully & cached , then it is probably + // not there. + if (iszip) { + return null; + } + + // Now the only choice left is to make a url connection. + try { + URL realURL = new URL(url.getProtocol(), url.getHost(), url.getFile() + resource); + data = readFully(realURL.openStream()); + // add it to cache if needed... + if (cache) + resourceCache.put(resource, data); + return data; + } catch (Exception e) { + return null; + } + } + + public void close() { + resourceCache.clear(); + resourceCache = null; + } + + public String toString() { + return url.toString(); + } } - - - - - - - - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java index d6bc797..7f28e06 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java @@ -17,6 +17,7 @@ License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation.internal; + import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.Collection; @@ -25,694 +26,562 @@ import java.util.LinkedList; import java.util.Map; /** -* A utility class to convert objects to a desired class. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class ValueConverter -{ - /** - * Returns the specified object as converted to an instance of the - * specified class, or null if the conversion could not be performed. - */ - static public Object convertObjectToClass( Object anObject, Class aClass ) - { - if ( aClass == String.class ) - { - return getString( anObject ); - } - if ( aClass == Short.class ) - { - return getShort( anObject ); - } - if ( aClass == short.class ) - { - return getShort( anObject ); - } - if ( aClass == Integer.class ) - { - return getInteger( anObject ); - } - if ( aClass == int.class ) - { - return getInteger( anObject ); - } - if ( aClass == Long.class ) - { - return getLong( anObject ); - } - if ( aClass == long.class ) - { - return getLong( anObject ); - } - if ( aClass == Float.class ) - { - return getFloat( anObject ); - } - if ( aClass == float.class ) - { - return getFloat( anObject ); - } - if ( aClass == Double.class ) - { - return getDouble( anObject ); - } - if ( aClass == double.class ) - { - return getDouble( anObject ); - } - if ( aClass == java.util.Date.class ) - { - return getDate( anObject ); - } - if ( aClass == Boolean.class ) - { - return getBoolean( anObject ); - } - if ( aClass == boolean.class ) - { - return getBoolean( anObject ); - } - if ( aClass == Character.class ) - { - return getCharacter( anObject ); - } - if ( aClass == char.class ) - { - return getCharacter( anObject ); - } - if ( aClass == Byte.class ) - { - return getByte( anObject ); - } - if ( aClass == byte.class ) - { - return getByte( anObject ); - } - if ( Collection.class.isAssignableFrom( aClass ) ) - { - return getCollection( anObject, aClass ); - } - if ( aClass.isArray() ) - { - return getArray( anObject, aClass ); - } - - return convert( anObject, aClass ); - } - - /** - * Called by convertObjectToClass() when we need to - * convert to an unrecognized type. - * This implementation scans the constructors of the - * specified class for the best fit to the object. - * and returns a new instance with that constructor. - * Subclasses can override to directly support specific - * types. - */ - static protected Object convert( Object anObject, Class aClass ) - { - Constructor[] ctors = aClass.getConstructors(); - - Class[] types; - for ( int i = 0; i < ctors.length; i++ ) - { - types = ctors[i].getParameterTypes(); - if ( types.length == 1 ) - { - if ( types[0].equals( anObject.getClass() ) ) - { - try - { - return ctors[i].newInstance( new Object[] { anObject } ); - } - catch ( Exception exc ) - { - // fall through - } - } - } - } - - for ( int i = 0; i < ctors.length; i++ ) - { - types = ctors[i].getParameterTypes(); - if ( types.length == 1 ) - { - if ( anObject.getClass().isAssignableFrom( types[0] ) ) - { - try - { - return ctors[i].newInstance( new Object[] { anObject } ); - } - catch ( Exception exc ) - { - // fall through - } - } - } - } - - return null; - } - - /** - * Tries to convert all objects to either Numbers or objects - * that will produce a parsable toString result. - */ - static protected Object preprocess( Object anObject ) - { - if ( anObject instanceof Boolean ) - { - if ( ((Boolean)anObject).booleanValue() ) - { - return new Double( 1.0 ); - } - return new Double( 0.0 ); - } - - if ( anObject instanceof Character ) - { - return anObject.toString(); - } - - return anObject; - } - - static public short getShortValue( Object anObject ) - { - Short result = getShort( anObject ); - return ( result == null ) ? (short) 0 : result.shortValue(); - } - static public Short getShort( Object anObject ) - { - if ( anObject == null ) return new Short( (short) 0 ); - if ( "".equals( anObject ) ) return new Short( (short) 0 ); - if ( anObject instanceof Short ) return (Short) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Short( ((Number)anObject).shortValue() ); - } - - try - { - return Short.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public int getIntValue( Object anObject ) - { - Integer result = getInteger( anObject ); - return ( result == null ) ? 0 : result.intValue(); - } - static public Integer getInteger( Object anObject ) - { - if ( anObject == null ) return new Integer( 0 ); - if ( "".equals( anObject ) ) return new Integer( 0 ); - if ( anObject instanceof Integer ) return (Integer) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Integer( ((Number)anObject).intValue() ); - } - - try - { - return Integer.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public long getLongValue ( Object anObject ) - { - Long result = getLong( anObject ); - return ( result == null ) ? (long) 0 : result.longValue(); - } - static public Long getLong( Object anObject ) - { - if ( anObject == null ) return new Long( 0 ); - if ( "".equals( anObject ) ) return new Long( 0 ); - if ( anObject instanceof Long ) return (Long) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Long( ((Number)anObject).longValue() ); - } - - try - { - return Long.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public double getDoubleValue ( Object anObject ) - { - Double result = getDouble( anObject ); - return ( result == null ) ? 0.0f : result.doubleValue(); - } - static public Double getDouble( Object anObject ) - { - if ( anObject == null ) return new Double( 0.0 ); - if ( "".equals( anObject ) ) return new Double( 0 ); - if ( anObject instanceof Double ) return (Double) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Double( ((Number)anObject).doubleValue() ); - } - - try - { - return Double.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public float getFloatValue( Object anObject ) - { - Float result = getFloat( anObject ); - return ( result == null ) ? 0.0f : result.floatValue(); - } - static public Float getFloat( Object anObject ) - { - if ( anObject == null ) return new Float( 0.0 ); - if ( "".equals( anObject ) ) return new Float( 0.0 ); - if ( anObject instanceof Float ) return (Float) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Float( ((Number)anObject).floatValue() ); - } - - try - { - return Float.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public char getCharValue( Object anObject ) - { - Character result = getCharacter( anObject ); - return ( result == null ) ? (char) 0 : result.charValue(); - } - static public Character getCharacter( Object anObject ) - { - if ( anObject == null ) return new Character( (char) 0 ); - if ( anObject instanceof Character ) return (Character) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Character( (char) ((Number)anObject).byteValue() ); - } - - try - { - return new Character( anObject.toString().charAt( 0 ) ); - } - catch ( Exception exc ) - { - return null; - } - } - - static public byte getByteValue( Object anObject ) - { - Byte result = getByte ( anObject ); - return ( result == null ) ? (byte) 0 : result.byteValue(); - } - static public Byte getByte( Object anObject ) - { - if ( anObject == null ) return new Byte( Byte.MIN_VALUE ); - if ( "".equals( anObject ) ) return new Byte( Byte.MIN_VALUE ); - if ( anObject instanceof Byte ) return (Byte) anObject; - - anObject = preprocess( anObject ); - - if ( anObject instanceof Number ) - { - return new Byte( ((Number)anObject).byteValue() ); - } - - try - { - return Byte.decode( anObject.toString() ); - } - catch ( Exception exc ) - { - // fall through - } - - try - { - return Byte.valueOf( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } + * A utility class to convert objects to a desired class. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class ValueConverter { + /** + * Returns the specified object as converted to an instance of the specified + * class, or null if the conversion could not be performed. + */ + static public Object convertObjectToClass(Object anObject, Class aClass) { + if (aClass == String.class) { + return getString(anObject); + } + if (aClass == Short.class) { + return getShort(anObject); + } + if (aClass == short.class) { + return getShort(anObject); + } + if (aClass == Integer.class) { + return getInteger(anObject); + } + if (aClass == int.class) { + return getInteger(anObject); + } + if (aClass == Long.class) { + return getLong(anObject); + } + if (aClass == long.class) { + return getLong(anObject); + } + if (aClass == Float.class) { + return getFloat(anObject); + } + if (aClass == float.class) { + return getFloat(anObject); + } + if (aClass == Double.class) { + return getDouble(anObject); + } + if (aClass == double.class) { + return getDouble(anObject); + } + if (aClass == java.util.Date.class) { + return getDate(anObject); + } + if (aClass == Boolean.class) { + return getBoolean(anObject); + } + if (aClass == boolean.class) { + return getBoolean(anObject); + } + if (aClass == Character.class) { + return getCharacter(anObject); + } + if (aClass == char.class) { + return getCharacter(anObject); + } + if (aClass == Byte.class) { + return getByte(anObject); + } + if (aClass == byte.class) { + return getByte(anObject); + } + if (Collection.class.isAssignableFrom(aClass)) { + return getCollection(anObject, aClass); + } + if (aClass.isArray()) { + return getArray(anObject, aClass); + } + + return convert(anObject, aClass); + } + + /** + * Called by convertObjectToClass() when we need to convert to an unrecognized + * type. This implementation scans the constructors of the specified class for + * the best fit to the object. and returns a new instance with that constructor. + * Subclasses can override to directly support specific types. + */ + static protected Object convert(Object anObject, Class aClass) { + Constructor[] ctors = aClass.getConstructors(); + + Class[] types; + for (int i = 0; i < ctors.length; i++) { + types = ctors[i].getParameterTypes(); + if (types.length == 1) { + if (types[0].equals(anObject.getClass())) { + try { + return ctors[i].newInstance(new Object[] { anObject }); + } catch (Exception exc) { + // fall through + } + } + } + } + + for (int i = 0; i < ctors.length; i++) { + types = ctors[i].getParameterTypes(); + if (types.length == 1) { + if (anObject.getClass().isAssignableFrom(types[0])) { + try { + return ctors[i].newInstance(new Object[] { anObject }); + } catch (Exception exc) { + // fall through + } + } + } + } + + return null; + } + + /** + * Tries to convert all objects to either Numbers or objects that will produce a + * parsable toString result. + */ + static protected Object preprocess(Object anObject) { + if (anObject instanceof Boolean) { + if (((Boolean) anObject).booleanValue()) { + return new Double(1.0); + } + return new Double(0.0); + } + + if (anObject instanceof Character) { + return anObject.toString(); + } + + return anObject; + } + + static public short getShortValue(Object anObject) { + Short result = getShort(anObject); + return (result == null) ? (short) 0 : result.shortValue(); + } + + static public Short getShort(Object anObject) { + if (anObject == null) + return new Short((short) 0); + if ("".equals(anObject)) + return new Short((short) 0); + if (anObject instanceof Short) + return (Short) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Short(((Number) anObject).shortValue()); + } + + try { + return Short.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static public int getIntValue(Object anObject) { + Integer result = getInteger(anObject); + return (result == null) ? 0 : result.intValue(); + } + + static public Integer getInteger(Object anObject) { + if (anObject == null) + return new Integer(0); + if ("".equals(anObject)) + return new Integer(0); + if (anObject instanceof Integer) + return (Integer) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Integer(((Number) anObject).intValue()); + } + + try { + return Integer.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static public long getLongValue(Object anObject) { + Long result = getLong(anObject); + return (result == null) ? (long) 0 : result.longValue(); + } + + static public Long getLong(Object anObject) { + if (anObject == null) + return new Long(0); + if ("".equals(anObject)) + return new Long(0); + if (anObject instanceof Long) + return (Long) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Long(((Number) anObject).longValue()); + } + + try { + return Long.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static public double getDoubleValue(Object anObject) { + Double result = getDouble(anObject); + return (result == null) ? 0.0f : result.doubleValue(); + } + + static public Double getDouble(Object anObject) { + if (anObject == null) + return new Double(0.0); + if ("".equals(anObject)) + return new Double(0); + if (anObject instanceof Double) + return (Double) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Double(((Number) anObject).doubleValue()); + } + + try { + return Double.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static public float getFloatValue(Object anObject) { + Float result = getFloat(anObject); + return (result == null) ? 0.0f : result.floatValue(); + } + + static public Float getFloat(Object anObject) { + if (anObject == null) + return new Float(0.0); + if ("".equals(anObject)) + return new Float(0.0); + if (anObject instanceof Float) + return (Float) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Float(((Number) anObject).floatValue()); + } + + try { + return Float.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static public char getCharValue(Object anObject) { + Character result = getCharacter(anObject); + return (result == null) ? (char) 0 : result.charValue(); + } + + static public Character getCharacter(Object anObject) { + if (anObject == null) + return new Character((char) 0); + if (anObject instanceof Character) + return (Character) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Character((char) ((Number) anObject).byteValue()); + } + + try { + return new Character(anObject.toString().charAt(0)); + } catch (Exception exc) { + return null; + } + } + + static public byte getByteValue(Object anObject) { + Byte result = getByte(anObject); + return (result == null) ? (byte) 0 : result.byteValue(); + } + + static public Byte getByte(Object anObject) { + if (anObject == null) + return new Byte(Byte.MIN_VALUE); + if ("".equals(anObject)) + return new Byte(Byte.MIN_VALUE); + if (anObject instanceof Byte) + return (Byte) anObject; + + anObject = preprocess(anObject); + + if (anObject instanceof Number) { + return new Byte(((Number) anObject).byteValue()); + } + + try { + return Byte.decode(anObject.toString()); + } catch (Exception exc) { + // fall through + } + + try { + return Byte.valueOf(anObject.toString()); + } catch (Exception exc) { + return null; + } + } /** - * Calls getBoolean and converts result to primitive. - */ - static public boolean getBooleanValue( Object anObject ) - { - Boolean result = getBoolean( anObject ); - return ( result == null ) ? false : result.booleanValue(); - } - + * Calls getBoolean and converts result to primitive. + */ + static public boolean getBooleanValue(Object anObject) { + Boolean result = getBoolean(anObject); + return (result == null) ? false : result.booleanValue(); + } + /** - * Numbers equal to zero are true; Strings equal to "yes" are true; - * Strings are then passed to the Boolean constructor. - * Other values return null. - */ - static public Boolean getBoolean( Object anObject ) - { - if ( anObject instanceof Boolean ) - { + * Numbers equal to zero are true; Strings equal to "yes" are true; Strings are + * then passed to the Boolean constructor. Other values return null. + */ + static public Boolean getBoolean(Object anObject) { + if (anObject instanceof Boolean) { return (Boolean) anObject; } - if ( anObject instanceof Number ) - { - return new Boolean( ((Number)anObject).doubleValue() == 0.0 ); - } - - if ( anObject instanceof String ) - { - if ( anObject.toString().toLowerCase().equals( "yes" ) ) - { + if (anObject instanceof Number) { + return new Boolean(((Number) anObject).doubleValue() == 0.0); + } + + if (anObject instanceof String) { + if (anObject.toString().toLowerCase().equals("yes")) { return Boolean.TRUE; } - return new Boolean( (String) anObject ); - } - - return null; - } - - /** - * Get an appropriate String representation for the - * object. Nulls are converted to "null". Date are - * formatted according to the current date format. - * All else uses toString. - */ - static public String getString( Object anObject ) - { - if ( anObject == null ) return "null"; - if ( anObject instanceof java.util.Date ) - { - return dateFormat.format( (java.util.Date) anObject ); - } - return anObject.toString(); - } - - /** - * Converts the object into the specified collection class. - * If unable to convert in any other way, resorts to creating - * a new collection of the specified type containing the - * specified object. - */ - static public Collection getCollection( Object anObject, Class aCollectionClass ) - { - if ( anObject == null ) return null; - if ( aCollectionClass.isAssignableFrom( anObject.getClass() ) ) - { - return (Collection) anObject; - } - - Collection converted = null; - - // convert to collection class - if ( anObject instanceof Collection ) - { - converted = (Collection) anObject; - } - else - // try to convert an array - if ( anObject.getClass().isArray() ) - { - try - { - int length = Array.getLength( anObject ); - converted = new LinkedList(); - for ( int i = 0; i < length; i++ ) - { - converted.add( Array.get( anObject, i ) ); - } - } - catch ( Exception exc ) - { - // try another approach - } - } - else - // convert map values to collection and pass through - if ( anObject instanceof Map ) - { - converted = ((Map)anObject).values(); - } - - // fall back on list containing the object - if ( converted == null ) - { - converted = new LinkedList(); - converted.add( anObject ); - } - - Collection result = null; - - if ( converted != null ) - { - try - { - // collections required to have the copy constructor. - Constructor ctor = aCollectionClass.getConstructor( - new Class[] { Collection.class } ); - result = (Collection) ctor.newInstance( new Object[] { converted } ); - } - catch ( Exception exc ) - { - try - { - result = new LinkedList(); - result.addAll( converted ); - } - catch ( Exception exc2 ) - { - // all attempts failed - result = null; - } - } - } - - return result; - } - - /** - * Convert the object to the specified array type. - */ - static public Object getArray( Object anObject, Class anArrayClass ) - { - if ( anObject == null ) return null; - - // try to convert an array - if ( anObject.getClass().isArray() ) - { - try - { - int length = Array.getLength( anObject ); - Object result = Array.newInstance( - anArrayClass.getComponentType(), length ); - for ( int i = 0; i < length; i++ ) - { - Array.set( result, i, Array.get( anObject, i ) ); - } - return result; - } - catch ( Exception exc ) - { - // try another approach - } - } - // convert map values to collection and pass through - if ( anObject instanceof Map ) - { - anObject = ((Map)anObject).values(); - } - // try to convert a collection - if ( anObject instanceof Collection ) - { - try - { - int length = ((Collection)anObject).size(); - Object result = Array.newInstance( - anArrayClass.getComponentType(), length ); - Iterator it = ((Collection)anObject).iterator(); - for ( int i = 0; i < length; i++ ) - { - Array.set( result, i, it.next() ); - } - return result; - } - catch ( Exception exc ) - { - // try another approach - } - } - // if appropriate type, put the object in a single element array - if ( anObject.getClass().equals( anArrayClass.getComponentType() ) ) - { - try - { - Object result = Array.newInstance( - anArrayClass.getComponentType(), 1 ); - Array.set( result, 0, anObject ); - return result; - } - catch ( Exception exc ) - { - // try another approach - } - } - return null; - } - - /** - * Get an appropriate Date from the given object. - */ - static public java.util.Date getDate( Object anObject ) - { - if ( anObject == null ) return new java.util.Date( 0 ); - if ( anObject instanceof java.util.Date ) - return (java.util.Date) anObject; - - if ( anObject instanceof Number ) - { - return new java.util.Date( getLongValue( anObject ) ); - } - - try - { - return dateFormat.parse( anObject.toString() ); - } - catch ( Exception exc ) - { - return null; - } - } - - static private java.text.DateFormat dateFormat = - new java.text.SimpleDateFormat(); - static public java.text.DateFormat getDateFormat() - { - return dateFormat; - } - static public void setDateFormat( java.text.DateFormat aDateFormat ) - { - if ( aDateFormat != null ) - { - dateFormat = aDateFormat; - } - } - - /** - * Returns the "inverted" value of the specified object. - * Numbers except for chars and bytes are converted to - * their negative representation. Chars and bytes are - * treated as booleans. String are converted to booleans. - * Booleans are converted to their opposite. - * All other types return null. - */ - public static Object invert( Object anObject ) - { - if ( anObject == null ) return null; - Class aClass = anObject.getClass(); - - if ( ( ( anObject instanceof Number ) - &&! ( anObject instanceof Byte ) - &&! ( anObject instanceof Character ) ) - || ( aClass == short.class ) - || ( aClass == int.class ) - || ( aClass == long.class ) - || ( aClass == float.class ) - || ( aClass == double.class ) ) - { - return convertObjectToClass( - new Double( getDoubleValue( anObject ) * -1 ), aClass ); - } - - Boolean converted = getBoolean( anObject ); - if ( converted != null ) - { - if ( converted.booleanValue() ) - { - return convertObjectToClass( - Boolean.FALSE, anObject.getClass() ); - } - else - { - return convertObjectToClass( - Boolean.TRUE, anObject.getClass() ); - } - } - - return null; - } + return new Boolean((String) anObject); + } + + return null; + } + + /** + * Get an appropriate String representation for the object. Nulls are converted + * to "null". Date are formatted according to the current date format. All else + * uses toString. + */ + static public String getString(Object anObject) { + if (anObject == null) + return "null"; + if (anObject instanceof java.util.Date) { + return dateFormat.format((java.util.Date) anObject); + } + return anObject.toString(); + } + + /** + * Converts the object into the specified collection class. If unable to convert + * in any other way, resorts to creating a new collection of the specified type + * containing the specified object. + */ + static public Collection getCollection(Object anObject, Class aCollectionClass) { + if (anObject == null) + return null; + if (aCollectionClass.isAssignableFrom(anObject.getClass())) { + return (Collection) anObject; + } + + Collection converted = null; + + // convert to collection class + if (anObject instanceof Collection) { + converted = (Collection) anObject; + } else + // try to convert an array + if (anObject.getClass().isArray()) { + try { + int length = Array.getLength(anObject); + converted = new LinkedList(); + for (int i = 0; i < length; i++) { + converted.add(Array.get(anObject, i)); + } + } catch (Exception exc) { + // try another approach + } + } else + // convert map values to collection and pass through + if (anObject instanceof Map) { + converted = ((Map) anObject).values(); + } + + // fall back on list containing the object + if (converted == null) { + converted = new LinkedList(); + converted.add(anObject); + } + + Collection result = null; + + if (converted != null) { + try { + // collections required to have the copy constructor. + Constructor ctor = aCollectionClass.getConstructor(new Class[] { Collection.class }); + result = (Collection) ctor.newInstance(new Object[] { converted }); + } catch (Exception exc) { + try { + result = new LinkedList(); + result.addAll(converted); + } catch (Exception exc2) { + // all attempts failed + result = null; + } + } + } + + return result; + } + + /** + * Convert the object to the specified array type. + */ + static public Object getArray(Object anObject, Class anArrayClass) { + if (anObject == null) + return null; + + // try to convert an array + if (anObject.getClass().isArray()) { + try { + int length = Array.getLength(anObject); + Object result = Array.newInstance(anArrayClass.getComponentType(), length); + for (int i = 0; i < length; i++) { + Array.set(result, i, Array.get(anObject, i)); + } + return result; + } catch (Exception exc) { + // try another approach + } + } + // convert map values to collection and pass through + if (anObject instanceof Map) { + anObject = ((Map) anObject).values(); + } + // try to convert a collection + if (anObject instanceof Collection) { + try { + int length = ((Collection) anObject).size(); + Object result = Array.newInstance(anArrayClass.getComponentType(), length); + Iterator it = ((Collection) anObject).iterator(); + for (int i = 0; i < length; i++) { + Array.set(result, i, it.next()); + } + return result; + } catch (Exception exc) { + // try another approach + } + } + // if appropriate type, put the object in a single element array + if (anObject.getClass().equals(anArrayClass.getComponentType())) { + try { + Object result = Array.newInstance(anArrayClass.getComponentType(), 1); + Array.set(result, 0, anObject); + return result; + } catch (Exception exc) { + // try another approach + } + } + return null; + } + + /** + * Get an appropriate Date from the given object. + */ + static public java.util.Date getDate(Object anObject) { + if (anObject == null) + return new java.util.Date(0); + if (anObject instanceof java.util.Date) + return (java.util.Date) anObject; + + if (anObject instanceof Number) { + return new java.util.Date(getLongValue(anObject)); + } + + try { + return dateFormat.parse(anObject.toString()); + } catch (Exception exc) { + return null; + } + } + + static private java.text.DateFormat dateFormat = new java.text.SimpleDateFormat(); + + static public java.text.DateFormat getDateFormat() { + return dateFormat; + } + + static public void setDateFormat(java.text.DateFormat aDateFormat) { + if (aDateFormat != null) { + dateFormat = aDateFormat; + } + } + + /** + * Returns the "inverted" value of the specified object. Numbers except for + * chars and bytes are converted to their negative representation. Chars and + * bytes are treated as booleans. String are converted to booleans. Booleans are + * converted to their opposite. All other types return null. + */ + public static Object invert(Object anObject) { + if (anObject == null) + return null; + Class aClass = anObject.getClass(); + + if (((anObject instanceof Number) && !(anObject instanceof Byte) && !(anObject instanceof Character)) + || (aClass == short.class) || (aClass == int.class) || (aClass == long.class) || (aClass == float.class) + || (aClass == double.class)) { + return convertObjectToClass(new Double(getDoubleValue(anObject) * -1), aClass); + } + + Boolean converted = getBoolean(anObject); + if (converted != null) { + if (converted.booleanValue()) { + return convertObjectToClass(Boolean.FALSE, anObject.getClass()); + } else { + return convertObjectToClass(Boolean.TRUE, anObject.getClass()); + } + } + + return null; + } } /* - * $Log$ - * Revision 1.2 2006/02/16 13:11:47 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.2 2006/02/16 13:11:47 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2002/10/11 15:33:53 mpowers - * Now supporting "!" to invert the value of a string property. + * Revision 1.4 2002/10/11 15:33:53 mpowers Now supporting "!" to invert the + * value of a string property. * - * Revision 1.3 2001/07/02 16:29:08 mpowers - * XMLRPC decoder was relying on ValueConverter to convert LinkedLists into - * the appropriate type. This is now implemented in ValueConverter. + * Revision 1.3 2001/07/02 16:29:08 mpowers XMLRPC decoder was relying on + * ValueConverter to convert LinkedLists into the appropriate type. This is now + * implemented in ValueConverter. * - * Revision 1.2 2001/03/01 20:36:35 mpowers - * Better error handling and better handling of nulls. + * Revision 1.2 2001/03/01 20:36:35 mpowers Better error handling and better + * handling of nulls. * - * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers Contributing wotonomy. * - * Revision 1.8 2000/12/20 16:25:48 michael - * Added log to all files. + * Revision 1.8 2000/12/20 16:25:48 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java index e2210d0..21b47b3 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java @@ -22,113 +22,96 @@ import java.io.PrintStream; import java.io.PrintWriter; /** -* This is a simple RuntimeException that can encapsulate -* another throwable. Behaves as a normal RuntimeException -* except that it prints a stack trace of the wrapped -* throwable if one is specified. -* -* Intended to be used anytime you'd -* report an exception with System.out.println: that is, -* for debugging and non-user-visible exceptions. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * This is a simple RuntimeException that can encapsulate another throwable. + * Behaves as a normal RuntimeException except that it prints a stack trace of + * the wrapped throwable if one is specified. + * + * Intended to be used anytime you'd report an exception with + * System.out.println: that is, for debugging and non-user-visible exceptions. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ -public class WotonomyException extends RuntimeException -{ +public class WotonomyException extends RuntimeException { protected String message; protected Throwable wrappedThrowable; - + /** - * Default constructor. - */ - public WotonomyException() - { + * Default constructor. + */ + public WotonomyException() { super(); message = null; - wrappedThrowable = null; + wrappedThrowable = null; } - + /** - * Standard constructor with message. - */ - public WotonomyException( String aMessage ) - { - super( aMessage ); + * Standard constructor with message. + */ + public WotonomyException(String aMessage) { + super(aMessage); message = aMessage; wrappedThrowable = null; } - + /** - * Specifies a throwable to wrap. - */ - public WotonomyException( Throwable aThrowable ) - { + * Specifies a throwable to wrap. + */ + public WotonomyException(Throwable aThrowable) { super(); message = null; wrappedThrowable = aThrowable; } - + /** - * Specifies a message and a throwable to wrap. - */ - public WotonomyException( String aMessage, Throwable aThrowable ) - { - super( aMessage ); + * Specifies a message and a throwable to wrap. + */ + public WotonomyException(String aMessage, Throwable aThrowable) { + super(aMessage); message = aMessage; wrappedThrowable = aThrowable; } - - /** - * Returns the wrapped throwable. - */ - public Throwable getWrappedThrowable() - { - return wrappedThrowable; - } - - public void printStackTrace(PrintWriter s) - { - if ( message != null ) - { - s.println( "Exception: " + message ); + + /** + * Returns the wrapped throwable. + */ + public Throwable getWrappedThrowable() { + return wrappedThrowable; + } + + public void printStackTrace(PrintWriter s) { + if (message != null) { + s.println("Exception: " + message); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace( s ); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(s); return; } - super.printStackTrace( s ); + super.printStackTrace(s); } - - public void printStackTrace(PrintStream s) - { - if ( message != null ) - { - s.println( "Exception: " + message ); + + public void printStackTrace(PrintStream s) { + if (message != null) { + s.println("Exception: " + message); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace( s ); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(s); return; } - super.printStackTrace( s ); + super.printStackTrace(s); } - - public void printStackTrace() - { - if ( message != null ) - { - System.err.println( "Exception: " + message ); + + public void printStackTrace() { + if (message != null) { + System.err.println("Exception: " + message); } - if ( wrappedThrowable != null ) - { - wrappedThrowable.printStackTrace(); + if (wrappedThrowable != null) { + wrappedThrowable.printStackTrace(); return; } super.printStackTrace(); } - + } diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java index 64ac4b2..79b3967 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java @@ -22,47 +22,42 @@ import java.io.InputStream; import java.net.URL; /** -* Defines an interface for classes that can de-serialize -* java objects from an XML representation. -*/ -public interface XMLDecoder -{ - /** - * Decodes an object from the specified input stream. - * @param anInputStream The input stream from which to read. - * The stream will be read fully. - * @param aDescription A description to accompany error messages - * for the stream, typically a file name. - * @param aURL A URL against which relative references within the - * XML will be resolved. - * @return The object that was constructed from the XML content, - * or null if no object could be constructed. - */ - public Object decode( - InputStream anInputStream, String aDescription, URL aURL ); + * Defines an interface for classes that can de-serialize java objects from an + * XML representation. + */ +public interface XMLDecoder { + /** + * Decodes an object from the specified input stream. + * + * @param anInputStream The input stream from which to read. The stream will be + * read fully. + * @param aDescription A description to accompany error messages for the + * stream, typically a file name. + * @param aURL A URL against which relative references within the XML + * will be resolved. + * @return The object that was constructed from the XML content, or null if no + * object could be constructed. + */ + public Object decode(InputStream anInputStream, String aDescription, URL aURL); } /* - * $Log$ - * Revision 1.1 2006/02/18 22:21:10 cgruber - * Add in simple xml interfaces from net.wotonomy.xml project. - * Add cobertura.ser to .cvsignore. God I wish sourceforge would move on this subversion thing... + * $Log$ Revision 1.1 2006/02/18 22:21:10 cgruber Add in simple xml interfaces + * from net.wotonomy.xml project. Add cobertura.ser to .cvsignore. God I wish + * sourceforge would move on this subversion thing... * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/06 14:34:23 mpowers - * Forgot to rename the package declarations. + * Revision 1.2 2001/02/06 14:34:23 mpowers Forgot to rename the package + * declarations. * - * Revision 1.1 2001/02/06 14:31:19 mpowers - * Moving XML utilities from util to xml package. + * Revision 1.1 2001/02/06 14:31:19 mpowers Moving XML utilities from util to + * xml package. * - * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:48 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:48 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java index e543bca..0d180aa 100644 --- a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java @@ -21,41 +21,37 @@ package net.wotonomy.foundation.xml; import java.io.OutputStream; /** -* Defines an interface for classes that can serialize -* an arbitrary java object into XML format. -*/ -public interface XMLEncoder -{ - /** - * Encodes an object to the specified output stream as XML. - * @param anObject The object to be serialized to XML format. - * @param anOutputStream The output stream to which the object - * will be written. The stream will not be flushed nor closed. - */ - public void encode( Object anObject, OutputStream anOutputStream ); + * Defines an interface for classes that can serialize an arbitrary java object + * into XML format. + */ +public interface XMLEncoder { + /** + * Encodes an object to the specified output stream as XML. + * + * @param anObject The object to be serialized to XML format. + * @param anOutputStream The output stream to which the object will be written. + * The stream will not be flushed nor closed. + */ + public void encode(Object anObject, OutputStream anOutputStream); } /* - * $Log$ - * Revision 1.1 2006/02/18 22:21:10 cgruber - * Add in simple xml interfaces from net.wotonomy.xml project. - * Add cobertura.ser to .cvsignore. God I wish sourceforge would move on this subversion thing... + * $Log$ Revision 1.1 2006/02/18 22:21:10 cgruber Add in simple xml interfaces + * from net.wotonomy.xml project. Add cobertura.ser to .cvsignore. God I wish + * sourceforge would move on this subversion thing... * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/06 14:34:23 mpowers - * Forgot to rename the package declarations. + * Revision 1.2 2001/02/06 14:34:23 mpowers Forgot to rename the package + * declarations. * - * Revision 1.1 2001/02/06 14:31:19 mpowers - * Moving XML utilities from util to xml package. + * Revision 1.1 2001/02/06 14:31:19 mpowers Moving XML utilities from util to + * xml package. * - * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:48 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:48 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.foundation/src/test/java/AllTests.java b/projects/net.wotonomy.foundation/src/test/java/AllTests.java index 1b36871..906dfac 100644 --- a/projects/net.wotonomy.foundation/src/test/java/AllTests.java +++ b/projects/net.wotonomy.foundation/src/test/java/AllTests.java @@ -8,9 +8,9 @@ public class AllTests { public static Test suite() { TestSuite suite = new TestSuite("Test for default package"); - //$JUnit-BEGIN$ + // $JUnit-BEGIN$ suite.addTestSuite(net.wotonomy.foundation.AllTests.class); - //$JUnit-END$ + // $JUnit-END$ return suite; } diff --git a/projects/net.wotonomy.foundation/src/test/java/TestBundle.java b/projects/net.wotonomy.foundation/src/test/java/TestBundle.java index 7fb5020..9c0b1c7 100644 --- a/projects/net.wotonomy.foundation/src/test/java/TestBundle.java +++ b/projects/net.wotonomy.foundation/src/test/java/TestBundle.java @@ -5,17 +5,19 @@ import java.util.List; import java.util.Properties; public class TestBundle { - public String toString() { return "This is a test"; } - + public String toString() { + return "This is a test"; + } + public static void main(String[] argv) { Properties p = System.getProperties(); List keyList = new ArrayList(p.keySet()); Collections.sort(keyList); Iterator keys = keyList.iterator(); while (keys.hasNext()) { - String key = (String)keys.next(); + String key = (String) keys.next(); System.out.println(key + "=" + p.getProperty(key)); } - + } } diff --git a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/AllTests.java b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/AllTests.java index 920b0a7..7a2e664 100644 --- a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/AllTests.java +++ b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/AllTests.java @@ -7,10 +7,10 @@ public class AllTests { public static Test suite() { TestSuite suite = new TestSuite("Test for net.wotonomy.foundation"); - //$JUnit-BEGIN$ + // $JUnit-BEGIN$ suite.addTestSuite(NSArrayTest.class); suite.addTestSuite(NSBundleTest.class); - //$JUnit-END$ + // $JUnit-END$ return suite; } diff --git a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSArrayTest.java b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSArrayTest.java index 8873f1a..e8bdbbc 100644 --- a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSArrayTest.java +++ b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSArrayTest.java @@ -11,9 +11,8 @@ import junit.framework.TestCase; public class NSArrayTest extends TestCase { - - Object o1, o2, o3, o4, o5; - + Object o1, o2, o3, o4, o5; + public void setUp() throws Exception { o1 = "o1"; o2 = new Integer(2); @@ -21,7 +20,7 @@ public class NSArrayTest extends TestCase { o4 = "o4"; super.setUp(); } - + public void tearDown() throws Exception { o1 = null; o2 = null; @@ -29,17 +28,17 @@ public class NSArrayTest extends TestCase { o4 = null; super.tearDown(); } - + /* * Test method for 'net.wotonomy.foundation.NSArray.hashCode()' */ public void testHashCode() { NSArray array1 = new NSArray(o1); - NSArray array2 = new NSArray(o1); //same content, same hashcode. + NSArray array2 = new NSArray(o1); // same content, same hashcode. NSArray array3 = new NSArray("Different"); - assertNotSame(array1,array2); - assertEquals(array1.hashCode(),array2.hashCode()); - assertFalse("Should have different hashcodes",array1.hashCode() == array3.hashCode()); + assertNotSame(array1, array2); + assertEquals(array1.hashCode(), array2.hashCode()); + assertFalse("Should have different hashcodes", array1.hashCode() == array3.hashCode()); } /* @@ -49,7 +48,7 @@ public class NSArrayTest extends TestCase { List list = new ArrayList(); NSArray array = NSArray.arrayBackedByList(list); assertNotNull(array); - assertSame(list,array.list); + assertSame(list, array.list); } /* @@ -57,7 +56,7 @@ public class NSArrayTest extends TestCase { */ public void testEmptyList() { assertNotNull(NSArray.EmptyArray); - assertEquals(0,NSArray.EmptyArray.count()); + assertEquals(0, NSArray.EmptyArray.count()); } /* @@ -67,7 +66,7 @@ public class NSArrayTest extends TestCase { List list = new ArrayList(); NSArray array = new NSArray(list, null); assertNotNull(array); - assertSame(list,array.list); + assertSame(list, array.list); } /* @@ -81,7 +80,8 @@ public class NSArrayTest extends TestCase { try { array = new NSArray(-1); fail("Failed to catch IllegalArgumentException."); - } catch (IllegalArgumentException e) {} + } catch (IllegalArgumentException e) { + } assertNotNull(array); array = new NSArray(1000); assertNotNull(array); @@ -101,29 +101,29 @@ public class NSArrayTest extends TestCase { public void testNSArrayObject() { NSArray array = new NSArray(o1); assertNotNull(array); - assertEquals(1,array.count()); - assertEquals(o1,array.get(0)); + assertEquals(1, array.count()); + assertEquals(o1, array.get(0)); } /* * Test method for 'net.wotonomy.foundation.NSArray.NSArray(Object)' */ public void testNSArrayObjectWithNull() { - NSArray array = new NSArray((Object)null); + NSArray array = new NSArray((Object) null); assertNotNull(array); - assertEquals(1,array.count()); - assertEquals(null,array.get(0)); + assertEquals(1, array.count()); + assertEquals(null, array.get(0)); } - + /* * Test method for 'net.wotonomy.foundation.NSArray.NSArray(Object[])' */ public void testNSArrayObjectArray() { Object[] objects = { o1, o2, o3 }; NSArray array = new NSArray(objects); - assertEquals(3,array.count()); - for (int i = 0; i < objects.length ; i++) { - assertEquals(objects[i],array.get(i)); + assertEquals(3, array.count()); + for (int i = 0; i < objects.length; i++) { + assertEquals(objects[i], array.get(i)); } } @@ -137,29 +137,30 @@ public class NSArrayTest extends TestCase { list.add(o3); NSArray array = new NSArray(list); assertNotNull(array); - assertNotSame(list,array.list); - assertEquals(3,array.count()); + assertNotSame(list, array.list); + assertEquals(3, array.count()); // the following only works because we used ArrayList. // A collection that doesn't guarrantee order may not // pass this test. - for (int i = 0; i < list.size() ; i++) { - assertEquals(list.get(i),array.get(i)); + for (int i = 0; i < list.size(); i++) { + assertEquals(list.get(i), array.get(i)); } } - + /* * Test method for 'net.wotonomy.foundation.NSArray.arrayByAddingObject(Object)' */ public void testArrayByAddingObject() { NSArray array = new NSArray(o1); NSArray array2 = array.arrayByAddingObject(o2); - assertEquals(2,array2.count()); - assertEquals(o1,array2.get(0)); - assertEquals(o2,array2.get(1)); + assertEquals(2, array2.count()); + assertEquals(o1, array2.get(0)); + assertEquals(o2, array2.get(1)); } /* - * Test method for 'net.wotonomy.foundation.NSArray.arrayByAddingObjectsFromArray(Collection)' + * Test method for + * 'net.wotonomy.foundation.NSArray.arrayByAddingObjectsFromArray(Collection)' */ public void testArrayByAddingObjectsFromArray() { NSArray array = new NSArray(o1); @@ -167,14 +168,15 @@ public class NSArrayTest extends TestCase { list.add(o2); list.add(o3); NSArray array2 = array.arrayByAddingObjectsFromArray(list); - assertEquals(3,array2.count()); - assertEquals(o1,array2.get(0)); - assertEquals(o2,array2.get(1)); - assertEquals(o3,array2.get(2)); + assertEquals(3, array2.count()); + assertEquals(o1, array2.get(0)); + assertEquals(o2, array2.get(1)); + assertEquals(o3, array2.get(2)); } /* - * Test method for 'net.wotonomy.foundation.NSArray.componentsJoinedByString(String)' + * Test method for + * 'net.wotonomy.foundation.NSArray.componentsJoinedByString(String)' */ public void testComponentsJoinedByString() { Object[] objects = { o1, o2, o3 }; @@ -192,15 +194,16 @@ public class NSArrayTest extends TestCase { } /* - * Test method for 'net.wotonomy.foundation.NSArray.firstObjectCommonWithArray(Collection)' + * Test method for + * 'net.wotonomy.foundation.NSArray.firstObjectCommonWithArray(Collection)' */ - public void testFirstObjectCommonWithArray() { + public void testFirstObjectCommonWithArray() { ArrayList list = new ArrayList(); list.add(o2); list.add(o3); list.add(o4); - NSArray array = new NSArray(new Object[]{o1, o3, o4}); - assertEquals(o3,array.firstObjectCommonWithArray(list)); + NSArray array = new NSArray(new Object[] { o1, o3, o4 }); + assertEquals(o3, array.firstObjectCommonWithArray(list)); } /* @@ -208,61 +211,61 @@ public class NSArrayTest extends TestCase { */ public void testEqualsAndIsEqualToArray() { NSArray array1 = new NSArray(o1); - NSArray array2 = new NSArray(o1); //same content, same hashcode. + NSArray array2 = new NSArray(o1); // same content, same hashcode. NSArray array3 = new NSArray(o2); - assertNotSame(array1,array2); - assertTrue("Should be equal",array1.equals(array2)); - assertTrue("Should be equal",array1.isEqualToArray(array2)); - assertFalse("Should be unequal",array1.equals(array3)); - assertFalse("Should be unequal",array1.isEqualToArray(array3)); + assertNotSame(array1, array2); + assertTrue("Should be equal", array1.equals(array2)); + assertTrue("Should be equal", array1.isEqualToArray(array2)); + assertFalse("Should be unequal", array1.equals(array3)); + assertFalse("Should be unequal", array1.isEqualToArray(array3)); } /* * Test method for 'net.wotonomy.foundation.NSArray.lastObject()' */ - public void testLastObject() { - NSArray array = new NSArray(new Object[]{o1, o2}); - assertEquals(o2,array.lastObject()); + public void testLastObject() { + NSArray array = new NSArray(new Object[] { o1, o2 }); + assertEquals(o2, array.lastObject()); // test for empty array - assertEquals(null,NSArray.EmptyArray.lastObject()); + assertEquals(null, NSArray.EmptyArray.lastObject()); } /* * Test method for 'net.wotonomy.foundation.NSArray.subarrayWithRange(NSRange)' * TODO: Add ranges that exceed the size of the array. */ - public void testSubarrayWithRange() { - NSArray array = new NSArray(new Object[]{o1, o2, o3, o4}); - NSArray subarray = array.subarrayWithRange(new NSRange(1,2)); - assertEquals(2,subarray.count()); - assertEquals(o2,subarray.get(0)); - assertEquals(o3,subarray.get(1)); + public void testSubarrayWithRange() { + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + NSArray subarray = array.subarrayWithRange(new NSRange(1, 2)); + assertEquals(2, subarray.count()); + assertEquals(o2, subarray.get(0)); + assertEquals(o3, subarray.get(1)); } /* * Test method for 'net.wotonomy.foundation.NSArray.objectEnumerator()' */ public void testObjectEnumerator() { - NSArray array = new NSArray(new Object[]{o1, o2, o3, o4}); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); Enumeration e = array.objectEnumerator(); assertTrue(e.hasMoreElements()); - assertEquals(o1,e.nextElement()); - assertEquals(o2,e.nextElement()); - assertEquals(o3,e.nextElement()); - assertEquals(o4,e.nextElement()); + assertEquals(o1, e.nextElement()); + assertEquals(o2, e.nextElement()); + assertEquals(o3, e.nextElement()); + assertEquals(o4, e.nextElement()); } /* * Test method for 'net.wotonomy.foundation.NSArray.reverseObjectEnumerator()' */ public void testReverseObjectEnumerator() { - NSArray array = new NSArray(new Object[]{o1, o2, o3, o4}); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); Enumeration e = array.reverseObjectEnumerator(); assertTrue(e.hasMoreElements()); - assertEquals(o4,e.nextElement()); - assertEquals(o3,e.nextElement()); - assertEquals(o2,e.nextElement()); - assertEquals(o1,e.nextElement()); + assertEquals(o4, e.nextElement()); + assertEquals(o3, e.nextElement()); + assertEquals(o2, e.nextElement()); + assertEquals(o1, e.nextElement()); } /* @@ -270,369 +273,359 @@ public class NSArrayTest extends TestCase { */ public void testGetObjectsObjectArray() { Object[] oa = new Object[4]; - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); array.getObjects(oa); - assertEquals(o1,oa[0]); - assertEquals(o2,oa[1]); - assertEquals(o3,oa[2]); - assertEquals(o4,oa[3]); - assertEquals(4,oa.length); + assertEquals(o1, oa[0]); + assertEquals(o2, oa[1]); + assertEquals(o3, oa[2]); + assertEquals(o4, oa[3]); + assertEquals(4, oa.length); } - + public void testGetObjectsObjectArrayWithSmallArray() { Object[] oa = new Object[2]; - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); array.getObjects(oa); - assertEquals(o1,oa[0]); - assertEquals(o2,oa[1]); - assertEquals(2,oa.length); + assertEquals(o1, oa[0]); + assertEquals(o2, oa[1]); + assertEquals(2, oa.length); } - + public void testGetObjectsObjectArrayWithLargeArray() { Object[] oa = new Object[5]; - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); array.getObjects(oa); - assertEquals(o1,oa[0]); - assertEquals(o2,oa[1]); - assertEquals(o3,oa[2]); - assertEquals(o4,oa[3]); - assertEquals(null,oa[4]); - assertEquals(5,oa.length); + assertEquals(o1, oa[0]); + assertEquals(o2, oa[1]); + assertEquals(o3, oa[2]); + assertEquals(o4, oa[3]); + assertEquals(null, oa[4]); + assertEquals(5, oa.length); } + /* - * Test method for 'net.wotonomy.foundation.NSArray.getObjects(Object[], NSRange)' - * TODO: Try more ranges. + * Test method for 'net.wotonomy.foundation.NSArray.getObjects(Object[], + * NSRange)' TODO: Try more ranges. */ public void testGetObjectsObjectArrayNSRange() { Object[] oa = new Object[2]; - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - array.getObjects(oa, new NSRange(1,2)); - assertEquals(o2,oa[0]); - assertEquals(o3,oa[1]); - assertEquals(2,oa.length); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + array.getObjects(oa, new NSRange(1, 2)); + assertEquals(o2, oa[0]); + assertEquals(o3, oa[1]); + assertEquals(2, oa.length); } public void testGetObjectsObjectArrayNSRangeWithLargeRange() { Object[] oa = new Object[4]; - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - array.getObjects(oa, new NSRange(1,90)); - assertEquals(o2,oa[0]); - assertEquals(o3,oa[1]); - assertEquals(o4,oa[2]); - assertEquals(null,oa[3]); - assertEquals(4,oa.length); - } - - + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + array.getObjects(oa, new NSRange(1, 90)); + assertEquals(o2, oa[0]); + assertEquals(o3, oa[1]); + assertEquals(o4, oa[2]); + assertEquals(null, oa[3]); + assertEquals(4, oa.length); + } + /* * Test method for 'net.wotonomy.foundation.NSArray.indexOfObject(Object)' */ public void testIndexOfObjectObject() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - assertEquals(0,array.indexOfObject(o1)); - assertEquals(1,array.indexOfObject(o2)); - assertEquals(2,array.indexOfObject(o3)); - assertEquals(3,array.indexOfObject(o4)); - assertEquals(-1,array.indexOfObject("No Such Object")); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + assertEquals(0, array.indexOfObject(o1)); + assertEquals(1, array.indexOfObject(o2)); + assertEquals(2, array.indexOfObject(o3)); + assertEquals(3, array.indexOfObject(o4)); + assertEquals(-1, array.indexOfObject("No Such Object")); } /* - * Test method for 'net.wotonomy.foundation.NSArray.indexOfObject(Object, NSRange)' + * Test method for 'net.wotonomy.foundation.NSArray.indexOfObject(Object, + * NSRange)' */ public void testIndexOfObjectObjectNSRange() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - NSRange range = new NSRange(1,2); - assertEquals(-1,array.indexOfObject(o1,range)); - assertEquals(1,array.indexOfObject(o2,range)); - + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + NSRange range = new NSRange(1, 2); + assertEquals(-1, array.indexOfObject(o1, range)); + assertEquals(1, array.indexOfObject(o2, range)); + // should be -1 because o3 == null; - assertEquals(-1,array.indexOfObject(o3,range)); - assertEquals(-1,array.indexOfObject(o4,range)); - assertEquals(-1,array.indexOfObject("No Such Object")); + assertEquals(-1, array.indexOfObject(o3, range)); + assertEquals(-1, array.indexOfObject(o4, range)); + assertEquals(-1, array.indexOfObject("No Such Object")); } - public void testIndexOfIdenticalObjectObject() { - Integer i1 = new Integer(3-1); - Integer i2 = new Integer(4/2); - NSArray array = new NSArray(new Object[] { o1,i2,o3,o4 }); - assertEquals(1,array.indexOfObject(i1)); + Integer i1 = new Integer(3 - 1); + Integer i2 = new Integer(4 / 2); + NSArray array = new NSArray(new Object[] { o1, i2, o3, o4 }); + assertEquals(1, array.indexOfObject(i1)); assertFalse(1 == array.indexOfIdenticalObject(i1)); assertEquals(1, array.indexOfIdenticalObject(i2)); } public void testIndexOfIdenticalObjectObjectNSRange() { - Integer i1 = new Integer(3-1); - Integer i2 = new Integer(4/2); - NSRange range = new NSRange(1,2); - NSArray array = new NSArray(new Object[] { o1,i2,o3,o4 }); - assertEquals(1,array.indexOfObject(i1,range)); - assertFalse(1 == array.indexOfIdenticalObject(i1,range)); - assertEquals(1, array.indexOfIdenticalObject(i2,range)); - NSRange range2 = new NSRange(3,2); - assertEquals(-1, array.indexOfObject(i1,range2)); - assertEquals(-1, array.indexOfIdenticalObject(i1,range2)); - assertEquals(-1, array.indexOfIdenticalObject(i2,range2)); + Integer i1 = new Integer(3 - 1); + Integer i2 = new Integer(4 / 2); + NSRange range = new NSRange(1, 2); + NSArray array = new NSArray(new Object[] { o1, i2, o3, o4 }); + assertEquals(1, array.indexOfObject(i1, range)); + assertFalse(1 == array.indexOfIdenticalObject(i1, range)); + assertEquals(1, array.indexOfIdenticalObject(i2, range)); + NSRange range2 = new NSRange(3, 2); + assertEquals(-1, array.indexOfObject(i1, range2)); + assertEquals(-1, array.indexOfIdenticalObject(i1, range2)); + assertEquals(-1, array.indexOfIdenticalObject(i2, range2)); } public void testObjectAtIndex() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - assertEquals(o1,array.objectAtIndex(0)); - assertEquals(o2,array.objectAtIndex(1)); - assertEquals(o3,array.objectAtIndex(2)); - assertEquals(o4,array.objectAtIndex(3)); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + assertEquals(o1, array.objectAtIndex(0)); + assertEquals(o2, array.objectAtIndex(1)); + assertEquals(o3, array.objectAtIndex(2)); + assertEquals(o4, array.objectAtIndex(3)); try { - assertEquals(null,array.objectAtIndex(4)); + assertEquals(null, array.objectAtIndex(4)); fail("Should have thrown index out of bounds."); - } catch (IndexOutOfBoundsException e) {} + } catch (IndexOutOfBoundsException e) { + } } public void testComponentsSeparatedByString() { String arrayString = "word0 word1 word2 word3"; - NSArray array = NSArray.componentsSeparatedByString(arrayString," "); - assertEquals("word0",array.objectAtIndex(0)); - assertEquals("word1",array.objectAtIndex(1)); - assertEquals("word2",array.objectAtIndex(2)); - assertEquals("word3",array.objectAtIndex(3)); - } - -/* - public void testClone() { - fail("Not yet implemented"); - - } - - public void testImmutableClone() { - fail("Not yet implemented"); - + NSArray array = NSArray.componentsSeparatedByString(arrayString, " "); + assertEquals("word0", array.objectAtIndex(0)); + assertEquals("word1", array.objectAtIndex(1)); + assertEquals("word2", array.objectAtIndex(2)); + assertEquals("word3", array.objectAtIndex(3)); } - public void testMutableClone() { - fail("Not yet implemented"); + /* + * public void testClone() { fail("Not yet implemented"); + * + * } + * + * public void testImmutableClone() { fail("Not yet implemented"); + * + * } + * + * public void testMutableClone() { fail("Not yet implemented"); + * + * } + */ - } -*/ - public void testToString() { - NSArray array = new NSArray(new Object[] { "o1",new Integer(1),"o3" }); - assertEquals("(o1, 1, o3)",array.toString()); + NSArray array = new NSArray(new Object[] { "o1", new Integer(1), "o3" }); + assertEquals("(o1, 1, o3)", array.toString()); } - - public void testContains() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); - assertTrue("Should contain object",array.contains(o1)); - assertTrue("Should contain object",array.contains(o2)); - assertFalse("Should not contain object",array.contains(o3)); - assertTrue("Should contain object",array.contains(o4)); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); + assertTrue("Should contain object", array.contains(o1)); + assertTrue("Should contain object", array.contains(o2)); + assertFalse("Should not contain object", array.contains(o3)); + assertTrue("Should contain object", array.contains(o4)); } + public void testContainsAll() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); ArrayList list = new ArrayList(); list.add(o1); list.add(o2); - assertTrue("Should have all elements of provided list.",array.containsAll(list)); + assertTrue("Should have all elements of provided list.", array.containsAll(list)); ArrayList list2 = new ArrayList(); list2.add(o2); list2.add(o3); - assertFalse("Should not have all elements of provided list.",array.containsAll(list2)); + assertFalse("Should not have all elements of provided list.", array.containsAll(list2)); } public void testGet() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); - assertEquals(o1,array.get(0)); - assertEquals(o2,array.get(1)); - assertEquals(o4,array.get(2)); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); + assertEquals(o1, array.get(0)); + assertEquals(o2, array.get(1)); + assertEquals(o4, array.get(2)); try { array.get(3); - } catch (IndexOutOfBoundsException e) {} + } catch (IndexOutOfBoundsException e) { + } } public void testIndexOf() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); - assertEquals(0,array.indexOf(o1)); - assertEquals(1,array.indexOf(o2)); - assertEquals(-1,array.indexOf(o3)); - assertEquals(2,array.indexOf(o4)); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); + assertEquals(0, array.indexOf(o1)); + assertEquals(1, array.indexOf(o2)); + assertEquals(-1, array.indexOf(o3)); + assertEquals(2, array.indexOf(o4)); } - + public void testIsEmpty() { - assertFalse(new NSArray(new Object[] { o1,o2,o4 } ).isEmpty()); - assertTrue(new NSArray(new Object[] {} ).isEmpty()); + assertFalse(new NSArray(new Object[] { o1, o2, o4 }).isEmpty()); + assertTrue(new NSArray(new Object[] {}).isEmpty()); } public void testLastIndexOf() { - NSArray array = new NSArray(new Object[] { o1,o4,o2,o4 }); - assertEquals(0,array.lastIndexOf(o1)); - assertEquals(2,array.lastIndexOf(o2)); - assertEquals(-1,array.lastIndexOf(o3)); - assertEquals(3,array.lastIndexOf(o4)); + NSArray array = new NSArray(new Object[] { o1, o4, o2, o4 }); + assertEquals(0, array.lastIndexOf(o1)); + assertEquals(2, array.lastIndexOf(o2)); + assertEquals(-1, array.lastIndexOf(o3)); + assertEquals(3, array.lastIndexOf(o4)); } public void testSize() { - assertEquals(3,new NSArray(new Object[] { o1,o2,o4 }).size()); - assertEquals(1,new NSArray(new Object[] { o1 }).size()); - assertEquals(0,new NSArray(new Object[] { }).size()); + assertEquals(3, new NSArray(new Object[] { o1, o2, o4 }).size()); + assertEquals(1, new NSArray(new Object[] { o1 }).size()); + assertEquals(0, new NSArray(new Object[] {}).size()); } public void testToArray() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); Object[] oarray = array.toArray(); - assertEquals(oarray[0],o1); - assertEquals(oarray[1],o2); - assertEquals(oarray[2],o4); + assertEquals(oarray[0], o1); + assertEquals(oarray[1], o2); + assertEquals(oarray[2], o4); } public void testToArrayObjectArray() { - NSArray array = new NSArray(new Object[] { o1,o2,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o4 }); Object[] oa0 = new Object[3]; Object[] oa1 = array.toArray(oa0); - assertSame(oa0,oa1); - assertEquals(oa1[0],o1); - assertEquals(oa1[1],o2); - assertEquals(oa1[2],o4); + assertSame(oa0, oa1); + assertEquals(oa1[0], o1); + assertEquals(oa1[1], o2); + assertEquals(oa1[2], o4); } public void testAddIntObject() { - NSArray array = new NSArray(new Object[] { o1,o3,o4 }); - array.add(1,o2); - assertEquals(0,array.indexOfObject(o1)); - assertEquals(1,array.indexOfObject(o2)); - assertEquals(2,array.indexOfObject(o3)); - assertEquals(3,array.indexOfObject(o4)); + NSArray array = new NSArray(new Object[] { o1, o3, o4 }); + array.add(1, o2); + assertEquals(0, array.indexOfObject(o1)); + assertEquals(1, array.indexOfObject(o2)); + assertEquals(2, array.indexOfObject(o3)); + assertEquals(3, array.indexOfObject(o4)); } public void testAddObject() { - NSArray array = new NSArray(new Object[] { o1,o2,o3 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3 }); array.add(o4); - assertEquals(0,array.indexOfObject(o1)); - assertEquals(1,array.indexOfObject(o2)); - assertEquals(2,array.indexOfObject(o3)); - assertEquals(3,array.indexOfObject(o4)); + assertEquals(0, array.indexOfObject(o1)); + assertEquals(1, array.indexOfObject(o2)); + assertEquals(2, array.indexOfObject(o3)); + assertEquals(3, array.indexOfObject(o4)); } public void testAddAllCollection() { - NSArray array = new NSArray(new Object[] { o1,o2 }); + NSArray array = new NSArray(new Object[] { o1, o2 }); ArrayList list = new ArrayList(); list.add(o3); list.add(o4); array.addAll(list); - assertEquals(0,array.indexOfObject(o1)); - assertEquals(1,array.indexOfObject(o2)); - assertEquals(2,array.indexOfObject(o3)); - assertEquals(3,array.indexOfObject(o4)); + assertEquals(0, array.indexOfObject(o1)); + assertEquals(1, array.indexOfObject(o2)); + assertEquals(2, array.indexOfObject(o3)); + assertEquals(3, array.indexOfObject(o4)); } - public void testAddAllIntCollection() { - NSArray array = new NSArray(new Object[] { o1,o4 }); + NSArray array = new NSArray(new Object[] { o1, o4 }); ArrayList list = new ArrayList(); list.add(o2); list.add(o3); - array.addAll(1,list); - assertEquals(0,array.indexOfObject(o1)); - assertEquals(1,array.indexOfObject(o2)); - assertEquals(2,array.indexOfObject(o3)); - assertEquals(3,array.indexOfObject(o4)); + array.addAll(1, list); + assertEquals(0, array.indexOfObject(o1)); + assertEquals(1, array.indexOfObject(o2)); + assertEquals(2, array.indexOfObject(o3)); + assertEquals(3, array.indexOfObject(o4)); } - public void testClear() { - NSArray array = new NSArray(new Object[] { o1,o2 }); + NSArray array = new NSArray(new Object[] { o1, o2 }); array.clear(); - assertEquals(0,array.size()); + assertEquals(0, array.size()); } public void testIterator() { - NSArray array = new NSArray(new Object[] { o1,o4 }); + NSArray array = new NSArray(new Object[] { o1, o4 }); Iterator i = array.iterator(); - assertEquals(o1,i.next()); - assertEquals(o4,i.next()); + assertEquals(o1, i.next()); + assertEquals(o4, i.next()); assertFalse(i.hasNext()); } public void testListIterator() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); ListIterator i = array.listIterator(); - assertEquals(o1,i.next()); - assertEquals(o2,i.next()); - assertEquals(o3,i.next()); - assertEquals(o4,i.next()); - assertEquals(o4,i.previous()); - assertEquals(o3,i.previous()); - assertEquals(o3,i.next()); - assertEquals(o4,i.next()); + assertEquals(o1, i.next()); + assertEquals(o2, i.next()); + assertEquals(o3, i.next()); + assertEquals(o4, i.next()); + assertEquals(o4, i.previous()); + assertEquals(o3, i.previous()); + assertEquals(o3, i.next()); + assertEquals(o4, i.next()); assertFalse(i.hasNext()); } public void testListIteratorInt() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); ListIterator i = array.listIterator(1); - assertEquals(o2,i.next()); - assertEquals(o3,i.next()); - assertEquals(o4,i.next()); - assertEquals(o4,i.previous()); - assertEquals(o3,i.previous()); - assertEquals(o3,i.next()); - assertEquals(o4,i.next()); + assertEquals(o2, i.next()); + assertEquals(o3, i.next()); + assertEquals(o4, i.next()); + assertEquals(o4, i.previous()); + assertEquals(o3, i.previous()); + assertEquals(o3, i.next()); + assertEquals(o4, i.next()); assertFalse(i.hasNext()); } public void testRemoveInt() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); - assertEquals(o2,array.remove(1)); - assertEquals(o3,array.remove(1)); - assertEquals(o1,array.objectAtIndex(0)); - assertEquals(o4,array.objectAtIndex(1)); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); + assertEquals(o2, array.remove(1)); + assertEquals(o3, array.remove(1)); + assertEquals(o1, array.objectAtIndex(0)); + assertEquals(o4, array.objectAtIndex(1)); } public void testRemoveObject() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); assertTrue(array.remove(o2)); assertTrue(array.remove(o3)); assertFalse(array.remove("blah")); - assertEquals(o1,array.objectAtIndex(0)); - assertEquals(o4,array.objectAtIndex(1)); + assertEquals(o1, array.objectAtIndex(0)); + assertEquals(o4, array.objectAtIndex(1)); } public void testRemoveAll() { - NSArray array = new NSArray(new Object[] { o1,o2,o3,o4 }); + NSArray array = new NSArray(new Object[] { o1, o2, o3, o4 }); ArrayList list = new ArrayList(); list.add(o1); - list.add(o2); + list.add(o2); array.removeAll(list); - assertEquals(2,array.size()); - assertEquals(o3,array.objectAtIndex(0)); - assertEquals(o4,array.objectAtIndex(1)); + assertEquals(2, array.size()); + assertEquals(o3, array.objectAtIndex(0)); + assertEquals(o4, array.objectAtIndex(1)); } /* - public void testRetainAll() { - fail("Not yet implemented"); - - } - - public void testSet() { - fail("Not yet implemented"); - - } - - public void testSubList() { - fail("Not yet implemented"); - - } - - public void testProtectedAdd() { - fail("Not yet implemented"); - - } - - public void testProtectedAddAll() { - fail("Not yet implemented"); - - } -*/ - + * public void testRetainAll() { fail("Not yet implemented"); + * + * } + * + * public void testSet() { fail("Not yet implemented"); + * + * } + * + * public void testSubList() { fail("Not yet implemented"); + * + * } + * + * public void testProtectedAdd() { fail("Not yet implemented"); + * + * } + * + * public void testProtectedAddAll() { fail("Not yet implemented"); + * + * } + */ } diff --git a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSBundleTest.java b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSBundleTest.java index d441bec..3945f4a 100644 --- a/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSBundleTest.java +++ b/projects/net.wotonomy.foundation/src/test/java/net/wotonomy/foundation/NSBundleTest.java @@ -30,7 +30,7 @@ public class NSBundleTest extends TestCase { public NSBundleTest(String arg0) { super(arg0); } - + protected void setUp() throws Exception { super.setUp(); } @@ -39,141 +39,92 @@ public class NSBundleTest extends TestCase { super.tearDown(); } /* - public void testNSBundle() { - //TODO Implement NSBundle(). - fail("Test not implemented."); - } - - public void testBundleForClass() { - //TODO Implement bundleForClass(). - fail("Test not implemented."); - } - */ - - public void testBundleWithURL() throws Exception{ + * public void testNSBundle() { //TODO Implement NSBundle(). + * fail("Test not implemented."); } + * + * public void testBundleForClass() { //TODO Implement bundleForClass(). + * fail("Test not implemented."); } + */ + + public void testBundleWithURL() throws Exception { /* - URL url = new File(System.getProperty("user.dir")+"/target/test-classes/TestBundle.framework").toURI().toURL(); - System.out.println(url.toString()); - NSBundle bundle = NSBundle.bundleWithURL(url); - Assert.assertNotNull(bundle); - Assert.assertEquals("TestBundle",bundle.name()); - Assert.assertEquals(true,bundle.isFramework()); - Properties p = bundle.properties(); - Assert.assertNotNull(p); - Assert.assertEquals("TestValue",p.getProperty("TestKey")); - */ + * URL url = new File(System.getProperty("user.dir")+ + * "/target/test-classes/TestBundle.framework").toURI().toURL(); + * System.out.println(url.toString()); NSBundle bundle = + * NSBundle.bundleWithURL(url); Assert.assertNotNull(bundle); + * Assert.assertEquals("TestBundle",bundle.name()); + * Assert.assertEquals(true,bundle.isFramework()); Properties p = + * bundle.properties(); Assert.assertNotNull(p); + * Assert.assertEquals("TestValue",p.getProperty("TestKey")); + */ } /* - public void testBundleForName() { - //TODO Implement bundleForName(). - fail("Test not implemented."); - } - - public void testFrameworkBundles() { - //TODO Implement frameworkBundles(). - fail("Test not implemented."); - } - - public void testSetMainBundle() { - //TODO Implement setMainBundle(). - fail("Test not implemented."); - } - - public void testMainBundle() { - //TODO Implement mainBundle(). - fail("Test not implemented."); - } - - public void testDefaultLocalePrefix() { - //TODO Implement defaultLocalePrefix(). - fail("Test not implemented."); - } - - public void testFindOrCreateBundleWithPath() { - //TODO Implement findOrCreateBundleWithPath(). - fail("Test not implemented."); - } - - public void testBundleClassPackageNames() { - //TODO Implement bundleClassPackageNames(). - fail("Test not implemented."); - } - - public void testBundlePath() { - //TODO Implement bundlePath(). - fail("Test not implemented."); - } - - public void testBytesForResourcePath() { - //TODO Implement bytesForResourcePath(). - fail("Test not implemented."); - } - - public void testBundleClassNames() { - //TODO Implement bundleClassNames(). - fail("Test not implemented."); - } - - public void testInfoDictionary() { - //TODO Implement infoDictionary(). - fail("Test not implemented."); - } - - public void testInputStreamForResourcePath() { - //TODO Implement inputStreamForResourcePath(). - fail("Test not implemented."); - } - - public void testIsFramework() { - //TODO Implement isFramework(). - fail("Test not implemented."); - } - - public void testLoad() { - //TODO Implement load(). - fail("Test not implemented."); - } - - public void testName() { - //TODO Implement name(). - fail("Test not implemented."); - } - - public void testPrincipalClass() { - //TODO Implement principalClass(). - fail("Test not implemented."); - } - - public void testProperties() { - //TODO Implement properties(). - fail("Test not implemented."); - } - - public void testResourcePathForLocalizedResourceNamed() { - //TODO Implement resourcePathForLocalizedResourceNamed(). - fail("Test not implemented."); - } - - public void testResourcePathsForDirectories() { - //TODO Implement resourcePathsForDirectories(). - fail("Test not implemented."); - } - - public void testResourcePathsForLocalizedResources() { - //TODO Implement resourcePathsForLocalizedResources(). - fail("Test not implemented."); - } - - public void testResourcePathsForResources() { - //TODO Implement resourcePathsForResources(). - fail("Test not implemented."); - } - - public void testToString() { - //TODO Implement toString(). - fail("Test not implemented."); - } - - */ + * public void testBundleForName() { //TODO Implement bundleForName(). + * fail("Test not implemented."); } + * + * public void testFrameworkBundles() { //TODO Implement frameworkBundles(). + * fail("Test not implemented."); } + * + * public void testSetMainBundle() { //TODO Implement setMainBundle(). + * fail("Test not implemented."); } + * + * public void testMainBundle() { //TODO Implement mainBundle(). + * fail("Test not implemented."); } + * + * public void testDefaultLocalePrefix() { //TODO Implement + * defaultLocalePrefix(). fail("Test not implemented."); } + * + * public void testFindOrCreateBundleWithPath() { //TODO Implement + * findOrCreateBundleWithPath(). fail("Test not implemented."); } + * + * public void testBundleClassPackageNames() { //TODO Implement + * bundleClassPackageNames(). fail("Test not implemented."); } + * + * public void testBundlePath() { //TODO Implement bundlePath(). + * fail("Test not implemented."); } + * + * public void testBytesForResourcePath() { //TODO Implement + * bytesForResourcePath(). fail("Test not implemented."); } + * + * public void testBundleClassNames() { //TODO Implement bundleClassNames(). + * fail("Test not implemented."); } + * + * public void testInfoDictionary() { //TODO Implement infoDictionary(). + * fail("Test not implemented."); } + * + * public void testInputStreamForResourcePath() { //TODO Implement + * inputStreamForResourcePath(). fail("Test not implemented."); } + * + * public void testIsFramework() { //TODO Implement isFramework(). + * fail("Test not implemented."); } + * + * public void testLoad() { //TODO Implement load(). + * fail("Test not implemented."); } + * + * public void testName() { //TODO Implement name(). + * fail("Test not implemented."); } + * + * public void testPrincipalClass() { //TODO Implement principalClass(). + * fail("Test not implemented."); } + * + * public void testProperties() { //TODO Implement properties(). + * fail("Test not implemented."); } + * + * public void testResourcePathForLocalizedResourceNamed() { //TODO Implement + * resourcePathForLocalizedResourceNamed(). fail("Test not implemented."); } + * + * public void testResourcePathsForDirectories() { //TODO Implement + * resourcePathsForDirectories(). fail("Test not implemented."); } + * + * public void testResourcePathsForLocalizedResources() { //TODO Implement + * resourcePathsForLocalizedResources(). fail("Test not implemented."); } + * + * public void testResourcePathsForResources() { //TODO Implement + * resourcePathsForResources(). fail("Test not implemented."); } + * + * public void testToString() { //TODO Implement toString(). + * fail("Test not implemented."); } + * + */ } diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptor.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptor.java index ece159b..f3d848c 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptor.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptor.java @@ -23,14 +23,13 @@ import net.wotonomy.access.EOModel; import net.wotonomy.access.EOSQLExpressionFactory; import net.wotonomy.foundation.NSDictionary; - /** -* An adaptor that connects to a databaser server via JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * An adaptor that connects to a databaser server via JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCAdaptor extends EOAdaptor { protected EOSQLExpressionFactory _expressionFactory; @@ -39,6 +38,7 @@ public class JDBCAdaptor extends EOAdaptor { /** * Creates a new instance. + * * @param name The name of the adaptor (should always be JDBC) */ public JDBCAdaptor(String name) { @@ -48,17 +48,19 @@ public class JDBCAdaptor extends EOAdaptor { public void setConnectionDictionary(NSDictionary dict) { super.setConnectionDictionary(dict); if (dict.objectForKey("driver") != null) - _driverName = (String)dict.objectForKey("driver"); + _driverName = (String) dict.objectForKey("driver"); else throw new JDBCAdaptorException("Connection dictionary must have a 'driver' key.", null); if (dict.objectForKey("jdbc2Info") != null) - _jdbcInfo = (NSDictionary)dict.objectForKey("jdbc2Info"); + _jdbcInfo = (NSDictionary) dict.objectForKey("jdbc2Info"); else throw new JDBCAdaptorException("Connection dictionary must have a 'jdbc2Info' key.", null); } - /* Checks to see if the connection dictionary is valid. - * Throws an exception if it's not. + /* + * Checks to see if the connection dictionary is valid. Throws an exception if + * it's not. + * * @see net.wotonomy.access.EOAdaptor#assertConnectionDictionaryIsValid() */ public void assertConnectionDictionaryIsValid() { @@ -67,7 +69,9 @@ public class JDBCAdaptor extends EOAdaptor { context.disconnect(); } - /* Creates a JDBCContext. + /* + * Creates a JDBCContext. + * * @see net.wotonomy.access.EOAdaptor#createAdaptorContext() */ public EOAdaptorContext createAdaptorContext() { @@ -76,14 +80,18 @@ public class JDBCAdaptor extends EOAdaptor { return context; } - /* Returns the JDBCExpression class. + /* + * Returns the JDBCExpression class. + * * @see net.wotonomy.access.EOAdaptor#defaultExpressionClass() */ public Class defaultExpressionClass() { return JDBCExpression.class; } - /* Returns a JDBCExpressionFactory. + /* + * Returns a JDBCExpressionFactory. + * * @see net.wotonomy.access.EOAdaptor#expressionFactory() */ public EOSQLExpressionFactory expressionFactory() { @@ -92,8 +100,11 @@ public class JDBCAdaptor extends EOAdaptor { return _expressionFactory; } - /* Determines if a qualifier type is valid. - * @see net.wotonomy.access.EOAdaptor#isValidQualifierType(java.lang.String, net.wotonomy.access.EOModel) + /* + * Determines if a qualifier type is valid. + * + * @see net.wotonomy.access.EOAdaptor#isValidQualifierType(java.lang.String, + * net.wotonomy.access.EOModel) */ public boolean isValidQualifierType(String typeName, EOModel model) { // TODO Auto-generated method stub @@ -110,14 +121,13 @@ public class JDBCAdaptor extends EOAdaptor { } /* - * $Log$ - * Revision 1.2 2006/02/18 22:59:22 cgruber - * make it compile with maven dependencies and add a cvsignore. + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/08/13 20:09:37 chochos - * a concrete implementation of EOAdaptor for use with JDBC + * Revision 1.1 2003/08/13 20:09:37 chochos a concrete implementation of + * EOAdaptor for use with JDBC * */ diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptorException.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptorException.java index 5a999d0..412c0a1 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptorException.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCAdaptorException.java @@ -14,19 +14,20 @@ 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.jdbcadaptor; + */ +package net.wotonomy.jdbcadaptor; import java.sql.SQLException; import net.wotonomy.access.EOGeneralAdaptorException; /** -* Concrete implementation of EOSQLExpression for use with JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * Concrete implementation of EOSQLExpression for use with JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCAdaptorException extends EOGeneralAdaptorException { protected SQLException _sqlException; @@ -55,14 +56,13 @@ public class JDBCAdaptorException extends EOGeneralAdaptorException { } /* - * $Log$ - * Revision 1.2 2006/02/18 22:59:22 cgruber - * make it compile with maven dependencies and add a cvsignore. + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/13 20:11:30 chochos - * forgot to add the wotonomy disclaimer... + * Revision 1.2 2003/08/13 20:11:30 chochos forgot to add the wotonomy + * disclaimer... * */ diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCChannel.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCChannel.java index c62c463..64e4572 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCChannel.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCChannel.java @@ -41,12 +41,12 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSTimestamp; /** -* Concrete implementation of EOAdaptorChannel for use with JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * Concrete implementation of EOAdaptorChannel for use with JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCChannel extends EOAdaptorChannel { protected boolean _fetchInProgress; @@ -60,6 +60,7 @@ public class JDBCChannel extends EOAdaptorChannel { /** * Creates a new JDBCChannel. + * * @param context The JDBCContext this channel belongs to. */ public JDBCChannel(JDBCContext context) { @@ -67,24 +68,31 @@ public class JDBCChannel extends EOAdaptorChannel { } protected JDBCContext _context() { - return (JDBCContext)adaptorContext(); + return (JDBCContext) adaptorContext(); } - /* Sets the attributes to be fetched from the database. - * @see net.wotonomy.access.EOAdaptorChannel#setAttributesToFetch(net.wotonomy.foundation.NSArray) + /* + * Sets the attributes to be fetched from the database. + * + * @see net.wotonomy.access.EOAdaptorChannel#setAttributesToFetch(net.wotonomy. + * foundation.NSArray) */ public void setAttributesToFetch(NSArray atts) { _attsToFetch = atts; } - /* Returns an array with the attributes that will be fetched. + /* + * Returns an array with the attributes that will be fetched. + * * @see net.wotonomy.access.EOAdaptorChannel#attributesToFetch() */ public NSArray attributesToFetch() { return _attsToFetch; } - /* Cancels the fetch, rolling back the transaction. + /* + * Cancels the fetch, rolling back the transaction. + * * @see net.wotonomy.access.EOAdaptorChannel#cancelFetch() */ public void cancelFetch() { @@ -98,7 +106,9 @@ public class JDBCChannel extends EOAdaptorChannel { } } - /* Closes the jdbc channel. + /* + * Closes the jdbc channel. + * * @see net.wotonomy.access.EOAdaptorChannel#closeChannel() */ public void closeChannel() { @@ -111,9 +121,11 @@ public class JDBCChannel extends EOAdaptorChannel { } } - /* If the fetch was done with an array of EOAttributes, returns - * that same array; otherwise it creates an array of EOAttributes - * based on the column names that will be fetched. + /* + * If the fetch was done with an array of EOAttributes, returns that same array; + * otherwise it creates an array of EOAttributes based on the column names that + * will be fetched. + * * @see net.wotonomy.access.EOAdaptorChannel#describeResults() */ public NSArray describeResults() { @@ -134,7 +146,7 @@ public class JDBCChannel extends EOAdaptorChannel { a.setAllowsNull(_rsmeta.isNullable(i) == ResultSetMetaData.columnNullable); a.setWidth(_rsmeta.getColumnDisplaySize(i)); a.setReadOnly(_rsmeta.isReadOnly(i)); - attarr[i-1] = a; + attarr[i - 1] = a; } _resultAttributes = new NSArray(attarr); } catch (SQLException ex) { @@ -144,9 +156,12 @@ public class JDBCChannel extends EOAdaptorChannel { return _resultAttributes; } - /* Deletes from the database the rows described by the qualifier, - * in the specified entity. - * @see net.wotonomy.access.EOAdaptorChannel#deleteRowsDescribedByQualifier(net.wotonomy.control.EOQualifier, net.wotonomy.access.EOEntity) + /* + * Deletes from the database the rows described by the qualifier, in the + * specified entity. + * + * @see net.wotonomy.access.EOAdaptorChannel#deleteRowsDescribedByQualifier(net. + * wotonomy.control.EOQualifier, net.wotonomy.access.EOEntity) */ public int deleteRowsDescribedByQualifier(EOQualifier q, EOEntity entity) { EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity); @@ -155,11 +170,14 @@ public class JDBCChannel extends EOAdaptorChannel { return _resultCount; } - /* Creates a java.sql.Statement object and executes it. - * If there is an open transaction, the statement is executed inside it; - * otherwise a transaction is started, the statement executed, and - * the transaction is committed. - * @see net.wotonomy.access.EOAdaptorChannel#evaluateExpression(net.wotonomy.access.EOSQLExpression) + /* + * Creates a java.sql.Statement object and executes it. If there is an open + * transaction, the statement is executed inside it; otherwise a transaction is + * started, the statement executed, and the transaction is committed. + * + * @see + * net.wotonomy.access.EOAdaptorChannel#evaluateExpression(net.wotonomy.access. + * EOSQLExpression) */ public void evaluateExpression(EOSQLExpression sql) { if (!isOpen()) @@ -173,20 +191,20 @@ public class JDBCChannel extends EOAdaptorChannel { boolean isQuery = false; String text = sql.statement(); try { - //run an executeUpdate with these prefixes + // run an executeUpdate with these prefixes if (text.startsWith("INSERT") || text.startsWith("DELETE") || text.startsWith("UPDATE")) { conditionalBeginTransaction(); _resultCount = _statement.executeUpdate(text); conditionalCommitTransaction(); return; } else if (text.startsWith("SELECT")) { - //run an executeQuery with SELECT + // run an executeQuery with SELECT if (_resultCount > 0) _statement.setMaxRows(_resultCount); _resultSet = _statement.executeQuery(text); _fetchInProgress = true; return; - } else { //just plain execute + } else { // just plain execute conditionalBeginTransaction(); isQuery = _statement.execute(text); } @@ -207,80 +225,83 @@ public class JDBCChannel extends EOAdaptorChannel { } } - /* Executes a stored procedure with the specified parameters. - * Any results that the procedure returns should be obtained - * by calling returnValuesForLastStoredProcedureInvocation. - * @see net.wotonomy.access.EOAdaptorChannel#executeStoredProcedure(net.wotonomy.access.EOStoredProcedure, net.wotonomy.foundation.NSDictionary) + /* + * Executes a stored procedure with the specified parameters. Any results that + * the procedure returns should be obtained by calling + * returnValuesForLastStoredProcedureInvocation. + * + * @see + * net.wotonomy.access.EOAdaptorChannel#executeStoredProcedure(net.wotonomy. + * access.EOStoredProcedure, net.wotonomy.foundation.NSDictionary) */ - public void executeStoredProcedure( - EOStoredProcedure proc, NSDictionary values) { + public void executeStoredProcedure(EOStoredProcedure proc, NSDictionary values) { if (!isOpen()) throw new EOGeneralAdaptorException("Attempt to execute a stored procedure on a closed channel."); conditionalBeginTransaction(); try { - //Assemble the procedure call + // Assemble the procedure call StringBuffer buf = new StringBuffer("{ call "); buf.append(proc.externalName()); NSArray args = proc.arguments(); if (args != null && args.count() > 0) { buf.append("["); for (int i = 0; i < args.count(); i++) { - EOAttribute a = (EOAttribute)args.objectAtIndex(i); + EOAttribute a = (EOAttribute) args.objectAtIndex(i); if (a.parameterDirection() != EOAttribute.OutParameter) { buf.append('?'); buf.append(", "); } } - buf.delete(buf.length()-2, buf.length()); + buf.delete(buf.length() - 2, buf.length()); buf.append("]"); } buf.append(" }"); - //get the callable statement + // get the callable statement CallableStatement sp = _context().connection().prepareCall(buf.toString()); if (args != null && args.count() > 0) { int pos = 1; - //set the in and inOut parameters + // set the in and inOut parameters for (int i = 0; i < args.count(); i++) { - EOAttribute a = (EOAttribute)args.objectAtIndex(i); + EOAttribute a = (EOAttribute) args.objectAtIndex(i); if (a.parameterDirection() != EOAttribute.OutParameter) { Object val = values.objectForKey(a.name()); if (val == NSKeyValueCoding.NullValue) - sp.setNull(pos, 0); //TODO: check sql type + sp.setNull(pos, 0); // TODO: check sql type if (val instanceof String) - sp.setString(pos, (String)val); + sp.setString(pos, (String) val); else if (val instanceof BigDecimal) - sp.setBigDecimal(pos, (BigDecimal)val); + sp.setBigDecimal(pos, (BigDecimal) val); else if (val instanceof NSTimestamp) - sp.setTimestamp(pos, (NSTimestamp)val); + sp.setTimestamp(pos, (NSTimestamp) val); else if (val instanceof NSData) - sp.setBytes(pos, ((NSData)val).bytes()); + sp.setBytes(pos, ((NSData) val).bytes()); else if (val instanceof Integer) - sp.setInt(pos, ((Integer)val).intValue()); + sp.setInt(pos, ((Integer) val).intValue()); else if (val instanceof Long) - sp.setLong(pos, ((Long)val).longValue()); + sp.setLong(pos, ((Long) val).longValue()); else sp.setObject(pos, val); pos++; } } } - //run the procedure + // run the procedure sp.execute(); - //get the return values + // get the return values if (args != null && args.count() > 0) { int pos = 1; NSMutableDictionary retvals = new NSMutableDictionary(); for (int i = 0; i < args.count(); i++) { - EOAttribute a = (EOAttribute)args.objectAtIndex(i); + EOAttribute a = (EOAttribute) args.objectAtIndex(i); if (a.parameterDirection() != EOAttribute.InParameter) { Object val = sp.getObject(pos); if (val == null) retvals.setObjectForKey(NSKeyValueCoding.NullValue, a.name()); else if (val instanceof Blob) { try { - retvals.setObjectForKey(new NSData(((Blob)val).getBinaryStream(), 1024), a.name()); + retvals.setObjectForKey(new NSData(((Blob) val).getBinaryStream(), 1024), a.name()); } catch (java.io.IOException ex) { - //what should I do here? + // what should I do here? retvals.setObjectForKey(NSData.EmptyData, a.name()); } } else @@ -296,7 +317,9 @@ public class JDBCChannel extends EOAdaptorChannel { conditionalCommitTransaction(); } - /* Fetches one row from the database + /* + * Fetches one row from the database + * * @see net.wotonomy.access.EOAdaptorChannel#fetchRow() */ public NSMutableDictionary fetchRow() { @@ -306,7 +329,7 @@ public class JDBCChannel extends EOAdaptorChannel { if (attributesToFetch() == null) throw new EOGeneralAdaptorException("Attempt to fetchRow without setting attributes to fetch first."); try { - //If the current result set ends, there may be another one + // If the current result set ends, there may be another one if (!_resultSet.next()) { _resultSet.close(); _resultAttributes = null; @@ -319,12 +342,12 @@ public class JDBCChannel extends EOAdaptorChannel { throw new JDBCAdaptorException("While trying to fetch row.", ex); } - //Assemble the dictionary + // Assemble the dictionary NSMutableDictionary dict = new NSMutableDictionary(attributesToFetch().count()); try { for (int i = 0; i < attributesToFetch().count(); i++) { - EOAttribute a = (EOAttribute)attributesToFetch().objectAtIndex(i); - Object o = _resultSet.getObject(i+1); + EOAttribute a = (EOAttribute) attributesToFetch().objectAtIndex(i); + Object o = _resultSet.getObject(i + 1); if (o == null) o = NSKeyValueCoding.NullValue; dict.setObjectForKey(o, a.name()); @@ -335,8 +358,11 @@ public class JDBCChannel extends EOAdaptorChannel { return dict; } - /* Inserts a row into a table in the database. - * @see net.wotonomy.access.EOAdaptorChannel#insertRow(net.wotonomy.foundation.NSDictionary, net.wotonomy.access.EOEntity) + /* + * Inserts a row into a table in the database. + * + * @see net.wotonomy.access.EOAdaptorChannel#insertRow(net.wotonomy.foundation. + * NSDictionary, net.wotonomy.access.EOEntity) */ public void insertRow(NSDictionary row, EOEntity entity) { EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity); @@ -344,15 +370,19 @@ public class JDBCChannel extends EOAdaptorChannel { evaluateExpression(exp); } - /* Indicates if a fetch is in progress; that is, if a SELECT statement - * was executed and there are still rows to be fetched. + /* + * Indicates if a fetch is in progress; that is, if a SELECT statement was + * executed and there are still rows to be fetched. + * * @see net.wotonomy.access.EOAdaptorChannel#isFetchInProgress() */ public boolean isFetchInProgress() { return _fetchInProgress; } - /* Indicates if the channel is open. + /* + * Indicates if the channel is open. + * * @see net.wotonomy.access.EOAdaptorChannel#isOpen() */ public boolean isOpen() { @@ -365,9 +395,10 @@ public class JDBCChannel extends EOAdaptorChannel { return open; } - /* Opens the channel. If the adaptor context has not yet made - * a connection to the database, this forces the context to - * connect. + /* + * Opens the channel. If the adaptor context has not yet made a connection to + * the database, this forces the context to connect. + * * @see net.wotonomy.access.EOAdaptorChannel#openChannel() */ public void openChannel() { @@ -379,36 +410,46 @@ public class JDBCChannel extends EOAdaptorChannel { } } - /* Returns the values obtained from the last stored procedure executed. - * @see net.wotonomy.access.EOAdaptorChannel#returnValuesForLastStoredProcedureInvocation() + /* + * Returns the values obtained from the last stored procedure executed. + * + * @see net.wotonomy.access.EOAdaptorChannel# + * returnValuesForLastStoredProcedureInvocation() */ public NSDictionary returnValuesForLastStoredProcedureInvocation() { return _spReturnValues; } - /* Creates a SELECT expression and executes it. If the attribute array is null, - * then the result's metadata is used to dynamically create an array - * of attributes. - * @see net.wotonomy.access.EOAdaptorChannel#selectAttributes(net.wotonomy.foundation.NSArray, net.wotonomy.control.EOFetchSpecification, boolean, net.wotonomy.access.EOEntity) + /* + * Creates a SELECT expression and executes it. If the attribute array is null, + * then the result's metadata is used to dynamically create an array of + * attributes. + * + * @see + * net.wotonomy.access.EOAdaptorChannel#selectAttributes(net.wotonomy.foundation + * .NSArray, net.wotonomy.control.EOFetchSpecification, boolean, + * net.wotonomy.access.EOEntity) */ - public void selectAttributes( - NSArray atts, EOFetchSpecification fspec, - boolean lock, EOEntity entity) { + public void selectAttributes(NSArray atts, EOFetchSpecification fspec, boolean lock, EOEntity entity) { _resultAttributes = atts; EOSQLExpression expr = adaptorContext().adaptor().expressionFactory().createExpression(entity); _fetchInProgress = true; expr.prepareSelectExpressionWithAttributes(atts, lock, fspec); - //for now we store the fetch limit here + // for now we store the fetch limit here if (fspec != null) _resultCount = fspec.fetchLimit(); evaluateExpression(expr); } - /* Creates and executes an UPDATE statement. - * @see net.wotonomy.access.EOAdaptorChannel#updateValuesInRowsDescribedByQualifier(net.wotonomy.foundation.NSDictionary, net.wotonomy.control.EOQualifier, net.wotonomy.access.EOEntity) + /* + * Creates and executes an UPDATE statement. + * + * @see + * net.wotonomy.access.EOAdaptorChannel#updateValuesInRowsDescribedByQualifier( + * net.wotonomy.foundation.NSDictionary, net.wotonomy.control.EOQualifier, + * net.wotonomy.access.EOEntity) */ - public int updateValuesInRowsDescribedByQualifier( - NSDictionary row, EOQualifier q, EOEntity entity) { + public int updateValuesInRowsDescribedByQualifier(NSDictionary row, EOQualifier q, EOEntity entity) { EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity); exp.prepareUpdateExpressionWithRow(row, q); evaluateExpression(exp); @@ -429,21 +470,18 @@ public class JDBCChannel extends EOAdaptorChannel { } /* -* $Log$ -* Revision 1.2 2006/02/18 22:59:22 cgruber -* make it compile with maven dependencies and add a cvsignore. -* -* Revision 1.1 2006/02/16 13:22:23 cgruber -* Check in all sources in eclipse-friendly maven-enabled packages. -* -* Revision 1.3 2003/08/14 02:15:11 chochos -* added lots of comments -* -* Revision 1.2 2003/08/13 20:45:20 chochos -* small fixes in evaluateExpression, which has been successfully tested with a SELECT statement. -* -* Revision 1.1 2003/08/13 20:12:48 chochos -* a subclass of EOAdaptorChannel to be used with JDBC. -* -*/ - + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. + * + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/08/14 02:15:11 chochos added lots of comments + * + * Revision 1.2 2003/08/13 20:45:20 chochos small fixes in evaluateExpression, + * which has been successfully tested with a SELECT statement. + * + * Revision 1.1 2003/08/13 20:12:48 chochos a subclass of EOAdaptorChannel to be + * used with JDBC. + * + */ diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCContext.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCContext.java index 445ffb5..9e6df6c 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCContext.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCContext.java @@ -27,18 +27,19 @@ import net.wotonomy.access.EOAdaptorChannel; import net.wotonomy.access.EOAdaptorContext; /** -* Concrete implementation of EOAdaptorContext for use with JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * Concrete implementation of EOAdaptorContext for use with JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCContext extends EOAdaptorContext { protected Connection _jdbcConnection; /** * Creates a new instance. + * * @param adaptor The adaptor this context belongs to. */ public JDBCContext(EOAdaptor adaptor) { @@ -49,12 +50,12 @@ public class JDBCContext extends EOAdaptorContext { try { if (_jdbcConnection != null && !_jdbcConnection.isClosed()) throw new JDBCAdaptorException("Attempt to connect when already connected.", null); - Class.forName(((JDBCAdaptor)adaptor()).driverName()); - String url = (String)adaptor().connectionDictionary().objectForKey("URL"); + Class.forName(((JDBCAdaptor) adaptor()).driverName()); + String url = (String) adaptor().connectionDictionary().objectForKey("URL"); Driver driver = DriverManager.getDriver(url); java.util.Properties props = new java.util.Properties(); - props.setProperty("user", (String)adaptor().connectionDictionary().objectForKey("username")); - props.setProperty("password", (String)adaptor().connectionDictionary().objectForKey("password")); + props.setProperty("user", (String) adaptor().connectionDictionary().objectForKey("username")); + props.setProperty("password", (String) adaptor().connectionDictionary().objectForKey("password")); _jdbcConnection = driver.connect(url, props); _jdbcConnection.setAutoCommit(false); } catch (SQLException ex) { @@ -80,8 +81,9 @@ public class JDBCContext extends EOAdaptorContext { return _jdbcConnection; } - /* Begins a transaction. Actually it does nothing because it's not - * necessary. + /* + * Begins a transaction. Actually it does nothing because it's not necessary. + * * @see net.wotonomy.access.EOAdaptorContext#beginTransaction() */ public void beginTransaction() { @@ -90,7 +92,9 @@ public class JDBCContext extends EOAdaptorContext { transactionDidBegin(); } - /* Commits a transaction. + /* + * Commits a transaction. + * * @see net.wotonomy.access.EOAdaptorContext#commitTransaction() */ public void commitTransaction() { @@ -102,7 +106,9 @@ public class JDBCContext extends EOAdaptorContext { } } - /* Rolls back a transacion. + /* + * Rolls back a transacion. + * * @see net.wotonomy.access.EOAdaptorContext#rollbackTransaction() */ public void rollbackTransaction() { @@ -114,7 +120,9 @@ public class JDBCContext extends EOAdaptorContext { } } - /* Creates a JDBCChannel instance. + /* + * Creates a JDBCChannel instance. + * * @see net.wotonomy.access.EOAdaptorContext#createAdaptorChannel() */ public EOAdaptorChannel createAdaptorChannel() { @@ -123,7 +131,9 @@ public class JDBCContext extends EOAdaptorContext { return channel; } - /* I don't know what to do here. Throw something, maybe? + /* + * I don't know what to do here. Throw something, maybe? + * * @see net.wotonomy.access.EOAdaptorContext#handleDroppedConnection() */ public void handleDroppedConnection() { @@ -133,14 +143,14 @@ public class JDBCContext extends EOAdaptorContext { } /* - * $Log$ - * Revision 1.2 2006/02/18 22:59:22 cgruber - * make it compile with maven dependencies and add a cvsignore. + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/08/13 20:13:33 chochos - * concrete implementation of EOAdaptorContext for use with JDBC. This is the class that holds the physical connection to the database. + * Revision 1.1 2003/08/13 20:13:33 chochos concrete implementation of + * EOAdaptorContext for use with JDBC. This is the class that holds the physical + * connection to the database. * */ diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpression.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpression.java index 2258a65..97e3352 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpression.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpression.java @@ -23,14 +23,13 @@ import net.wotonomy.access.EOSQLExpression; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; - /** -* Concrete implementation of EOSQLExpression for use with JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * Concrete implementation of EOSQLExpression for use with JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCExpression extends EOSQLExpression { protected NSDictionary _jdbcInfo; @@ -45,29 +44,32 @@ public class JDBCExpression extends EOSQLExpression { public void setJdbcInfo(NSDictionary info) { _jdbcInfo = info; } + public NSDictionary jdbcInfo() { return _jdbcInfo; } - /* (non-Javadoc) - * @see net.wotonomy.access.EOSQLExpression#bindVariableDictionaryForAttribute(net.wotonomy.access.EOAttribute, java.lang.Object) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.access.EOSQLExpression#bindVariableDictionaryForAttribute(net. + * wotonomy.access.EOAttribute, java.lang.Object) */ - public NSMutableDictionary bindVariableDictionaryForAttribute( - EOAttribute attr, Object variable) { + public NSMutableDictionary bindVariableDictionaryForAttribute(EOAttribute attr, Object variable) { // TODO Auto-generated method stub return null; } } /* - * $Log$ - * Revision 1.2 2006/02/18 22:59:22 cgruber - * make it compile with maven dependencies and add a cvsignore. + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/08/13 20:14:38 chochos - * subclass of EOSQLExpression. Still needs a lot of work for bindings, mostly. + * Revision 1.1 2003/08/13 20:14:38 chochos subclass of EOSQLExpression. Still + * needs a lot of work for bindings, mostly. * */ diff --git a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpressionFactory.java b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpressionFactory.java index 15c167f..6f51181 100644 --- a/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpressionFactory.java +++ b/projects/net.wotonomy.persistence.adapter.jdbc/src/main/java/net/wotonomy/jdbcadaptor/JDBCExpressionFactory.java @@ -23,25 +23,25 @@ import net.wotonomy.access.EOSQLExpression; import net.wotonomy.access.EOSQLExpressionFactory; import net.wotonomy.foundation.NSDictionary; - /** -* Concrete implementation of EOSQLExpressionFactory for use with JDBC. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 903 $ -*/ + * Concrete implementation of EOSQLExpressionFactory for use with JDBC. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 903 $ + */ public class JDBCExpressionFactory extends EOSQLExpressionFactory { protected NSDictionary _jdbcInfo; /** * Creates a new instance. + * * @param adaptor The adaptor for this factory. */ public JDBCExpressionFactory(EOAdaptor adaptor) { super(adaptor); - _jdbcInfo = ((JDBCAdaptor)adaptor()).jdbcInfo(); + _jdbcInfo = ((JDBCAdaptor) adaptor()).jdbcInfo(); } public EOSQLExpression createExpression(EOEntity entity) { @@ -52,14 +52,13 @@ public class JDBCExpressionFactory extends EOSQLExpressionFactory { } /* - * $Log$ - * Revision 1.2 2006/02/18 22:59:22 cgruber - * make it compile with maven dependencies and add a cvsignore. + * $Log$ Revision 1.2 2006/02/18 22:59:22 cgruber make it compile with maven + * dependencies and add a cvsignore. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/08/13 20:15:05 chochos - * the subclass of EOSQLExpressionFactory to be used with JDBC. + * Revision 1.1 2003/08/13 20:15:05 chochos the subclass of + * EOSQLExpressionFactory to be used with JDBC. * */ \ No newline at end of file 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 index 1aafa13..ebb8199 100644 --- 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 @@ -21,25 +21,31 @@ 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 { + * 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) { + 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) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOFaultHandler#completeInitializationOfObject(java.lang. + * Object) */ public void completeInitializationOfObject(Object obj) { // TODO Auto-generated method stub @@ -56,16 +62,16 @@ import net.wotonomy.control.EOKeyGlobalID; } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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) + * 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 index d4cabe9..0690295 100644 --- 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 @@ -21,13 +21,14 @@ 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 { + * 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; @@ -37,8 +38,12 @@ import net.wotonomy.control.EOKeyGlobalID; setContext(dbc, ec); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOFaultHandler#completeInitializationOfObject(java.lang.Object) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOFaultHandler#completeInitializationOfObject(java.lang. + * Object) */ public void completeInitializationOfObject(Object obj) { // TODO Auto-generated method stub @@ -51,16 +56,16 @@ import net.wotonomy.control.EOKeyGlobalID; } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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) + * 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 index 6876151..31378f4 100644 --- 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 @@ -21,12 +21,12 @@ 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 $ -*/ + * 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; @@ -36,7 +36,9 @@ public abstract class EOAccessGenericFaultHandler extends EOFaultHandler { super(); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOFaultHandler#faultWillFire(java.lang.Object) */ public void faultWillFire(Object obj) { @@ -59,17 +61,16 @@ public abstract class EOAccessGenericFaultHandler extends EOFaultHandler { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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) + * 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 index 28199c5..f305579 100644 --- 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 @@ -20,8 +20,8 @@ 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. + * 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 $ @@ -45,16 +45,15 @@ public class EOAccessLock { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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 index 28295d1..17d6454 100644 --- 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 @@ -26,11 +26,11 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSTimestamp; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public abstract class EOAdaptor { @@ -48,6 +48,7 @@ public abstract class EOAdaptor { /** * 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. */ @@ -64,11 +65,13 @@ public abstract class EOAdaptor { } /** - * 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. + * 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 */ @@ -78,10 +81,10 @@ public abstract class EOAdaptor { if (name.endsWith("Adaptor") && name.indexOf('.') > 0) { cname = name; int lastdot = name.lastIndexOf('.'); - //take off the package and the 'Adaptor' suffix + // take off the package and the 'Adaptor' suffix name = cname.substring(lastdot, cname.length() - 7); } else { - //construct the fully qualified class name + // construct the fully qualified class name cname = "net.wotonomy." + name.toLowerCase() + "adaptor." + name + "Adaptor"; } try { @@ -92,12 +95,14 @@ public abstract class EOAdaptor { 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 }); + 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()); + 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; } @@ -105,8 +110,9 @@ public abstract class EOAdaptor { public static void setExpressionClassName(String expClassName, String adaptorClassName) { _expressionClassesByName.setObjectForKey(expClassName, adaptorClassName); } + public static String expressionClassName(String adaptorClassName) { - return (String)_expressionClassesByName.objectForKey(adaptorClassName); + return (String) _expressionClassesByName.objectForKey(adaptorClassName); } public void assignExternalInfoForAttribute(EOAttribute attribute) { @@ -126,11 +132,11 @@ public abstract class EOAdaptor { 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 + 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); + EOAttribute a = (EOAttribute) atts.objectAtIndex(i); assignExternalInfoForAttribute(a); } assignExternalInfoForEntity(e); @@ -149,6 +155,7 @@ public abstract class EOAdaptor { public void setConnectionDictionary(NSDictionary connection) { _connectionDictionary = connection; } + public NSDictionary connectionDictionary() { return _connectionDictionary; } @@ -205,19 +212,19 @@ public abstract class EOAdaptor { if (value == NSKeyValueCoding.NullValue) return value; if (value instanceof String) - return fetchedValueForStringValue((String)value, attr); + return fetchedValueForStringValue((String) value, attr); if (value instanceof NSData) - return fetchedValueForDataValue((NSData)value, attr); + return fetchedValueForDataValue((NSData) value, attr); if (value instanceof Number) - return fetchedValueForNumberValue((Number)value, attr); + return fetchedValueForNumberValue((Number) value, attr); if (value instanceof NSTimestamp) - return fetchedValueForDateValue((NSTimestamp)value, attr); + return fetchedValueForDateValue((NSTimestamp) value, attr); return value; } public void handleDroppedConnection() { for (int i = 0; i < _contexts.count(); i++) { - EOAdaptorContext c = (EOAdaptorContext)_contexts.objectAtIndex(i); + EOAdaptorContext c = (EOAdaptorContext) _contexts.objectAtIndex(i); c.transactionDidRollback(); c.handleDroppedConnection(); } @@ -226,7 +233,7 @@ public abstract class EOAdaptor { public boolean hasOpenChannels() { for (int i = 0; i < _contexts.count(); i++) { - EOAdaptorContext c = (EOAdaptorContext)_contexts.objectAtIndex(i); + EOAdaptorContext c = (EOAdaptorContext) _contexts.objectAtIndex(i); if (c.hasOpenChannels()) return true; } @@ -251,19 +258,19 @@ public abstract class EOAdaptor { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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 + * 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 index 09b8a6d..44b3401 100644 --- 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 @@ -24,11 +24,11 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public abstract class EOAdaptorChannel { protected EOAdaptorContext _context; @@ -102,7 +102,7 @@ public abstract class EOAdaptorChannel { 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(); + keys[i] = ((EOAttribute) attributes.objectAtIndex(i)).name(); return new NSMutableDictionary(values, keys); } @@ -150,57 +150,60 @@ public abstract class EOAdaptorChannel { 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.", + 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.", + 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.", + 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.", + 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.", + 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.", + 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.", + 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.", + 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.", + 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, + 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); + EOAdaptorOperation adop = (EOAdaptorOperation) ops.objectAtIndex(i); performAdaptorOperation(adop); } } @@ -217,28 +220,29 @@ public abstract class EOAdaptorChannel { int count = updateValuesInRowsDescribedByQualifier(row, q, entity); if (count != 1) { adaptorContext().rollbackTransaction(); - throw new EOGeneralAdaptorException("The qualifier should describe exactly one row (updated " + count + " rows)"); + 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. + * $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. + * 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 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 + * 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 + * 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 index aff0ddd..35a1468 100644 --- 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 @@ -22,11 +22,11 @@ import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSNotificationCenter; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public abstract class EOAdaptorContext { @@ -63,7 +63,7 @@ public abstract class EOAdaptorContext { public boolean hasBusyChannels() { for (int i = 0; i < _channels.count(); i++) { - EOAdaptorChannel chan = (EOAdaptorChannel)_channels.objectAtIndex(i); + EOAdaptorChannel chan = (EOAdaptorChannel) _channels.objectAtIndex(i); if (chan.isFetchInProgress()) return true; } @@ -72,7 +72,7 @@ public abstract class EOAdaptorContext { public boolean hasOpenChannels() { for (int i = 0; i < _channels.count(); i++) { - EOAdaptorChannel chan = (EOAdaptorChannel)_channels.objectAtIndex(i); + EOAdaptorChannel chan = (EOAdaptorChannel) _channels.objectAtIndex(i); if (chan.isOpen()) return true; } @@ -85,34 +85,31 @@ public abstract class EOAdaptorContext { public void transactionDidBegin() { _hasOpenTransaction = true; - NSNotificationCenter.defaultCenter().postNotification( - AdaptorContextBeginTransactionNotification, this); + NSNotificationCenter.defaultCenter().postNotification(AdaptorContextBeginTransactionNotification, this); } public void transactionDidCommit() { _hasOpenTransaction = false; - NSNotificationCenter.defaultCenter().postNotification( - AdaptorContextCommitTransactionNotification, this); + NSNotificationCenter.defaultCenter().postNotification(AdaptorContextCommitTransactionNotification, this); } public void transactionDidRollback() { _hasOpenTransaction = false; - NSNotificationCenter.defaultCenter().postNotification( - AdaptorContextRollbackTransactionNotification, this); + 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. + * $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. + * 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 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 + * 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 index 818985d..6c977b9 100644 --- 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 @@ -22,14 +22,13 @@ 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 $ -*/ + * 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; @@ -48,6 +47,7 @@ public class EOAdaptorOperation { public void setAdaptorOperator(int adOp) { _adaptorOp = adOp; } + public int adaptorOperator() { return _adaptorOp; } @@ -55,6 +55,7 @@ public class EOAdaptorOperation { public void setAttributes(NSArray atts) { _attributes = atts; } + public NSArray attributes() { return _attributes; } @@ -62,6 +63,7 @@ public class EOAdaptorOperation { public void setChangedValues(NSDictionary values) { _changedValues = values; } + public NSDictionary changedValues() { return _changedValues; } @@ -85,6 +87,7 @@ public class EOAdaptorOperation { public void setException(Throwable t) { _exception = t; } + public Throwable exception() { return _exception; } @@ -92,6 +95,7 @@ public class EOAdaptorOperation { public void setQualifier(EOQualifier q) { _qualifier = q; } + public EOQualifier qualifier() { return _qualifier; } @@ -99,22 +103,23 @@ public class EOAdaptorOperation { 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. + * $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. + * 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 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 + * 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 index 8b651ec..d77a162 100644 --- 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 @@ -22,18 +22,18 @@ 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 $ -*/ + * 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. + // 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; @@ -75,26 +75,26 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public EOAttribute(NSDictionary dict, Object obj) { super(); if (obj instanceof EOEntity) - _entity = (EOEntity)obj; - setName((String)dict.objectForKey("name")); + _entity = (EOEntity) obj; + setName((String) dict.objectForKey("name")); if (dict.objectForKey("columnName") != null) - setColumnName((String)dict.objectForKey("columnName")); + 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"); + 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"))); + setPrecision(Integer.parseInt((String) dict.objectForKey("precision"))); if (dict.objectForKey("scale") != null) - setScale(Integer.parseInt((String)dict.objectForKey("scale"))); + setScale(Integer.parseInt((String) dict.objectForKey("scale"))); if (dict.objectForKey("width") != null) - setWidth(Integer.parseInt((String)dict.objectForKey("width"))); + setWidth(Integer.parseInt((String) dict.objectForKey("width"))); if (dict.objectForKey("parameterDirection") != null) - setParameterDirection(Integer.parseInt((String)dict.objectForKey("parameterDirection"))); + setParameterDirection(Integer.parseInt((String) dict.objectForKey("parameterDirection"))); setAllowsNull("Y".equals(dict.objectForKey("allowsNull"))); } @@ -105,6 +105,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setName(String name) { _name = name; } + public String name() { return _name; } @@ -112,6 +113,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setColumnName(String name) { _columnName = name; } + public String columnName() { if (_columnName != null) return _columnName; @@ -124,6 +126,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setClassName(String name) { _className = name; } + public String className() { if (_className != null) return _className; @@ -137,6 +140,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { _definition = def; _columnName = null; } + public String definition() { if (_definition != null) return _definition; @@ -149,6 +153,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setExternalType(String type) { _externalType = type; } + public String externalType() { if (_externalType != null) return _externalType; @@ -162,6 +167,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { _allowsNull = flag; _has_allowsNull = true; } + public boolean allowsNull() { if (_has_allowsNull) return _allowsNull; @@ -173,6 +179,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setReadOnly(boolean flag) { _readOnly = flag; } + public boolean readOnly() { return _readOnly; } @@ -184,6 +191,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { else _prototypeName = null; } + public EOAttribute prototype() { if (_prototypeName != null && _prototype == null) { try { @@ -202,6 +210,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setPrecision(int value) { _precision = value; } + public int precision() { if (_precision > 0) return _precision; @@ -213,6 +222,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setScale(int value) { _scale = value; } + public int scale() { if (_scale > 0) return _scale; @@ -224,10 +234,11 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setWidth(int value) { _width = value; } + public int width() { if (_width > 0) return _width; - if (prototype() != null) + if (prototype() != null) return _prototype.width(); return _width; } @@ -236,6 +247,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setValueClassName(String name) { setClassName(name); } + /** @deprecated Use className() instead. */ public String valueClassName() { return className(); @@ -244,6 +256,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setValueType(String type) { _valueType = type; } + public String valueType() { if (_valueType != null) return _valueType; @@ -255,6 +268,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setReadFormat(String value) { _readFormat = value; } + public String readFormat() { return _readFormat; } @@ -262,6 +276,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setWriteFormat(String value) { _writeFormat = value; } + public String writeFormat() { return _writeFormat; } @@ -270,9 +285,11 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { 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. + /** + * 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() { @@ -292,6 +309,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setParameterDirection(int dir) { _parameterDirection = dir; } + public int parameterDirection() { return _parameterDirection; } @@ -305,6 +323,7 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { public void setUserInfo(NSDictionary value) { _userInfo = value; } + public NSDictionary userInfo() { return _userInfo; } @@ -348,34 +367,31 @@ public class EOAttribute extends EOProperty implements EOPropertyListEncoding { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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.4 2003/08/09 01:35:35 chochos implement EOPropertyListEncoding * - * Revision 1.3 2003/08/08 06:52:09 chochos - * isFlattened() works + * 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.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. + * Revision 1.1 2003/08/07 02:39:45 chochos EOAttribute. Can be initialized from + * a property list. * -*/ \ No newline at end of file + */ \ 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 index a9177c5..a995290 100644 --- 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 @@ -28,11 +28,11 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSTimestamp; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public class EODatabase { protected EOAdaptor _adaptor; @@ -98,10 +98,10 @@ public class EODatabase { public EOEntity entityForObject(EOEnterpriseObject eo) { String cname = eo.getClass().getName(); for (int i = 0; i < _models.count(); i++) { - EOModel m = (EOModel)_models.objectAtIndex(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); + EOEntity e = (EOEntity) ents.objectAtIndex(i); if (e.className().equals(cname)) return e; } @@ -111,10 +111,10 @@ public class EODatabase { public EOEntity entityNamed(String name) { for (int i = 0; i < _models.count(); i++) { - EOModel m = (EOModel)_models.objectAtIndex(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); + EOEntity e = (EOEntity) ents.objectAtIndex(i); if (e.name().equals(name)) return e; } @@ -132,13 +132,13 @@ public class EODatabase { public void forgetSnapshotsForGlobalIDs(NSArray gids) { for (int i = 0; i < gids.count(); i++) - forgetSnapshotForGlobalID((EOGlobalID)gids.objectAtIndex(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); + EODatabaseContext c = (EODatabaseContext) _contexts.objectAtIndex(i); c.handleDroppedConnection(); } } @@ -160,7 +160,7 @@ public class EODatabase { } public void recordSnapshotForSourceGlobalID(NSArray gids, EOGlobalID gid, String name) { - NSMutableDictionary d = (NSMutableDictionary)_snapshots.objectForKey(gid); + NSMutableDictionary d = (NSMutableDictionary) _snapshots.objectForKey(gid); if (d == null) { d = new NSMutableDictionary(); _snapshots.setObjectForKey(d, gid); @@ -175,12 +175,12 @@ public class EODatabase { public void recordToManySnapshots(NSDictionary snaps) { Enumeration enumeration = snaps.keyEnumerator(); while (enumeration.hasMoreElements()) { - EOGlobalID gid = (EOGlobalID)enumeration.nextElement(); - NSDictionary rels = (NSDictionary)snaps.objectForKey(gid); + 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); + String relName = (String) relEnum.nextElement(); + NSArray gids = (NSArray) rels.objectForKey(relName); recordSnapshotForSourceGlobalID(gids, gid, relName); } } @@ -203,7 +203,7 @@ public class EODatabase { } public NSArray resultCacheForEntityNamed(String name) { - return (NSArray)_resultCache.objectForKey(name); + return (NSArray) _resultCache.objectForKey(name); } public void setResultCache(NSArray cache, String entityName) { @@ -215,7 +215,7 @@ public class EODatabase { } public NSDictionary snapshotForGlobalID(EOGlobalID gid) { - return (NSDictionary)_snapshots.objectForKey(gid); + return (NSDictionary) _snapshots.objectForKey(gid); } public NSDictionary snapshotForGlobalID(EOGlobalID gid, long l) { @@ -223,10 +223,10 @@ public class EODatabase { } public NSArray snapshotForSourceGlobalID(EOGlobalID gid, String name) { - NSDictionary d = (NSDictionary)_snapshots.objectForKey(gid); + NSDictionary d = (NSDictionary) _snapshots.objectForKey(gid); if (d == null) return null; - return (NSArray)d.objectForKey(name); + return (NSArray) d.objectForKey(name); } public NSDictionary snapshotForSourceGlobalID(EOGlobalID gid, String s, long l) { @@ -247,21 +247,21 @@ public class EODatabase { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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... + * 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 index 10424e9..72427ef 100644 --- 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 @@ -27,11 +27,11 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableArray; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public class EODatabaseChannel { protected EODatabaseContext _context; @@ -65,14 +65,15 @@ public class EODatabaseChannel { EOGlobalID gid = _currEntity.globalIDForRow(r); Object eo = _currEC.objectForGlobalID(gid); if (eo == null) { - eo = EOClassDescription.classDescriptionForEntityName(_currEntity.name()).createInstanceWithEditingContext(_currEC, gid); + eo = EOClassDescription.classDescriptionForEntityName(_currEntity.name()) + .createInstanceWithEditingContext(_currEC, gid); if (eo instanceof EOKeyValueCodingAdditions) - ((EOKeyValueCodingAdditions)eo).takeValuesFromDictionary(r); + ((EOKeyValueCodingAdditions) eo).takeValuesFromDictionary(r); else EOKeyValueCodingAdditions.DefaultImplementation.takeValuesFromDictionary(eo, r); } else { if (isRefreshingObjects()) { - //TODO: refresh object (how?) + // TODO: refresh object (how?) } } return eo; @@ -120,19 +121,19 @@ public class EODatabaseChannel { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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... + * 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 index af696fe..6ee58a5 100644 --- 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 @@ -42,13 +42,12 @@ 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 { + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ +public class EODatabaseContext extends EOCooperatingObjectStore implements NSLocking { private static Class _contextClass; protected EODatabase _database; @@ -79,7 +78,7 @@ public class EODatabaseContext public EODatabaseChannel availableChannel() { for (int i = 0; i < _channels.count(); i++) { - EODatabaseChannel c = (EODatabaseChannel)_channels.objectAtIndex(i); + EODatabaseChannel c = (EODatabaseChannel) _channels.objectAtIndex(i); if (!c.isFetchInProgress()) return c; } @@ -94,6 +93,7 @@ public class EODatabaseContext public static void setContextClassToRegister(Class contextClass) { _contextClass = contextClass; } + public static Class contextClassToRegister() { if (_contextClass == null) _contextClass = EODatabaseContext.class; @@ -109,21 +109,28 @@ public class EODatabaseContext } public void handleDroppedConnection() { - //TODO: unregister channels + // TODO: unregister channels adaptorContext().handleDroppedConnection(); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOCooperatingObjectStore#ownsGlobalID(net.wotonomy.control.EOGlobalID) + /* + * (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); + return (database().entityNamed(((EOKeyGlobalID) gid).entityName()) != null); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOCooperatingObjectStore#ownsObject(net.wotonomy.control.EOEnterpriseObject) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOCooperatingObjectStore#ownsObject(net.wotonomy.control + * .EOEnterpriseObject) */ public boolean ownsObject(EOEnterpriseObject eo) { if (eo.entityName() == null) @@ -131,8 +138,12 @@ public class EODatabaseContext return (database().entityNamed(eo.entityName()) != null); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOCooperatingObjectStore#handlesFetchSpecification(net.wotonomy.control.EOFetchSpecification) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOCooperatingObjectStore#handlesFetchSpecification(net. + * wotonomy.control.EOFetchSpecification) */ public boolean handlesFetchSpecification(EOFetchSpecification fspec) { String ename = fspec.entityName(); @@ -143,8 +154,13 @@ public class EODatabaseContext return adaptorContext().hasBusyChannels(); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOCooperatingObjectStore#prepareForSaveWithCoordinator(net.wotonomy.control.EOObjectStoreCoordinator, net.wotonomy.control.EOEditingContext) + /* + * (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 @@ -152,15 +168,21 @@ public class EODatabaseContext _currEC = ec; } - /* (non-Javadoc) - * @see net.wotonomy.control.EOCooperatingObjectStore#recordChangesInEditingContext() + /* + * (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) + /* + * (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 @@ -175,7 +197,7 @@ public class EODatabaseContext 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); + NSMutableDictionary d = (NSMutableDictionary) _manySnaps.objectForKey(gid); if (d == null) { d = new NSMutableDictionary(); _manySnaps.setObjectForKey(d, gid); @@ -187,13 +209,12 @@ public class EODatabaseContext 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); - }*/ + /* + * 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) { @@ -202,18 +223,20 @@ public class EODatabaseContext Enumeration enumeration = snaps.keyEnumerator(); while (enumeration.hasMoreElements()) { Object key = enumeration.nextElement(); - NSDictionary d = (NSDictionary)snaps.objectForKey(key); - NSMutableDictionary d2 = (NSMutableDictionary)_manySnaps.objectForKey(key); + 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 + // this could also be done with many calls to recordSnapshotForSourceGID d2.addEntriesFromDictionary(d); } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOCooperatingObjectStore#performChanges() */ public void performChanges() { @@ -221,70 +244,92 @@ public class EODatabaseContext } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOCooperatingObjectStore#commitChanges() */ public void commitChanges() { adaptorContext().commitTransaction(); } - /* (non-Javadoc) + /* + * (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) + /* + * (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) + /* + * (non-Javadoc) + * * @see net.wotonomy.foundation.NSLocking#lock() */ public void lock() { EOAccessLock.lock(); } - /* (non-Javadoc) + /* + * (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) + /* + * (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) { + 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); + 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) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOObjectStore#faultForGlobalID(net.wotonomy.control. + * EOGlobalID, net.wotonomy.control.EOEditingContext) */ - public /*EOEnterpriseObject*/Object faultForGlobalID(EOGlobalID gid, EOEditingContext ec) { + 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()); + 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) + /* + * (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) { + public /* EOEnterpriseObject */ Object faultForRawRow(Map row, String entityName, EOEditingContext ec) { EOEntity e = database().entityNamed(entityName); EOGlobalID gid = e.globalIDForRow(row); return faultForGlobalID(gid, ec); @@ -299,74 +344,81 @@ public class EODatabaseContext public void forgetSnapshotsForGlobalIDs(List gids) { for (int i = 0; i < gids.size(); i++) { - EOGlobalID g = (EOGlobalID)gids.get(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) + /* + * (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) { + 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()); + 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); + 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() ); - } + 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()) { + if (((EORelationship) p).isToMany()) { val = arrayFaultWithSourceGlobalID(gid, p.name(), ec); } else { - EOEntity dest = ((EORelationship)p).destinationEntity(); - EOKeyGlobalID kgid = (EOKeyGlobalID)dest.globalIDForRow(snap); + 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() ); - } + if (eo instanceof EOKeyValueCoding) { + ((EOKeyValueCoding) eo).takeValueForKey(val, p.name()); + } else { + EOKeyValueCodingSupport.takeValueForKey(eo, val, p.name()); + } } } } - /* (non-Javadoc) + /* + * (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) + /* + * (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) + /* + * (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); @@ -376,8 +428,11 @@ public class EODatabaseContext return _lockedObjects.containsObject(gid); } - /* (non-Javadoc) - * @see net.wotonomy.control.EOObjectStore#lockObjectWithGlobalID(net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + /* + * (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); @@ -385,7 +440,7 @@ public class EODatabaseContext return; if (!(gid instanceof EOKeyGlobalID)) return; - EOEntity e = database().entityNamed(((EOKeyGlobalID)gid).entityName()); + EOEntity e = database().entityNamed(((EOKeyGlobalID) gid).entityName()); EOQualifier q = e.qualifierForPrimaryKey(snap); EOFetchSpecification fspec = new EOFetchSpecification(e.name(), q, null); fspec.setLocksObjects(true); @@ -394,48 +449,53 @@ public class EODatabaseContext 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) + /* + * (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); + 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 + 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); + // 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 + handler = new EOAccessArrayFaultHandler((EOKeyGlobalID) gid, relationName, this, ec); + // TODO: fire the fault an return the value } else return value; } - //Get the relationship + // 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()); + throw new IllegalStateException( + "Cannot find relationship named " + relationName + " in entity " + entity.name()); - //create a fetch specification for this + // 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); + 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); + q = (EOQualifier) subq.objectAtIndex(0); } else { q = new EOAndQualifier(subq); } @@ -448,8 +508,12 @@ public class EODatabaseContext return res; } - /* (non-Javadoc) - * @see net.wotonomy.control.EOObjectStore#objectsWithFetchSpecification(net.wotonomy.control.EOFetchSpecification, net.wotonomy.control.EOEditingContext) + /* + * (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(); @@ -464,21 +528,28 @@ public class EODatabaseContext return arr; } - /* (non-Javadoc) - * @see net.wotonomy.control.EOObjectStore#refaultObject(java.lang.Object, net.wotonomy.control.EOGlobalID, net.wotonomy.control.EOEditingContext) + /* + * (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)); - } + // 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) + /* + * (non-Javadoc) + * + * @see + * net.wotonomy.control.EOObjectStore#saveChangesInEditingContext(net.wotonomy. + * control.EOEditingContext) */ public void saveChangesInEditingContext(EOEditingContext ec) { prepareForSaveWithCoordinator(null, ec); @@ -508,7 +579,7 @@ public class EODatabaseContext public NSDictionary snapshotForGlobalID(EOGlobalID gid) { NSDictionary d = null; if (_simpleSnaps != null) { - d = (NSDictionary)_simpleSnaps.objectForKey(gid); + d = (NSDictionary) _simpleSnaps.objectForKey(gid); } if (d == null) d = database().snapshotForGlobalID(gid); @@ -518,8 +589,8 @@ public class EODatabaseContext public NSArray snapshotForSourceGlobalID(EOGlobalID gid, String name) { NSArray a = null; if (_manySnaps != null) { - NSDictionary d = (NSDictionary)_manySnaps.objectForKey(gid); - a = (NSArray)d.objectForKey(name); + NSDictionary d = (NSDictionary) _manySnaps.objectForKey(gid); + a = (NSArray) d.objectForKey(name); } if (a == null) a = database().snapshotForSourceGlobalID(gid, name); @@ -536,33 +607,32 @@ public class EODatabaseContext } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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.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.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.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... + * 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 index 596180e..d8bd726 100644 --- 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 @@ -18,11 +18,11 @@ package net.wotonomy.access; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public class EODatabaseOperation { public static final int AdaptorLockOperator = 0; @@ -38,11 +38,10 @@ public class EODatabaseOperation { } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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. + * 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 index 4adc4a1..47a31c0 100644 --- 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 @@ -42,14 +42,14 @@ 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 $ -*/ + * 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(); @@ -80,55 +80,55 @@ public class EOEntity implements EOPropertyListEncoding { 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")); + _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"); + _internalInfo = (NSDictionary) dict.objectForKey("internalInfo"); if (dict.objectForKey("userInfo") != null) - _userInfo = (NSDictionary)dict.objectForKey("userInfo"); + _userInfo = (NSDictionary) dict.objectForKey("userInfo"); - //Read the attributes - NSArray atr = (NSArray)dict.objectForKey("attributes"); + // Read the attributes + NSArray atr = (NSArray) dict.objectForKey("attributes"); for (int i = 0; i < atr.count(); i++) { - NSDictionary d = (NSDictionary)atr.objectAtIndex(i); + NSDictionary d = (NSDictionary) atr.objectAtIndex(i); EOAttribute atrib = new EOAttribute(d, this); addAttribute(atrib); } - //Set the primary key - atr = (NSArray)dict.objectForKey("primaryKeyAttributes"); + // 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)); + EOAttribute a = attributeNamed((String) atr.objectAtIndex(i)); pka.addObject(a); } _pkAttributes = new NSArray(pka); _pkAttributeNames = atr; - //attributes used for locking + // attributes used for locking _lockingAttributes.removeAllObjects(); - atr = (NSArray)dict.objectForKey("attributesUsedForLocking"); + atr = (NSArray) dict.objectForKey("attributesUsedForLocking"); for (int i = 0; i < atr.count(); i++) { - String x = (String)atr.objectAtIndex(i); + String x = (String) atr.objectAtIndex(i); EOAttribute a = attributeNamed(x); _lockingAttributes.addObject(a); } - //class properties - atr = (NSArray)dict.objectForKey("classProperties"); + // 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"); + // 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); + NSDictionary d = (NSDictionary) atr.objectAtIndex(i); EORelationship rel = new EORelationship(d, this); addRelationship(rel); } @@ -169,7 +169,7 @@ public class EOEntity implements EOPropertyListEncoding { public EOFetchSpecification fetchSpecificationNamed(String name) { loadFetchSpecifications(); - return (EOFetchSpecification)_fetchSpecs.objectForKey(name); + return (EOFetchSpecification) _fetchSpecs.objectForKey(name); } public NSArray fetchSpecificationNames() { @@ -177,8 +177,8 @@ public class EOEntity implements EOPropertyListEncoding { return _fetchSpecs.allKeys(); } - /** Loads fetch specifications from the .fspec file, - * if one exists. + /** + * Loads fetch specifications from the .fspec file, if one exists. */ private void loadFetchSpecifications() { if (_loadedFetchSpecs) @@ -187,7 +187,7 @@ public class EOEntity implements EOPropertyListEncoding { if (model().path() == null) return; File f = new File(model().path()); - //Read the fetch specification file, if it exists + // Read the fetch specification file, if it exists f = new File(f, name() + ".fspec"); if (!f.exists()) return; @@ -207,11 +207,11 @@ public class EOEntity implements EOPropertyListEncoding { throw new IllegalArgumentException("Cannot read dictionary from " + f); NSArray keys = fdict.allKeys(); - //Unarchive the fetch specification + // 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); + String k = (String) keys.objectAtIndex(i); + EOFetchSpecification fs = (EOFetchSpecification) unarch.decodeObjectForKey(k); if (fs != null) _fetchSpecs.setObjectForKey(fs, k); } @@ -222,7 +222,7 @@ public class EOEntity implements EOPropertyListEncoding { } public EOAttribute attributeNamed(String name) { - return (EOAttribute)_attributes.objectForKey(name); + return (EOAttribute) _attributes.objectForKey(name); } public NSArray flattenedAttributes() { @@ -232,6 +232,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setClassName(String name) { _className = name; } + public String className() { return _className; } @@ -239,6 +240,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setName(String name) { _name = name; } + public String name() { return _name; } @@ -246,6 +248,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setExternalName(String name) { _externalName = name; } + public String externalName() { return _externalName; } @@ -274,7 +277,9 @@ public class EOEntity implements EOPropertyListEncoding { _classPropertyOneRelationships.removeObject(rel); } - /** Returns the relationships from this entity to other entities. + /** + * Returns the relationships from this entity to other entities. + * * @return An array of the relationships of this entity. */ public NSArray relationships() { @@ -282,8 +287,9 @@ public class EOEntity implements EOPropertyListEncoding { } public EORelationship relationshipNamed(String name) { - return (EORelationship)_relations.objectForKey(name); + return (EORelationship) _relations.objectForKey(name); } + public EOModel model() { return _model; } @@ -291,6 +297,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setPrimaryKeyAtributes(NSArray pk) { _pkAttributes = pk; } + public NSArray primaryKeyAttributes() { return _pkAttributes; } @@ -299,7 +306,7 @@ public class EOEntity implements EOPropertyListEncoding { if (_pkAttributeNames.count() != _pkAttributes.count()) { NSMutableArray arr = new NSMutableArray(); for (int i = 0; i < _pkAttributes.count(); i++) { - EOAttribute a = (EOAttribute)_pkAttributes.objectAtIndex(i); + EOAttribute a = (EOAttribute) _pkAttributes.objectAtIndex(i); arr.addObject(a.name()); } _pkAttributeNames = new NSArray(arr); @@ -319,6 +326,7 @@ public class EOEntity implements EOPropertyListEncoding { _lockingAttributes.removeAllObjects(); _lockingAttributes.addObjectsFromArray(value); } + public NSArray attributesUsedForLocking() { return new NSArray(_lockingAttributes); } @@ -331,18 +339,19 @@ public class EOEntity implements EOPropertyListEncoding { _classPropertyOneRelationships.removeAllObjects(); _classPropertyManyRelationships.removeAllObjects(); for (int i = 0; i < value.count(); i++) { - EOProperty o = (EOProperty)value.objectAtIndex(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()) + if (((EORelationship) o).isToMany()) _classPropertyManyRelationships.addObject(o); else _classPropertyOneRelationships.addObject(o); } } } + public NSArray classProperties() { if (_classProperties == null) { if (_classPropertyNames == null) @@ -353,7 +362,7 @@ public class EOEntity implements EOPropertyListEncoding { NSMutableArray ones = new NSMutableArray(); NSMutableArray manies = new NSMutableArray(); for (int i = 0; i < _classPropertyNames.count(); i++) { - String name = (String)_classPropertyNames.objectAtIndex(i); + String name = (String) _classPropertyNames.objectAtIndex(i); EOAttribute a = attributeNamed(name); EORelationship r = relationshipNamed(name); if (a != null) { @@ -385,8 +394,8 @@ public class EOEntity implements EOPropertyListEncoding { 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); + for (int i = 0; i < _classPropertyAttributes.count(); i++) { + EOAttribute a = (EOAttribute) _classPropertyAttributes.objectAtIndex(i); arr.addObject(a.name()); } return arr; @@ -396,8 +405,8 @@ public class EOEntity implements EOPropertyListEncoding { 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); + for (int i = 0; i < _classPropertyManyRelationships.count(); i++) { + EOAttribute a = (EOAttribute) _classPropertyManyRelationships.objectAtIndex(i); arr.addObject(a.name()); } return arr; @@ -407,8 +416,8 @@ public class EOEntity implements EOPropertyListEncoding { 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); + for (int i = 0; i < _classPropertyOneRelationships.count(); i++) { + EOAttribute a = (EOAttribute) _classPropertyOneRelationships.objectAtIndex(i); arr.addObject(a.name()); } return arr; @@ -417,6 +426,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setIsAbstractEntity(boolean flag) { _isAbstract = flag; } + public boolean isAbstractEntity() { return _isAbstract; } @@ -424,12 +434,14 @@ public class EOEntity implements EOPropertyListEncoding { 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; } @@ -462,8 +474,9 @@ public class EOEntity implements EOPropertyListEncoding { } /** - * Creates a global ID for a row. The row must have values - * for all the primary key attributes. + * 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. */ @@ -471,7 +484,7 @@ public class EOEntity implements EOPropertyListEncoding { 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)); + Number n = (Number) row.get(pknames.objectAtIndex(0)); gid = new EOIntegralKeyGlobalID(name(), n); } else { Object[] vals = new Object[pknames.count()]; @@ -485,15 +498,15 @@ public class EOEntity implements EOPropertyListEncoding { } /** - * Returns a dictionary with the primary key values contained in - * the global id. + * 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(); + Object[] vals = ((EOKeyGlobalID) gid).keyValues(); NSArray pknames = primaryKeyAttributeNames(); return new NSDictionary(vals, pknames.toArray()); } @@ -503,14 +516,14 @@ public class EOEntity implements EOPropertyListEncoding { EOQualifier q = null; NSMutableArray subq = new NSMutableArray(pknames.count()); for (int i = 0; i < pknames.count(); i++) { - String key = (String)pknames.objectAtIndex(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); + q = (EOQualifier) subq.objectAtIndex(0); else q = new EOAndQualifier(subq); return q; @@ -519,6 +532,7 @@ public class EOEntity implements EOPropertyListEncoding { public void setUserInfo(NSDictionary value) { _userInfo = value; } + public NSDictionary userInfo() { return _userInfo; } @@ -531,20 +545,20 @@ public class EOEntity implements EOPropertyListEncoding { dict.setObjectForKey(externalName(), "externalName"); dict.setObjectForKey(className(), "className"); - //Encode attributes + // Encode attributes NSMutableArray arr = new NSMutableArray(_attributes.allValues()); for (int i = 0; i < _attributes.count(); i++) { - EOAttribute a = (EOAttribute)arr.objectAtIndex(i); + EOAttribute a = (EOAttribute) arr.objectAtIndex(i); NSMutableDictionary d = new NSMutableDictionary(); a.encodeIntoPropertyList(d); arr.replaceObjectAtIndex(i, d); } dict.setObjectForKey(arr, "attributes"); - //Encode relationships + // 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); + EORelationship r = (EORelationship) arr.objectAtIndex(i); NSMutableDictionary d = new NSMutableDictionary(); r.encodeIntoPropertyList(d); arr.replaceObjectAtIndex(i, d); @@ -552,29 +566,29 @@ public class EOEntity implements EOPropertyListEncoding { dict.setObjectForKey(arr, "relationships"); } - //Fetch specifications + // 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); + 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 + // 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()); + arr.replaceObjectAtIndex(i, ((EOAttribute) arr.objectAtIndex(i)).name()); dict.setObjectForKey(arr, "attributesUsedForLocking"); - if (_userInfo != null && _userInfo.count() > 0 ) + if (_userInfo != null && _userInfo.count() > 0) dict.setObjectForKey(_userInfo, "userInfo"); - if (_internalInfo != null && _internalInfo.count() > 0 ) + if (_internalInfo != null && _internalInfo.count() > 0) dict.setObjectForKey(_internalInfo, "internalInfo"); } @@ -584,54 +598,54 @@ public class EOEntity implements EOPropertyListEncoding { return null; EORelationship r = null; EOEntity e = this; - for (int i = 0; i < comps.count()-1; i++) { - String name = (String)comps.objectAtIndex(i); + 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()); + 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. + * $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. + * 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 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 + * 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.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.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.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.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.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.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.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. + * 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 index a235c99..0485497 100644 --- 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 @@ -26,10 +26,10 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSMutableArray; /** -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public class EOEntityClassDescription extends EOClassDescription { protected EOEntity _entity; @@ -47,7 +47,7 @@ public class EOEntityClassDescription extends EOClassDescription { NSArray arr = entity().attributes(); NSMutableArray a = new NSMutableArray(arr.count()); for (int i = 0; i < arr.count(); i++) { - EOAttribute atrib = (EOAttribute)arr.objectAtIndex(i); + EOAttribute atrib = (EOAttribute) arr.objectAtIndex(i); a.addObject(atrib); } return a; @@ -61,7 +61,7 @@ public class EOEntityClassDescription extends EOClassDescription { NSArray arr = entity().relationships(); NSMutableArray a = new NSMutableArray(arr.count()); for (int i = 0; i < arr.count(); i++) { - EORelationship r = (EORelationship)arr.objectAtIndex(i); + EORelationship r = (EORelationship) arr.objectAtIndex(i); if (r.isToMany()) a.addObject(r); } @@ -72,21 +72,21 @@ public class EOEntityClassDescription extends EOClassDescription { NSArray arr = entity().relationships(); NSMutableArray a = new NSMutableArray(arr.count()); for (int i = 0; i < arr.count(); i++) { - EORelationship r = (EORelationship)arr.objectAtIndex(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. + /** + * 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); + EOAttribute atrib = (EOAttribute) arr.objectAtIndex(i); if (!atrib.isDerived()) a.addObject(atrib); } @@ -128,7 +128,7 @@ public class EOEntityClassDescription extends EOClassDescription { NSArray arr = entity().relationships(); NSMutableArray a = new NSMutableArray(arr.count()); for (int i = 0; i < arr.count(); i++) { - EORelationship r = (EORelationship)arr.objectAtIndex(i); + EORelationship r = (EORelationship) arr.objectAtIndex(i); if (r.isToMany() && !r.isFlattened()) a.addObject(r); } @@ -139,7 +139,7 @@ public class EOEntityClassDescription extends EOClassDescription { NSArray arr = entity().relationships(); NSMutableArray a = new NSMutableArray(arr.count()); for (int i = 0; i < arr.count(); i++) { - EORelationship r = (EORelationship)arr.objectAtIndex(i); + EORelationship r = (EORelationship) arr.objectAtIndex(i); if (!r.isToMany() && !r.isFlattened()) a.addObject(r); } @@ -147,7 +147,7 @@ public class EOEntityClassDescription extends EOClassDescription { } public Object createInstanceWithEditingContext(EOEditingContext ec, EOGlobalID gid) { - if (theClass == null) { + if (theClass == null) { try { theClass = Class.forName(entity().className()); } catch (ClassNotFoundException ex) { @@ -161,25 +161,26 @@ public class EOEntityClassDescription extends EOClassDescription { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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 + * 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 index 6c48a3f..435d6a1 100644 --- 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 @@ -20,12 +20,12 @@ 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 $ -*/ + * 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; @@ -41,9 +41,8 @@ public class EOGeneralAdaptorException extends RuntimeException implements java. public EOGeneralAdaptorException(String selectorName, String className, String msg) { super(msg); - _userInfo = new NSDictionary( - new Object[]{ selectorName, className }, - new Object[]{ "selectorName", "className" }); + _userInfo = new NSDictionary(new Object[] { selectorName, className }, + new Object[] { "selectorName", "className" }); } public NSDictionary userInfo() { @@ -52,11 +51,9 @@ public class EOGeneralAdaptorException extends RuntimeException implements java. } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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 + * 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 index 693a7d0..01326ac 100644 --- 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 @@ -18,13 +18,13 @@ 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 $ -*/ + * 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; @@ -45,12 +45,10 @@ public class EOJoin { } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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. + * 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 index 46fc8d0..9c77d45 100644 --- 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 @@ -31,18 +31,18 @@ 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 $ -*/ + * 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" + // This array contains dictionaries with "className" and "name" protected NSMutableArray _entities = new NSMutableArray(); protected NSMutableDictionary _entitiesByName = new NSMutableDictionary(); protected NSMutableDictionary _entitiesByClass = new NSMutableDictionary(); @@ -86,24 +86,25 @@ public class EOModel { throw new IllegalArgumentException("Cannot read index.eomodeld"); } NSDictionary d = NSPropertyListSerialization.dictionaryForString(x); - String version = (String)d.objectForKey("EOModelVersion"); + 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(); + 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")); + _storedProcedureNames.addObjectsFromArray((NSArray) d.objectForKey("storedProcedures")); if (d.objectForKey("internalInfo") != null) - _internalInfo = (NSDictionary)d.objectForKey("internalInfo"); + _internalInfo = (NSDictionary) d.objectForKey("internalInfo"); if (d.objectForKey("userInfo") != null) - _userInfo = (NSDictionary)d.objectForKey("userInfo"); + _userInfo = (NSDictionary) d.objectForKey("userInfo"); entityNamed("EOPrototypes"); } public void setConnectionDictionary(NSDictionary dict) { _connectionDictionary = dict; } + public NSDictionary connectionDictionary() { return _connectionDictionary; } @@ -130,10 +131,10 @@ public class EOModel { } public EOStoredProcedure storedProcedureNamed(String name) { - EOStoredProcedure proc = (EOStoredProcedure)_storedProcedures.objectForKey(name); - //if we can't find it, check if we should load it + 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 + // try to read it from file File f = new File(new File(_path), name + ".storedProcedure"); if (!f.exists()) return null; @@ -149,9 +150,10 @@ public class EOModel { } NSDictionary plist = NSPropertyListSerialization.dictionaryForString(sdict); if (plist == null) - throw new IllegalArgumentException("File " + f + " does not contain a valid stored procedure property list."); + throw new IllegalArgumentException( + "File " + f + " does not contain a valid stored procedure property list."); proc = new EOStoredProcedure(plist, this); - //add it to our collection + // add it to our collection _storedProcedures.setObjectForKey(proc, proc.name()); } return proc; @@ -159,8 +161,8 @@ public class EOModel { public NSArray storedProcedures() { if (_storedProcedures.count() < _storedProcedureNames.count()) { - for (int i=0; i<_storedProcedureNames.count(); i++) - storedProcedureNamed((String)_storedProcedureNames.objectAtIndex(i)); + for (int i = 0; i < _storedProcedureNames.count(); i++) + storedProcedureNamed((String) _storedProcedureNames.objectAtIndex(i)); } return _storedProcedures.allValues(); } @@ -172,6 +174,7 @@ public class EOModel { public void setAdaptorName(String value) { _adaptorName = value; } + public String adaptorName() { return _adaptorName; } @@ -181,8 +184,8 @@ public class EOModel { 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"))); + NSDictionary d = (NSDictionary) _entities.objectAtIndex(i); + es.addObject(entityNamed((String) d.objectForKey("name"))); } return es; } @@ -192,18 +195,18 @@ public class EOModel { return _entitiesByName.allKeys(); NSMutableArray names = new NSMutableArray(); for (int i = 0; i < _entities.count(); i++) { - NSDictionary d = (NSDictionary)_entities.objectAtIndex(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); + 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); + NSDictionary d = (NSDictionary) _entities.objectAtIndex(i); if (d.objectForKey("name").equals(name)) exists = true; } @@ -242,6 +245,7 @@ public class EOModel { public void setModelGroup(EOModelGroup group) { _group = group; } + public EOModelGroup modelGroup() { return _group; } @@ -266,7 +270,8 @@ public class EOModel { 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"))) + if (kids[i].isFile() && (fname.endsWith(".plist") || fname.endsWith(".eomodeld") + || fname.endsWith(".fspec") || fname.endsWith(".storedProcedure"))) kids[i].delete(); } } else @@ -275,7 +280,7 @@ public class EOModel { f.mkdirs(); File kid = new File(f, "index.eomodeld"); - //encode the index file + // encode the index file NSMutableDictionary d = new NSMutableDictionary(); d.setObjectForKey(_adaptorName, "adaptorName"); d.setObjectForKey("2.1", "EOModelVersion"); @@ -287,23 +292,24 @@ public class EOModel { if (_storedProcedureNames.count() > 0) d.setObjectForKey(_storedProcedureNames, "storedProcedures"); - //encode the entity list + // 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); + String key = (String) enumeration.nextElement(); + EOEntity ent = (EOEntity) _entitiesByName.objectForKey(key); md.removeAllObjects(); ent.encodeIntoPropertyList(md); File plist; - NSDictionary fetchSpecs = (NSDictionary)md.objectForKey("fetchSpecificationDictionary"); + 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")); + String ps = NSPropertyListSerialization + .stringForPropertyList(md.objectForKey("fetchSpecificationDictionary")); try { PrintStream stream = new PrintStream(new FileOutputStream(plist)); stream.println(ps); @@ -321,14 +327,13 @@ public class EOModel { 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" })); + // 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 + // write the index file String s = NSPropertyListSerialization.stringForPropertyList(d); try { PrintStream stream = new PrintStream(new FileOutputStream(kid)); @@ -338,9 +343,10 @@ public class EOModel { throw new RuntimeException("Cannot write index.eomodeld"); } - //write the stored procedures + // write the stored procedures for (int i = 0; i < _storedProcedureNames.count(); i++) { - EOStoredProcedure proc = (EOStoredProcedure)_storedProcedures.objectForKey(_storedProcedureNames.objectAtIndex(i)); + EOStoredProcedure proc = (EOStoredProcedure) _storedProcedures + .objectForKey(_storedProcedureNames.objectAtIndex(i)); kid = new File(f, proc.name() + ".storedProcedure"); d.removeAllObjects(); proc.encodeIntoPropertyList(d); @@ -358,39 +364,36 @@ public class EOModel { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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.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.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.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.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.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.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. + * 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 + */ \ 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 index dbab09d..70c1088 100644 --- 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 @@ -24,14 +24,14 @@ 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 $ -*/ + * 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; @@ -46,14 +46,14 @@ public class EOModelGroup { 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."); + 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); + 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); + throw new IllegalArgumentException( + "Cannot add model " + model.name() + " to group because it contains entity named " + ename); } _models.setObjectForKey(model, model.name()); } @@ -73,6 +73,7 @@ public class EOModelGroup { public static void setDefaultGroup(EOModelGroup group) { _defaultGroup = group; } + public static EOModelGroup defaultGroup() { if (_defaultGroup == null) { _defaultGroup = globalModelGroup(); @@ -83,7 +84,7 @@ public class EOModelGroup { public static EOModelGroup globalModelGroup() { if (_globalGroup == null) { _globalGroup = new EOModelGroup(); - //TODO: read all frameworks and get models from them + // TODO: read all frameworks and get models from them } return _globalGroup; } @@ -95,7 +96,7 @@ public class EOModelGroup { public EOEntity entityNamed(String name) { java.util.Enumeration enumeration = _models.objectEnumerator(); while (enumeration.hasMoreElements()) { - EOModel m = (EOModel)enumeration.nextElement(); + EOModel m = (EOModel) enumeration.nextElement(); if (m.entityNamed(name) != null) return m.entityNamed(name); } @@ -103,7 +104,7 @@ public class EOModelGroup { } public EOModel modelNamed(String name) { - return (EOModel)_models.objectForKey(name); + return (EOModel) _models.objectForKey(name); } public NSArray modelNames() { @@ -117,7 +118,7 @@ public class EOModelGroup { public EOModel modelWithPath(String path) { java.util.Enumeration enumeration = _models.objectEnumerator(); while (enumeration.hasMoreElements()) { - EOModel m = (EOModel)enumeration.nextElement(); + EOModel m = (EOModel) enumeration.nextElement(); if (m.path() != null && m.path().equals(path)) return m; } @@ -127,7 +128,7 @@ public class EOModelGroup { public EOStoredProcedure storedProcedureNamed(String name) { java.util.Enumeration enumeration = _models.objectEnumerator(); while (enumeration.hasMoreElements()) { - EOModel m = (EOModel)enumeration.nextElement(); + EOModel m = (EOModel) enumeration.nextElement(); if (m.storedProcedureNamed(name) != null) return m.storedProcedureNamed(name); } @@ -144,12 +145,12 @@ public class EOModelGroup { public void loadAllModelObjects() { java.util.Enumeration enumeration = _models.objectEnumerator(); while (enumeration.hasMoreElements()) { - EOModel m = (EOModel)enumeration.nextElement(); - //this causes all entities to be loaded + EOModel m = (EOModel) enumeration.nextElement(); + // this causes all entities to be loaded NSArray ents = m.entities(); - for (int i=0; i 1) { @@ -189,16 +191,16 @@ public interface EOQualifierSQLGeneration { } public String sqlStringForSQLExpression(EOQualifier qualifier, EOSQLExpression exp) { - EOOrQualifier q = (EOOrQualifier)qualifier; + 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); + 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) + if (i < qus.count() - 1) buf.append(" OR "); } if (qus.count() > 1) { @@ -220,22 +222,23 @@ public interface EOQualifierSQLGeneration { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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 + * 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 index a5a207f..4efd068 100644 --- 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 @@ -21,13 +21,15 @@ 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 $ -*/ + * 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; @@ -60,14 +62,14 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public EORelationship(NSDictionary dict, Object obj) { super(); - _entity = (EOEntity)obj; - setName((String)dict.objectForKey("name")); + _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"); + setDefinition((String) dict.objectForKey("definition")); + String delrule = (String) dict.objectForKey("deleteRule"); if (delrule != null) { if (delrule.equals("EODeleteRuleCascade")) setDeleteRule(0); @@ -78,7 +80,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding else if (delrule.equals("EODeleteRuleNullify")) setDeleteRule(0); } - delrule = (String)dict.objectForKey("joinSemantic"); + delrule = (String) dict.objectForKey("joinSemantic"); if (delrule != null) { if (delrule.equals("EOInnerJoin")) setJoinSemantic(InnerJoin); @@ -89,13 +91,13 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding else if (delrule.equals("EORightOuterJoin")) setJoinSemantic(RightOuterJoin); } - delrule = (String)dict.objectForKey("batchCount"); + delrule = (String) dict.objectForKey("batchCount"); if (delrule != null) setNumberOfToManyFaultsToBatchFetch(Integer.parseInt(delrule)); - NSDictionary d = (NSDictionary)dict.objectForKey("userInfo"); + NSDictionary d = (NSDictionary) dict.objectForKey("userInfo"); if (d != null) _userInfo = d; - d = (NSDictionary)dict.objectForKey("internalInfo"); + d = (NSDictionary) dict.objectForKey("internalInfo"); if (d != null) _internalInfo = d; plist = dict; @@ -104,6 +106,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setName(String name) { _name = name; } + public String name() { return _name; } @@ -125,7 +128,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding if (_destination == null && plist != null) { EOModel model = _entity.model(); EOModelGroup group = model.modelGroup(); - String destEntity = (String)plist.objectForKey("destination"); + String destEntity = (String) plist.objectForKey("destination"); if (group != null) _destination = group.entityNamed(destEntity); else @@ -137,6 +140,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setOwnsDestination(boolean flag) { _ownsDestination = flag; } + public boolean ownsDestination() { return _ownsDestination; } @@ -144,6 +148,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setToMany(boolean flag) { _isToMany = flag; } + public boolean isToMany() { return _isToMany; } @@ -151,6 +156,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setIsMandatory(boolean flag) { _isMandatory = flag; } + public boolean isMandatory() { return _isMandatory; } @@ -158,6 +164,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setPropagatesPrimaryKey(boolean flag) { _propagatesPrimaryKey = flag; } + public boolean propagatesPrimaryKey() { return _propagatesPrimaryKey; } @@ -165,6 +172,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setDeleteRule(int value) { _deleteRule = value; } + public int deleteRule() { return _deleteRule; } @@ -172,6 +180,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setJoinSemantic(int value) { _joinSemantic = value; } + public int joinSemantic() { return _joinSemantic; } @@ -179,17 +188,18 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding 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"); + 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"); + 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); @@ -202,6 +212,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setDefinition(String def) { _definition = def; } + public String definition() { return _definition; } @@ -218,7 +229,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding EORelationship r = null; EOEntity e = entity(); for (int i = 0; i < comps.count(); i++) { - String name = (String)comps.objectAtIndex(i); + String name = (String) comps.objectAtIndex(i); r = e.relationshipNamed(name); if (r == null) return false; @@ -242,6 +253,7 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding public void setUserInfo(NSDictionary value) { _userInfo = value; } + public NSDictionary userInfo() { return _userInfo; } @@ -260,18 +272,18 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding 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; + 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"); @@ -280,10 +292,10 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding else { NSMutableArray jarr = new NSMutableArray(joins().count()); for (int i = 0; i < _joins.count(); i++) { - EOJoin j = (EOJoin)_joins.objectAtIndex(i); + EOJoin j = (EOJoin) _joins.objectAtIndex(i); NSDictionary d = new NSDictionary( - new Object[]{ j.sourceAttribute().name(), j.destinationAttribute().name() }, - new Object[]{ "sourceAttribute", "destinationAttribute" }); + new Object[] { j.sourceAttribute().name(), j.destinationAttribute().name() }, + new Object[] { "sourceAttribute", "destinationAttribute" }); jarr.addObject(d); } dict.setObjectForKey(jarr, "joins"); @@ -292,28 +304,25 @@ public class EORelationship extends EOProperty implements EOPropertyListEncoding } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:13 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.4 2003/08/09 01:35:35 chochos implement EOPropertyListEncoding * - * Revision 1.3 2003/08/08 06:51:53 chochos - * isFlattened() works + * 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.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. + * 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 + */ \ 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 index 4cb15e5..c2b6e76 100644 --- 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 @@ -37,10 +37,10 @@ import net.wotonomy.foundation.NSTimestamp; import net.wotonomy.foundation.NSTimestampFormatter; /** -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public abstract class EOSQLExpression { public static final String BindVariableAttributeKey = "BindVariableAttribute"; @@ -54,9 +54,8 @@ public abstract class EOSQLExpression { 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' - }; + 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; @@ -77,7 +76,7 @@ public abstract class EOSQLExpression { private EOSQLExpression() { super(); } - + public EOSQLExpression(EOEntity entity) { super(); _entity = entity; @@ -88,7 +87,7 @@ public abstract class EOSQLExpression { } public String _aliasForRelationshipPath(String path) { - return (String)_aliasesByRelationshipPath.objectForKey(path); + return (String) _aliasesByRelationshipPath.objectForKey(path); } protected NSTimestampFormatter _defaultDateFormatter() { @@ -115,7 +114,7 @@ public abstract class EOSQLExpression { _entity = value; } - public String _sqlStringForJoinSemanticMatchSemantic(int semantic,int match) { + public String _sqlStringForJoinSemanticMatchSemantic(int semantic, int match) { return null; } @@ -129,11 +128,13 @@ public abstract class EOSQLExpression { return _valueListString; } - public void addBindVariableDictionary( NSDictionary dict ) { + 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 + /** + * 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) { @@ -158,9 +159,11 @@ public abstract class EOSQLExpression { public void addOrderByAttributeOrdering(EOSortOrdering order) { String sql = sqlStringForAttributeNamed(order.key()); - if (order.selector().equals(EOSortOrdering.CompareCaseInsensitiveAscending) || order.selector().equals(EOSortOrdering.CompareCaseInsensitiveDescending)) + 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)) + if (order.selector().equals(EOSortOrdering.CompareCaseInsensitiveAscending) + || order.selector().equals(EOSortOrdering.CompareAscending)) sql += " ASC"; else sql += " DESC"; @@ -205,8 +208,8 @@ public abstract class EOSQLExpression { return s; } - public String assembleInsertStatementWithRow(NSDictionary row, - String tableList, String columnList, String valueList) { + public String assembleInsertStatementWithRow(NSDictionary row, String tableList, String columnList, + String valueList) { String sql = "INSERT INTO " + tableList; if (columnList != null) sql += " (" + columnList + ")"; @@ -223,9 +226,9 @@ public abstract class EOSQLExpression { 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) { + 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; @@ -242,7 +245,8 @@ public abstract class EOSQLExpression { return sql; } - public String assembleUpdateStatementWithRow(NSDictionary row, EOQualifier q, String tableList, String updateList, String whereClause) { + 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; @@ -280,14 +284,14 @@ public abstract class EOSQLExpression { } /** - * Returns the received string wrapped in single quotes, - * with any quotes or escape chars found inside it - * properly escaped. + * 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--) { + for (int i = buf.length() - 1; i >= 0; i--) { if (buf.charAt(i) == sqlEscapeChar()) { buf.insert(i, sqlEscapeChar()); i++; @@ -306,10 +310,10 @@ public abstract class EOSQLExpression { if (value == null || value == NSKeyValueCoding.NullValue) return "NULL"; if (value instanceof String) - return formatStringValue((String)value); + return formatStringValue((String) value); if (value instanceof Number) - return sqlStringForNumber((Number)value); - //TODO: format timestamps + return sqlStringForNumber((Number) value); + // TODO: format timestamps return value.toString(); } @@ -338,7 +342,7 @@ public abstract class EOSQLExpression { } public String orderByString() { - if (_orderByString == null) + if (_orderByString == null) return null; return _orderByString.toString(); } @@ -356,7 +360,7 @@ public abstract class EOSQLExpression { StringBuffer values = new StringBuffer("("); Enumeration enumeration = row.keyEnumerator(); while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); + String key = (String) enumeration.nextElement(); EOAttribute a = _entity.attributeNamed(key); cols.append(a.columnName()); values.append(formatValueForAttribute(row.objectForKey(key), a)); @@ -379,30 +383,31 @@ public abstract class EOSQLExpression { q = fspec.qualifier(); order = fspec.sortOrderings(); } - //Assemble the column list (this yields the alias list) + // 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 + addSelectListAttribute((EOAttribute) atts.objectAtIndex(i)); + // assemble the where string if (q != null) { if (q instanceof EOQualifierSQLGeneration) - setWhereClauseString(sqlStringForQualifier((EOQualifierSQLGeneration)q)); + setWhereClauseString(sqlStringForQualifier((EOQualifierSQLGeneration) q)); else { EOQualifierSQLGeneration.Support sup = EOQualifierSQLGeneration.Support.supportForClass(q.getClass()); setWhereClauseString(sup.sqlStringForSQLExpression(q, this)); } } - //assemble the join string + // assemble the join string joinExpression(); - //assemble the order by string + // 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); + EOSortOrdering so = (EOSortOrdering) order.objectAtIndex(i); addOrderByAttributeOrdering(so); } } - //create the statement + // create the statement setStatement(assembleSelectStatementWithAttributes(atts, lock, q, order, "SELECT", listString(), - tableListWithRootEntity(_entity), whereClauseString(), joinClauseString(), orderByString(), lockClause())); + tableListWithRootEntity(_entity), whereClauseString(), joinClauseString(), orderByString(), + lockClause())); } /** Build an UPDATE statement with the given information. */ @@ -410,10 +415,11 @@ public abstract class EOSQLExpression { StringBuffer buf = new StringBuffer(); Enumeration enumeration = row.keyEnumerator(); while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); + 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()); + 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)); @@ -423,7 +429,8 @@ public abstract class EOSQLExpression { if (q != null) { setWhereClauseString(sqlStringForQualifier(null)); } - setStatement(assembleUpdateStatementWithRow(row, q, _entity.externalName(), buf.toString(), whereClauseString())); + setStatement( + assembleUpdateStatementWithRow(row, q, _entity.externalName(), buf.toString(), whereClauseString())); } public void setStatement(String statement) { @@ -453,6 +460,7 @@ public abstract class EOSQLExpression { public static void setUseQuotedExternalNames(boolean flag) { _quoteExternalNames = flag; } + /** @deprecated Use the instance method externalNameQuoteCharacter instead. */ public static boolean useQuotedExternalNames() { return _quoteExternalNames; @@ -481,33 +489,33 @@ public abstract class EOSQLExpression { public String sqlPatternFromShellPatternWithEscapeCharacter(String pattern, char escape) { StringBuffer buf = new StringBuffer(pattern); int idx = 0; - //escape all '%' + // escape all '%' do { idx = buf.indexOf("%"); if (idx == 0) buf.insert(escape, 0); - else if (idx > 0 && buf.charAt(idx-1) != escape) + else if (idx > 0 && buf.charAt(idx - 1) != escape) buf.insert(escape, idx); } while (idx >= 0); - //escape all '_' + // escape all '_' do { idx = buf.indexOf("_"); if (idx == 0) buf.insert(escape, 0); - else if (idx > 0 && buf.charAt(idx-1) != escape) + else if (idx > 0 && buf.charAt(idx - 1) != escape) buf.insert(escape, idx); } while (idx >= 0); - //substitute all '*' + // substitute all '*' do { idx = buf.indexOf("*"); if (idx >= 0) - buf.replace(idx, idx+1, "%"); + buf.replace(idx, idx + 1, "%"); } while (idx >= 0); - //substitute all '?' + // substitute all '?' do { idx = buf.indexOf("?"); if (idx >= 0) - buf.replace(idx, idx+1, "_"); + buf.replace(idx, idx + 1, "_"); } while (idx >= 0); return buf.toString(); } @@ -515,7 +523,7 @@ public abstract class EOSQLExpression { public String sqlStringForAttribute(EOAttribute attr) { if (_aliasesByEntityName == null) _aliasesByEntityName = new NSMutableDictionary(); - String alias = (String)_aliasesByEntityName.objectForKey(attr.entity().name()); + String alias = (String) _aliasesByEntityName.objectForKey(attr.entity().name()); if (alias == null) { alias = "t" + (_aliasesByEntityName.count() + 1); _aliasesByEntityName.setObjectForKey(alias, attr.entity().name()); @@ -531,20 +539,21 @@ public abstract class EOSQLExpression { 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. + * 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. + * @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); + EORelationship rel = (EORelationship) path.objectAtIndex(i); if (i > 0) buf.append('.'); buf.append(rel.name()); @@ -558,7 +567,8 @@ public abstract class EOSQLExpression { public String sqlStringForConjoinedQualifiers(NSArray qualifiers) { EOAndQualifier q = new EOAndQualifier(qualifiers); - return EOQualifierSQLGeneration.Support.supportForClass(EOAndQualifier.class).sqlStringForSQLExpression(q, this); + return EOQualifierSQLGeneration.Support.supportForClass(EOAndQualifier.class).sqlStringForSQLExpression(q, + this); } public String sqlStringForData(NSData data) { @@ -580,11 +590,13 @@ public abstract class EOSQLExpression { } public String sqlStringForKeyComparisonQualifier(EOKeyComparisonQualifier q) { - return EOQualifierSQLGeneration.Support.supportForClass(EOKeyComparisonQualifier.class).sqlStringForSQLExpression(q, this); + return EOQualifierSQLGeneration.Support.supportForClass(EOKeyComparisonQualifier.class) + .sqlStringForSQLExpression(q, this); } public String sqlStringForKeyValueQualifier(EOKeyValueQualifier q) { - return EOQualifierSQLGeneration.Support.supportForClass(EOKeyValueQualifier.class).sqlStringForSQLExpression(q, this); + return EOQualifierSQLGeneration.Support.supportForClass(EOKeyValueQualifier.class).sqlStringForSQLExpression(q, + this); } public String sqlStringForNegatedQualifier(EOQualifier q) { @@ -622,7 +634,8 @@ public abstract class EOSQLExpression { return "<="; } else if (sel == EOQualifier.QualifierOperatorGreaterThanOrEqualTo) { return ">="; - } else if (sel == EOQualifier.QualifierOperatorLike || sel == EOQualifier.QualifierOperatorCaseInsensitiveLike) { + } else if (sel == EOQualifier.QualifierOperatorLike + || sel == EOQualifier.QualifierOperatorCaseInsensitiveLike) { return " like "; } return sel.name(); @@ -646,7 +659,7 @@ public abstract class EOSQLExpression { if (_aliasesByEntityName.count() > 0) { Enumeration enumeration = _aliasesByEntityName.keyEnumerator(); while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); + String key = (String) enumeration.nextElement(); if (!key.equals(root.name())) { buf.append(", "); buf.append(key); @@ -668,30 +681,31 @@ public abstract class EOSQLExpression { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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.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.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.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. + * 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 index 6bdbffe..4e20a7a 100644 --- 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 @@ -25,11 +25,11 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ public class EOSQLExpressionFactory { protected EOAdaptor _adaptor; @@ -49,8 +49,9 @@ public class EOSQLExpressionFactory { } /** - * Creates an instance of the adaptor's expression class, - * with entity assigned to it. + * 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. */ @@ -58,13 +59,14 @@ public class EOSQLExpressionFactory { EOSQLExpression expr = null; if (_instantiator == null) { try { - _instantiator = _expressionClass.getConstructor(new Class[]{ EOEntity.class }); + _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."); + throw new IllegalArgumentException("The expression class " + _expressionClass.getName() + + " has no constructor with an entity parameter."); } } try { - expr = (EOSQLExpression)_instantiator.newInstance(new Object[]{ entity }); + expr = (EOSQLExpression) _instantiator.newInstance(new Object[] { entity }); } catch (Exception ex) { throw new IllegalArgumentException("Cannot create new expression of class " + _expressionClass.getName()); } @@ -87,7 +89,8 @@ public class EOSQLExpressionFactory { return expr; } - public EOSQLExpression selectStatementForAttributes(NSArray atts, boolean lock, EOFetchSpecification fspec, EOEntity entity) { + public EOSQLExpression selectStatementForAttributes(NSArray atts, boolean lock, EOFetchSpecification fspec, + EOEntity entity) { EOSQLExpression expr = createExpression(entity); expr.prepareSelectExpressionWithAttributes(atts, lock, fspec); return expr; @@ -102,7 +105,7 @@ public class EOSQLExpressionFactory { public EOSQLExpression expressionForString(String sql) { EOSQLExpression expr = null; try { - expr = (EOSQLExpression)_expressionClass.newInstance(); + expr = (EOSQLExpression) _expressionClass.newInstance(); } catch (Exception e) { return null; } @@ -116,16 +119,17 @@ public class EOSQLExpressionFactory { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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. + * 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 index f38ec2a..6fab43a 100644 --- 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 @@ -22,13 +22,14 @@ 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 $ -*/ + * 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; @@ -42,23 +43,23 @@ public class EOStoredProcedure implements EOPropertyListEncoding { public EOStoredProcedure(NSDictionary dict, Object obj) { super(); if (obj instanceof EOModel) - _model = (EOModel)obj; - NSArray a = (NSArray)dict.objectForKey("arguments"); + _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); + 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")); + setName((String) dict.objectForKey("name")); + setExternalName((String) dict.objectForKey("externalName")); if (dict.objectForKey("userInfo") != null) - setUserInfo((NSDictionary)dict.objectForKey("userInfo")); + setUserInfo((NSDictionary) dict.objectForKey("userInfo")); if (dict.objectForKey("internalInfo") != null) - _internalInfo = (NSDictionary)dict.objectForKey("internalInfo"); + _internalInfo = (NSDictionary) dict.objectForKey("internalInfo"); } public EOStoredProcedure(String withName) { @@ -69,6 +70,7 @@ public class EOStoredProcedure implements EOPropertyListEncoding { public void setName(String value) { _name = value; } + public String name() { return _name; } @@ -76,6 +78,7 @@ public class EOStoredProcedure implements EOPropertyListEncoding { public void setExternalName(String value) { _externalName = value; } + public String externalName() { return _externalName; } @@ -83,6 +86,7 @@ public class EOStoredProcedure implements EOPropertyListEncoding { public void setArguments(NSArray value) { _arguments = value; } + public NSArray arguments() { return _arguments; } @@ -94,6 +98,7 @@ public class EOStoredProcedure implements EOPropertyListEncoding { public void setUserInfo(NSDictionary info) { _userInfo = info; } + public NSDictionary userInfo() { return _userInfo; } @@ -107,7 +112,7 @@ public class EOStoredProcedure implements EOPropertyListEncoding { NSMutableArray arr = new NSMutableArray(_arguments.count()); NSMutableDictionary d = null; for (int i = 0; i < _arguments.count(); i++) { - EOAttribute a = (EOAttribute)_arguments.objectAtIndex(i); + EOAttribute a = (EOAttribute) _arguments.objectAtIndex(i); d = new NSMutableDictionary(); a.encodeIntoPropertyList(d); arr.addObject(d); @@ -117,25 +122,23 @@ public class EOStoredProcedure implements EOPropertyListEncoding { } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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. + * Revision 1.1 2003/08/07 02:41:04 chochos these don't do much for now. * -*/ \ No newline at end of file + */ \ 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 index ddaacf5..1989582 100644 --- 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 @@ -35,728 +35,638 @@ 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(); + * 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 ) - { + 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. - * - * - */ + 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 index 59e135b..3a2bcaf 100644 --- 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 @@ -25,195 +25,165 @@ 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.

-* -* 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.

-* -* 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 ) - { + * A class that extends NSArray to intercept any accessor calls in order to + * defer loading until the last possible moment.
+ *
+ * + * 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.
+ *
+ * + * 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 ) ); + 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+"]"; - } + } + } + + 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. + * $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. + * 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 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.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.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.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.2 2001/05/06 22:22:55 mpowers Debugging. * - * Revision 1.1 2001/05/05 23:05:42 mpowers - * Implemented Array Faults. + * 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 index 8123668..1d62881 100644 --- 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 @@ -7,181 +7,137 @@ 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.

-* -* 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 ) ); + * 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.
+ *
+ * + * 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 ); + + /** + * 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 ) ); - } + 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 ) - { + /** + * 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 deleteObject ( Object anObject ) - { + /** + * This implementation does nothing. + */ + public void insertObject(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 ); + /** + * This implementation does nothing. + */ + public void deleteObject(Object anObject) { + } - /** - * 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 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 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; - } + /** + * 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 index 049eb33..9d8eb96 100644 --- 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 @@ -28,88 +28,75 @@ 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; - } + * 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"); + 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); + NSDictionary d = (NSDictionary) a.objectAtIndex(i); EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d); - EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua); + EOQualifier q = (EOQualifier) EOQualifier.decodeWithKeyValueUnarchiver(ua); if (q != null) l.addObject(q); } @@ -119,11 +106,11 @@ public class EOAndQualifier extends EOQualifier 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); + 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); + ((EOKeyValueArchiving) q).encodeWithKeyValueArchiver(ar2); arr.addObject(ar2.dictionary()); } else throw new WotonomyException("Cannot archive instance of " + q.getClass().getName()); @@ -134,34 +121,31 @@ public class EOAndQualifier extends EOQualifier } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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.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.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. + * 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 index cd07ebb..79019de 100644 --- 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 @@ -28,574 +28,476 @@ 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. -*

-* -* 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. -*

-* -* 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".

-* -* 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"; + * EOClassDescription provides meta-information about a class and is used to + * customize certain behaviors within wotonomy and specifically within editing + * contexts and object stores.
+ *
+ * + * 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.
+ *
+ * + * 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". + *
+ *
+ * + * 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 ); - } + /** + * 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; + } -/* - 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 ) - { + /** + * 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; - } - + 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. + * $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. + * 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. + * 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 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.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.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.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.8 2001/12/01 23:51:45 mpowers Corrected createWithEditingContext. * - * Revision 1.7 2001/11/25 22:43:38 mpowers - * Corrected createInstanceWithEditingContext. + * 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.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.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.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.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.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. + * 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 index 072f867..98a8a60 100644 --- 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 @@ -24,59 +24,58 @@ 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 $ -*/ + * 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 abstract class EOCooperatingObjectStore extends EOObjectStore implements NSLocking { - public EOCooperatingObjectStore() { - } + public EOCooperatingObjectStore() { + } - public abstract boolean ownsGlobalID(EOGlobalID eoglobalid); + public abstract boolean ownsGlobalID(EOGlobalID eoglobalid); - public abstract boolean ownsObject(EOEnterpriseObject eoenterpriseobject); + public abstract boolean ownsObject(EOEnterpriseObject eoenterpriseobject); - public abstract boolean handlesFetchSpecification(EOFetchSpecification eofetchspecification); + public abstract boolean handlesFetchSpecification(EOFetchSpecification eofetchspecification); - public abstract void prepareForSaveWithCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator, EOEditingContext eoeditingcontext); + public abstract void prepareForSaveWithCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator, + EOEditingContext eoeditingcontext); - public abstract void recordChangesInEditingContext(); + public abstract void recordChangesInEditingContext(); - public abstract void recordUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary); + public abstract void recordUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary); - public abstract void performChanges(); + public abstract void performChanges(); - public abstract void commitChanges(); + public abstract void commitChanges(); - public abstract void rollbackChanges(); + public abstract void rollbackChanges(); - public abstract NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject); + public abstract NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject); - public abstract void lock(); + public abstract void lock(); - public abstract void unlock(); + 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. + * $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. + * 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 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.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 + * 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 index 6b262cb..4c7ca40 100644 --- 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 @@ -28,646 +28,540 @@ 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(); - } + * 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. + * $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. + * 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 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.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.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. + * 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 index c7e5284..9d3c255 100644 --- 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 @@ -21,144 +21,122 @@ 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.

-* -* 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.

-* -* 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; - } + * 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.
+ *
+ * + * 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. + *
+ *
+ * + * 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. + * $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. + * 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 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.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.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.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.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.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. + * 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 index 2e350f1..489d779 100644 --- 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 @@ -28,312 +28,267 @@ 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; - } + * 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; -/* - public EOEntity entity() {} -*/ + /** + * 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 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 ); - } - } + /** + * 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. + * $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. + * 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 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. + * 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 index c87a097..6ce9808 100644 --- 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 @@ -19,30 +19,26 @@ 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 ); + * 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. + * $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. + * 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 index 758fb40..b947a82 100644 --- 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 @@ -22,131 +22,108 @@ 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 ); - } + * 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. + * $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. + * 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 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.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.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. + * 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 index 6b9b9c3..cea0d9e 100644 --- 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 @@ -26,298 +26,246 @@ 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). -*

-* -* 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. -*

-* -* 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 $ -*/ + * 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).
+ *
+ * + * 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.
+ *
+ * + * 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 } ); +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 () - { + private LinkedList priorityQueue; + + /** + * Default constructor. + */ + public EODelayedObserverQueue() { willRunLater = false; - priorityQueue = new LinkedList(); - } + priorityQueue = new LinkedList(); + } - /** - * Returns the system default observer queue. - */ - public static EODelayedObserverQueue defaultObserverQueue () - { - if ( defaultObserverQueue == null ) - { - defaultObserverQueue = new EODelayedObserverQueue(); + /** + * 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 ) - { + /** + * Removes the specified observer from the queue. + */ + public void dequeueObserver(EODelayedObserver anObserver) { //System.out.println( "dequeueObserver: " + anObserver ); - //synchronized ( priorityQueue ) - //{ - priorityQueue.remove( 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 + /** + * 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; + } else { + // place in the delayed observer queue + + // synchronized ( priorityQueue ) + // { + int i = 0; + int priority = observer.priority(); + Object o; - Iterator iterator = priorityQueue.iterator(); + 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 ); - - //} + // 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 ) - { + /** + * 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() ) - { + 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(); - } + 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 ) - { + * 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 ); + 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 ) - { + * 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(); - } + 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(); - } - } - } - } - -} + + /** + * 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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. + * 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 index b01727d..3a39035 100644 --- 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 @@ -42,3206 +42,2593 @@ import net.wotonomy.foundation.internal.WotonomyException; //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.

-* -* 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; + * EOEditingContext provides transactional support for fetching, editing, and + * committing changes made on a collection of objects to a parent object store. + *
+ *
+ * + * 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 + // 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 ); + // 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(); + } + + /** + * 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 ) + 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 { - 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 ); + 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."); - } -*/ + i = updatedObjects.indexOfIdenticalObject(anObject); + if (i != NSArray.NotFound) { + updatedObjects.removeObjectAtIndex(i); + } - /** - * 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 ) - { + // 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; - + + 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 () - { + 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; + 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; - } + 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)); + } + } + } -/* - public static Object initObjectWithCoder ( - Object anObject, - NSCoder aCoder ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ + // add to buffer + if (updatedObjectsBuffer.indexOfIdenticalObject(anObject) == NSArray.NotFound) { + updatedObjectsBuffer.addObject(anObject); + } + } - /** - * 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; - } + // static methods -/* - 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 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; + } - 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 ); + // 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 ); + 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 ) - { + } + + /** + * 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 ); + 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 ) - { + * 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(); + 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 ); - - } - + + // 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 112531c..db03a22 100644 --- 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 @@ -22,198 +22,180 @@ 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 $ -*/ + * 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(); + 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. + * $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. + * 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 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. + * 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 index 19dc2df..e407492 100644 --- 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 @@ -19,14 +19,13 @@ 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 $ -*/ + * 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; @@ -38,7 +37,7 @@ public abstract class EOFaultHandler { public static EOFaultHandler handlerForFault(Object obj) { if (!(obj instanceof EOFaulting)) throw new IllegalArgumentException("Object must implement EOFaulting"); - return ((EOFaulting)obj).faultHandler(); + return ((EOFaulting) obj).faultHandler(); } public static boolean isFault(Object obj) { @@ -46,20 +45,20 @@ public abstract class EOFaultHandler { return false; boolean isit = (obj instanceof EOFaulting); if (isit) - isit = ((EOFaulting)obj).isFault(); + 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); + ((EOFaulting) obj).turnIntoFault(handler); } public static void clearFault(Object obj) { if (!(obj instanceof EOFaulting)) throw new IllegalArgumentException("Object must implement EOFaulting"); - ((EOFaulting)obj).clearFault(); + ((EOFaulting) obj).clearFault(); } public Class targetClass() { @@ -87,16 +86,15 @@ public abstract class EOFaultHandler { } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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.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. + * 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 index 95b3e35..f468949 100644 --- 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 @@ -19,61 +19,56 @@ 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 ); + * 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. + * $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. + * 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 index 71f3b78..9ff353e 100644 --- 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 @@ -30,439 +30,378 @@ 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 $ -*/ + * 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; - } + 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 + // flags if (isDeep()) arch.encodeObject("YES", "isDeep"); arch.encodeObject(qualifier(), "qualifier"); @@ -479,11 +418,11 @@ public class EOFetchSpecification implements EOKeyValueArchiving { if (usesDistinct()) arch.encodeObject("YES", "usesDistinct"); - //encode arrays - if (sortOrderings() != null) { + // 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); + EOSortOrdering so = (EOSortOrdering) sortOrderings().objectAtIndex(i); EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); so.encodeWithKeyValueArchiver(ar2); arr.addObject(ar2.dictionary()); @@ -500,18 +439,18 @@ public class EOFetchSpecification implements EOKeyValueArchiving { public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver unarch) { EOFetchSpecification fs = new EOFetchSpecification(); - fs.setEntityName((String)unarch.decodeObjectForKey("entityName")); + 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) { + // 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"); + NSDictionary so = (NSDictionary) arr.objectAtIndex(i); + String selname = (String) so.objectForKey("selectorName"); NSSelector selector = EOSortOrdering.CompareAscending; if (selname.startsWith("compareDescending")) selector = EOSortOrdering.CompareDescending; @@ -519,47 +458,43 @@ public class EOFetchSpecification implements EOKeyValueArchiving { selector = EOSortOrdering.CompareCaseInsensitiveAscending; else if (selname.startsWith("compareCaseInsensitiveDescending")) selector = EOSortOrdering.CompareCaseInsensitiveDescending; - EOSortOrdering eoso = new EOSortOrdering((String)so.objectForKey("key"), selector); + EOSortOrdering eoso = new EOSortOrdering((String) so.objectForKey("key"), selector); orderings.addObject(eoso); } fs.setSortOrderings(orderings); } - //raw rows - arr = (NSArray)unarch.decodeObjectForKey("rawRowKeyPaths"); + // 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")); + // 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. + * $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. + * 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 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.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.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.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. + * 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 index 4358ecf..9d732f9 100644 --- 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 @@ -22,130 +22,112 @@ 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 ); - } + * 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. + * $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. + * 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 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.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. + * 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 index 1b4ccbe..346362c 100644 --- 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 @@ -21,63 +21,51 @@ 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(); - } + * 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. + * $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.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.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.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. + * 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 index ee7aac1..14f7925 100644 --- 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 @@ -18,11 +18,11 @@ package net.wotonomy.control; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public class EOIntegralKeyGlobalID extends EOKeyGlobalID { protected Number keyValue; @@ -32,28 +32,36 @@ public class EOIntegralKeyGlobalID extends EOKeyGlobalID { keyValue = value; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#keyValues() */ public Object[] keyValues() { - return new Object[]{ keyValue }; + return new Object[] { keyValue }; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#_keyValuesNoCopy() */ public Object[] _keyValuesNoCopy() { - return new Object[]{ keyValue }; + return new Object[] { keyValue }; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#keyCount() */ public int keyCount() { return 1; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOGlobalID#isTemporary() */ public boolean isTemporary() { @@ -62,11 +70,9 @@ public class EOIntegralKeyGlobalID extends EOKeyGlobalID { } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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 + * 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 index 97baaab..ef9c244 100644 --- 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 @@ -22,13 +22,11 @@ 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 { + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ +public class EOKeyComparisonQualifier extends EOQualifier implements EOKeyValueArchiving, EOQualifierEvaluation { private String _leftKey; private String _rightKey; @@ -54,29 +52,31 @@ public class EOKeyComparisonQualifier } /** - * 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. - */ + * 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()); + 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(); + return ((Boolean) _selector.invoke(lvalue, rvalue)).booleanValue(); } catch (Exception exc) { - throw new WotonomyException( exc ); + throw new WotonomyException(exc); } } - /* (non-Javadoc) - * @see net.wotonomy.control.EOKeyValueArchiving#encodeWithKeyValueArchiver(net.wotonomy.control.EOKeyValueArchiver) + /* + * (non-Javadoc) + * + * @see net.wotonomy.control.EOKeyValueArchiving#encodeWithKeyValueArchiver(net. + * wotonomy.control.EOKeyValueArchiver) */ public void encodeWithKeyValueArchiver(EOKeyValueArchiver arch) { arch.encodeObject("EOKeyComparisonQualifier", "class"); @@ -108,10 +108,10 @@ public class EOKeyComparisonQualifier } public static Object decodeWithKeyValueArchiver(EOKeyValueUnarchiver arch) { - String k = (String)arch.decodeObjectForKey("key"); - String v = (String)arch.decodeObjectForKey("value"); + String k = (String) arch.decodeObjectForKey("key"); + String v = (String) arch.decodeObjectForKey("value"); NSSelector sel = null; - String sname = (String)arch.decodeObjectForKey("selectorName"); + String sname = (String) arch.decodeObjectForKey("selectorName"); if (sname.equals("isEqualTo:")) sel = EOQualifier.QualifierOperatorEqual; else if (sname.equals("isNotEqualTo:")) @@ -136,16 +136,15 @@ public class EOKeyComparisonQualifier } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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 + * 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 index 76c4d05..7034f03 100644 --- 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 @@ -28,119 +28,117 @@ 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 $ -*/ + * 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 { +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 EOKeyGlobalID(String entityName, int hashCode) { + super(); + _entityName = entityName; + _hash = hashCode; + } - protected void _prepClone(EOKeyGlobalID gid) { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + 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 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 static EOKeyGlobalID _defaultGlobalIDWithEntityName(String s, Object aobj[]) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public String entityName() { - return _entityName; - } + public String entityName() { + return _entityName; + } - public String _literalEntityName() { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + public String _literalEntityName() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public boolean _isFinal() { - 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 void _setGuessedEntityName(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public String _guessedEntityName() { - 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 void _setSubEntityName(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public String _subEntityName() { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + public String _subEntityName() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public int hashCode() { - return _hash; - } + public int hashCode() { + return _hash; + } - public abstract Object[] keyValues(); + public abstract Object[] keyValues(); - public abstract Object[] _keyValuesNoCopy(); + public abstract Object[] _keyValuesNoCopy(); - public abstract int keyCount(); + public abstract int keyCount(); - public NSArray keyValuesArray() { - return new NSArray(keyValues()); - } + public NSArray keyValuesArray() { + return new NSArray(keyValues()); + } - public static Object decodeObject(NSCoder nscoder) { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + 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 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 void encodeWithCoder(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public Class classForCoder() { - 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"); - } + 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. + * $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. + * 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 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.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/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. + * 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 index 3e2f808..e759c1a 100644 --- 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 @@ -23,15 +23,14 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSSelector; /** -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ + * @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 }); + // for the delegate + public static final NSSelector sel = new NSSelector("referenceToEncodeForObject", new Class[] { Object.class }); NSMutableDictionary dict = new NSMutableDictionary(); private Object _delegate; @@ -57,7 +56,7 @@ public class EOKeyValueArchiver { } if (obj instanceof EOKeyValueArchiving) { EOKeyValueArchiver arch = new EOKeyValueArchiver(); - ((EOKeyValueArchiving)obj).encodeWithKeyValueArchiver(arch); + ((EOKeyValueArchiving) obj).encodeWithKeyValueArchiver(arch); dict.setObjectForKey(arch.dictionary(), key); return; } @@ -85,22 +84,23 @@ public class EOKeyValueArchiver { 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. + * $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. + * 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 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) + * 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 index 0d9f78c..97a1338 100644 --- 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 @@ -18,24 +18,23 @@ 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) + * 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 $ -*/ + * @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. + * $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) + * 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 index b3cf926..490738c 100644 --- 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 @@ -21,109 +21,95 @@ 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.

-* -* On an object that implements this interface, wotonomy -* will call these methods, and otherwise use the static -* methods on EOKeyValueCodingSupport.

-* -* EOKeyValueCodingSupport implements the default behaviors -* for each of these methods, so classes implementing this -* interface can call those methods to acheive the same -* behavior.

-* -* 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.

-* -* 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 ); + * EOKeyValueCoding defines an interface for classes that need to have more + * control over the wotonomy's property introspection facilities.
+ *
+ * + * On an object that implements this interface, wotonomy will call these + * methods, and otherwise use the static methods on EOKeyValueCodingSupport. + *
+ *
+ * + * EOKeyValueCodingSupport implements the default behaviors for each of these + * methods, so classes implementing this interface can call those methods to + * acheive the same behavior.
+ *
+ * + * 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.
+ *
+ * + * 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 ); + /** + * 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 ); + /** + * 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 ); + /** + * 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 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 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 ); + /** + * 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. + * $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.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.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. + * 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 index bc48f58..d1bb2c0 100644 --- 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 @@ -25,152 +25,127 @@ 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.

-* -* @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." ); - } - } + * EOKeyValueCodingAdditions defines an interface for classes that need to have + * more control over the wotonomy's bulk property copying and cloning + * facilities.
+ *
+ * + * @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. + * $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. + * 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 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.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.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.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.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.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. + * 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 index 89e3e91..70cf63c 100644 --- 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 @@ -25,211 +25,161 @@ import net.wotonomy.foundation.internal.NullPrimitiveException; import net.wotonomy.foundation.internal.WotonomyException; /** -* EOKeyValueCodingSupport defines default behavior for -* classes implementing EOKeyValueSupport.

-* -* 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.

- * - * If the property does not exist, this method calls - * handleQueryWithUnboundKey on the object if it - * implements EOKeyValueCoding, otherwise calls - * handleQueryWithUnboundKey on this class.

- */ - 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 ); - } + * EOKeyValueCodingSupport defines default behavior for classes implementing + * EOKeyValueSupport.
+ *
+ * + * 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. + *
+ *
+ * + * If the property does not exist, this method calls handleQueryWithUnboundKey + * on the object if it implements EOKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class.
+ *
+ */ + 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. + * $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. + * 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 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.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.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.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.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. + * 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 index 799955e..4ced471 100644 --- 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 @@ -24,100 +24,80 @@ 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 + " )"; - } + * 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"); @@ -159,10 +139,10 @@ public class EOKeyValueQualifier extends EOQualifier } public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver arch) { - String k = (String)arch.decodeObjectForKey("key"); + String k = (String) arch.decodeObjectForKey("key"); Object v = arch.decodeObjectForKey("value"); NSSelector sel = null; - String sname = (String)arch.decodeObjectForKey("selectorName"); + String sname = (String) arch.decodeObjectForKey("selectorName"); if (sname.equals("isEqualTo:")) sel = EOQualifier.QualifierOperatorEqual; else if (sname.equals("isNotEqualTo:")) @@ -188,46 +168,42 @@ public class EOKeyValueQualifier extends EOQualifier } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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. + * 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 index caed8a4..5238c70 100644 --- 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 @@ -22,17 +22,18 @@ 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 $ -*/ +/** + * 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 }; + protected static final Class[] METHOD_ARGS = new Class[] { EOKeyValueUnarchiver.class }; public EOKeyValueUnarchiver(NSDictionary archive) { super(); @@ -54,7 +55,7 @@ public class EOKeyValueUnarchiver { if (x == null) return 0; if (x instanceof Number) - return ((Number)x).intValue(); + return ((Number) x).intValue(); try { int i = Integer.parseInt(x.toString()); return i; @@ -68,7 +69,7 @@ public class EOKeyValueUnarchiver { if (x == null) return null; if (x instanceof NSDictionary) { - NSDictionary d = (NSDictionary)x; + NSDictionary d = (NSDictionary) x; if (d.objectForKey("class") != null) { String cname = d.objectForKey("class").toString(); Class _class = null; @@ -82,11 +83,11 @@ public class EOKeyValueUnarchiver { if (d.objectForKey("value") != null) return d.objectForKey("value").toString(); } else if (cname.equals("EONull")) { - return NSKeyValueCoding.NullValue; + return NSKeyValueCoding.NullValue; } else if (cname.equals("EOFetchSpecification")) { _class = EOFetchSpecification.class; } else if (cname.equals("EOKeyValueQualifier")) { - _class = EOKeyValueQualifier.class; + _class = EOKeyValueQualifier.class; } else if (cname.equals("EONotQualifier")) { _class = EONotQualifier.class; } else if (cname.equals("EOAndQualifier")) { @@ -95,12 +96,12 @@ public class EOKeyValueUnarchiver { _class = EOOrQualifier.class; } else if (cname.equals("EOSortOrdering")) { _class = EOSortOrdering.class; - } else if (cname.indexOf(".") < 0) { //Load a class without package + } 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 + // search for the class in access if (_class == null) { try { _class = Class.forName("net.wotonomy.access." + cname); @@ -117,7 +118,7 @@ public class EOKeyValueUnarchiver { return x; try { Method met = _class.getMethod("decodeWithKeyValueUnarchiver", METHOD_ARGS); - return met.invoke(null, new Object[]{ new EOKeyValueUnarchiver(d) }); + return met.invoke(null, new Object[] { new EOKeyValueUnarchiver(d) }); } catch (Exception ex) { ex.printStackTrace(); return x; @@ -144,22 +145,23 @@ public class EOKeyValueUnarchiver { 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. + * $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. + * 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 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 + * 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 index d086840..1068fd7 100644 --- 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 @@ -21,54 +21,46 @@ 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; + * 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; - } + public EONotQualifier(EOQualifier aQualifier) { + qualifier = aQualifier; + } - /** - * Returns the qualifier that this qualifier negates. - */ - public EOQualifier qualifier() - { - return qualifier; - } + /** + * 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)); - } + /** + * 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()); - } + /** + * 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"); + EOQualifier q = (EOQualifier) arch.decodeObjectForKey("qualifier"); if (q == null) return null; return new EONotQualifier(q); @@ -78,7 +70,7 @@ public class EONotQualifier extends EOQualifier arch.encodeObject("EONotQualifier", "class"); if (qualifier instanceof EOKeyValueArchiving) { EOKeyValueArchiver ar2 = new EOKeyValueArchiver(); - ((EOKeyValueArchiving)qualifier).encodeWithKeyValueArchiver(ar2); + ((EOKeyValueArchiving) qualifier).encodeWithKeyValueArchiver(ar2); arch.encodeObject(ar2.dictionary(), "qualifiers"); } else throw new WotonomyException("Cannot archive instance of " + qualifier.getClass().getName()); @@ -87,43 +79,37 @@ public class EONotQualifier extends EOQualifier } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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.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.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.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.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.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. + * 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 index 3b4544e..1ecce4d 100644 --- 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 @@ -23,91 +23,84 @@ 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.

-* -* Use of the static singleton method nullValue() is required -* by this implementation because Java cannot return a singleton -* instance from a constructor.

-* -* 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 () - { - } + * 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.
+ *
+ * + * Use of the static singleton method nullValue() is required by this + * implementation because Java cannot return a singleton instance from a + * constructor.
+ *
+ * + * 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(); - /** - * Constructor specifying name and object. - */ - public static EONullValue nullValue () - { - return instance; - } + /** + * Create a new instance of EONullValue. + */ + private EONullValue() { + } - /** - * 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; - } + /** + * 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. + * $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. + * 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 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.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.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. + * 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 index cd18e36..4f60045 100644 --- 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 @@ -24,297 +24,234 @@ import java.util.Map; import net.wotonomy.foundation.NSArray; /** -* EOObjectStore defines an object repository that tracks -* object creations, deletions, and updates made by -* EOEditingContexts.

-* -* A concrete implementation would probably write these -* changes to some kind of persistent storage, like a -* database.

-* -* 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"; + * EOObjectStore defines an object repository that tracks object creations, + * deletions, and updates made by EOEditingContexts.
+ *
+ * + * A concrete implementation would probably write these changes to some kind of + * persistent storage, like a database.
+ *
+ * + * 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"; - /** - * Key for the user info of ObjectsChangedInStoreNotifications. - * The key should retrieve an array of inserted EOGlobalIDs. - */ - public static final String InsertedKey = "inserted"; + /** + * Default constructor is responsible for initializing internal state. + */ + public EOObjectStore() { + } - /** - * 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"; + /** + * 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) { + } - /** - * Key for the user info of ObjectsChangedInStoreNotification. - * The key should retrieve an array of EOGlobalIDs. - */ - public static final String InvalidatedKey = "invalidated"; + /** + * 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); - /** - * 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"; + /** + * 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); - /** - * 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 () - { - } + /** + * 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); - /** - * 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 ); + /** + * 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index e9a3a6a..c2ad03d 100644 --- 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 @@ -27,178 +27,172 @@ 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 $ -*/ + * 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 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 EOObjectStoreCoordinator defaultCoordinator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public static synchronized void setDefaultCoordinator(EOObjectStoreCoordinator eoobjectstorecoordinator) { - 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 EOObjectStoreCoordinator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void dispose() { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + public void dispose() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - private NSArray _sources() { - 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 NSArray cooperatingObjectStores() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void addCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) { - 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 void removeCooperatingObjectStore(EOCooperatingObjectStore eocooperatingobjectstore) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public EOCooperatingObjectStore objectStoreForGlobalID(EOGlobalID eoglobalid) { - 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 objectStoreForObject(EOEnterpriseObject eoenterpriseobject) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public EOCooperatingObjectStore objectStoreForFetchSpecification(EOFetchSpecification eofetchspecification) { - 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"); - } + EOCooperatingObjectStore objectStoreForEntityNamed(String s) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void forwardUpdateForObject(EOEnterpriseObject eoenterpriseobject, NSDictionary nsdictionary) { - 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 NSDictionary valuesForKeys(NSArray nsarray, EOEnterpriseObject eoenterpriseobject) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void saveChangesInEditingContext(EOEditingContext eoeditingcontext) { - 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 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 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 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 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 /* 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 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 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 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 invalidateObjectsWithGlobalIDs(NSArray nsarray) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void invalidateAllObjects() { - 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 _objectsChangedInSubStore(NSNotification nsnotification) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public void _invalidatedAllObjectsInSubStore(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 void setUserInfo(NSDictionary nsdictionary) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } - public NSDictionary userInfo() { - throw new UnsupportedOperationException("Not Yet Implemented"); - } + public NSDictionary userInfo() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } /** - * @see net.wotonomy.control.EOObjectStore#faultForRawRow(Map, String, EOEditingContext) + * @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"); + 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) + * @see net.wotonomy.control.EOObjectStore#initializeObject(Object, EOGlobalID, + * EOEditingContext) */ - public void initializeObject( - Object anObject, - EOGlobalID aGlobalID, - EOEditingContext aContext) { - throw new UnsupportedOperationException("Not Yet Implemented"); + 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"); + throw new UnsupportedOperationException("Not Yet Implemented"); } - /** - * @see net.wotonomy.control.EOObjectStore#refaultObject(Object, EOGlobalID, EOEditingContext) + * @see net.wotonomy.control.EOObjectStore#refaultObject(Object, EOGlobalID, + * EOEditingContext) */ - public void refaultObject( - Object anObject, - EOGlobalID aGlobalID, - EOEditingContext aContext) { - throw new UnsupportedOperationException("Not Yet Implemented"); + 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 index d42130c..a8710ef 100644 --- 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 @@ -34,514 +34,423 @@ 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.

-* -* 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.

-* -* 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. - */ + * 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.
+ *
+ * + * 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.
+ *
+ * + * 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; - + + // 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 ) - { + * 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() ) - { + synchronized (instance()) { // find observer list - List observers = (List) - observableToObservers.get( new ReferenceKey( anObject ) ); - + List observers = (List) observableToObservers.get(new ReferenceKey(anObject)); + // if observer list not found, create and add item - if ( observers == null ) - { + if (observers == null) { observers = new ArrayList(); - observers.add( new WeakReference( anObserver ) ); - processQueue(); - observableToObservers.put( new ReferenceKey( anObject, queue ), observers ); + 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() ); + if (anObject instanceof Observable) { + ((Observable) anObject).addObserver(instance()); } - } - else // observer list found - scan for observer - if ( indexOf( observers, anObserver ) < 0 ) - { + } else // observer list found - scan for observer + if (indexOf(observers, anObserver) < 0) { // observer not found, register it - observers.add( new WeakReference( anObserver ) ); + observers.add(new WeakReference(anObserver)); } - - lastRequest = null; - lastResult = null; + + 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 ); + * 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 ); + * 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() ) - { + while (it.hasNext()) { o = (EOObserving) it.next(); - o.objectWillChange( anObject ); + 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; - + * 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() ) - { + while (it.hasNext()) { o = it.next(); - if ( aClass.isAssignableFrom( o.getClass() ) ) - { - return (EOObserving) o; + 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() - { + * 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 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 ) ); + * 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 ) - { + * 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; - + 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 ); - + result.remove(index); + // if last observer, unregister observable - if ( result.size() == 0 ) - { - processQueue(); - observableToObservers.remove( new ReferenceKey( anObject ) ); + 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 ); + * 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--; + * 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() - { + * 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 ) - { + * 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++ ) - { + for (int i = 0; i < len; i++) { // compare by reference - if ( anObject == ((Reference)aList.get(i)).get() ) - { + 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 ) - { + * 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); + } + } + } + /** - * Interface Observer - */ - public void update( Observable o, Object arg ) - { - notifyObserversObjectWillChange( o ); + * 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()); + } } - - /* 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+"]"; + + 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; - } - + 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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. + * 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 index e616a33..ce8c9a7 100644 --- 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 @@ -21,84 +21,67 @@ 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; - } + * 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 ObserverPriorityThird priority, - * which is the default. - */ - public EOObserverProxy ( - Object anObject, NSSelector aSelector ) - { - this( anObject, aSelector, ObserverPriorityThird ); - } + /** + * 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; + } - /** - * Returns the priority of this observer in the queue. - * This implementation returns the priority specified - * in the constructor. - */ - public int priority () - { - return priority; - } + /** + * 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(); + } + } - /** - * 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. + * $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. + * 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 index 8ec6e5c..d6a3101 100644 --- 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 @@ -19,33 +19,27 @@ 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 ); + * 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. + * $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.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. + * 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 index 7f4b1c6..fe31abe 100644 --- 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 @@ -28,90 +28,76 @@ 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; - } + * 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"); + 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); + NSDictionary d = (NSDictionary) a.objectAtIndex(i); EOKeyValueUnarchiver ua = new EOKeyValueUnarchiver(d); - EOQualifier q = (EOQualifier)EOQualifier.decodeWithKeyValueUnarchiver(ua); + EOQualifier q = (EOQualifier) EOQualifier.decodeWithKeyValueUnarchiver(ua); if (q != null) l.addObject(q); } @@ -121,11 +107,11 @@ public class EOOrQualifier extends EOQualifier 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); + 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); + ((EOKeyValueArchiving) q).encodeWithKeyValueArchiver(ar2); arr.addObject(ar2.dictionary()); } else throw new WotonomyException("Cannot archive instance of " + q.getClass().getName()); @@ -136,34 +122,31 @@ public class EOOrQualifier extends EOQualifier } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $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. + * 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 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.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.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.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.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.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. + * 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 index 6fffea4..32443fa 100644 --- 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 @@ -38,643 +38,504 @@ 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.

-* -* 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 "!="; - } - } - + * 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.
+ *
+ * + * 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"); + String cname = (String) ua.decodeObjectForKey("class"); if (cname.equals("EOKeyValueQualifier")) - return (EOQualifier)EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua); + return (EOQualifier) EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EOAndQualifier")) - return (EOQualifier)EOAndQualifier.decodeWithKeyValueUnarchiver(ua); + return (EOQualifier) EOAndQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EOOrQualifier")) - return (EOQualifier)EOOrQualifier.decodeWithKeyValueUnarchiver(ua); + return (EOQualifier) EOOrQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EONotQualifier")) - return (EOQualifier)EONotQualifier.decodeWithKeyValueUnarchiver(ua); + 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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. + * 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 index 87769b8..bbdc190 100644 --- 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 @@ -18,25 +18,23 @@ 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.

-* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * EOQualifiers that want to perform in-memory evaluation should implement this + * interface.
+ *
+ * + * @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. + * $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 + * 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 index 568b555..4b8845b 100644 --- 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 @@ -19,58 +19,48 @@ 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 ); + * 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 ); + /** + * 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 ); + /** + * 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. + * $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. + * 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 index 789b6da..e2ce595 100644 --- 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 @@ -30,154 +30,128 @@ 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; - } + * 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"); + String k = (String) arch.decodeObjectForKey("key"); + String sname = (String) arch.decodeObjectForKey("selectorName"); NSSelector sel = null; if (sname.equals("compareAscending:")) sel = CompareAscending; @@ -189,8 +163,8 @@ public class EOSortOrdering implements Serializable, EOKeyValueArchiving sel = CompareCaseInsensitiveAscending; else { if (sname.endsWith(":")) - sname = sname.substring(0, sname.length()-1); - sel = new NSSelector(sname, new Class[]{ Object.class }); + sname = sname.substring(0, sname.length() - 1); + sel = new NSSelector(sname, new Class[] { Object.class }); } return new EOSortOrdering(k, sel); } @@ -210,197 +184,147 @@ public class EOSortOrdering implements Serializable, EOKeyValueArchiving 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 ); - } - } + 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 44ba855..31351a9 100644 --- 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 @@ -21,199 +21,181 @@ 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.

-* -* 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 ) - { + * EOTemporaryGlobalID is a network-wide unique key. This is used by + * EOEditingContext to construct temporary ids when new objects are created. + *
+ *
+ * + * 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(); - } + // 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. + * $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.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.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.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. + * 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 index a0aa4db..3054d4c 100644 --- 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 @@ -19,54 +19,48 @@ 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(); + * 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. + * $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. + * 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 index cc91fd7..5fc500d 100644 --- 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 @@ -18,11 +18,11 @@ package net.wotonomy.control; /** -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ public class EOVectorKeyGlobalID extends EOKeyGlobalID { protected Object[] _keyValues; @@ -35,14 +35,18 @@ public class EOVectorKeyGlobalID extends EOKeyGlobalID { } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#keyValuesNoCopy() */ public Object[] _keyValuesNoCopy() { return _keyValues; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#_keyValues() */ public Object[] keyValues() { @@ -53,14 +57,18 @@ public class EOVectorKeyGlobalID extends EOKeyGlobalID { return v; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOKeyGlobalID#keyCount() */ public int keyCount() { return _keyValues.length; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see net.wotonomy.control.EOGlobalID#isTemporary() */ public boolean isTemporary() { @@ -69,11 +77,9 @@ public class EOVectorKeyGlobalID extends EOKeyGlobalID { } /* - * $Log$ - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $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 + * 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 index ee40f23..1a120a5 100644 --- 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 @@ -25,259 +25,210 @@ import java.util.Map; //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(); - } + * 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. + * $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. + * 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 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.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. + * 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 index 1aa2147..d052fcf 100644 --- 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 @@ -38,227 +38,169 @@ 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 ) - { + * 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 ); @@ -266,475 +208,361 @@ public class KeyValueCodingUtilities //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 - { + // 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(); + 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; -*/ + /* + * 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 ); - } - } - + } 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 - { + /** + * 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(); + 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; -*/ + /* + * 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 - { + } 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 ); - } - } - + 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. + * $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. + * 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. + * 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 19d39ff..ff66578 100644 --- 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 @@ -26,321 +26,272 @@ 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; - } - + * 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 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 ); - } + * 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 ); - } + * 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 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 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 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 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() ); - } - } + * 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; - + * 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() ) - { + 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 ); + 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() ); + 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--; + 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 ) ); + 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 ); - } + * 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 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 ); - } + * 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 ); - } - + * 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 ); - } + * 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 ); - } + * 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. + * $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. + * 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 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 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/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. + * 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 index 8b88615..f91e76d 100644 --- 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 @@ -19,39 +19,31 @@ 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 ); + * 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. + * $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.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. + * 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 index 76f7219..c02c3e2 100644 --- 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 @@ -34,516 +34,413 @@ 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 ); + * 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. } - else - if ( value instanceof Collection ) - { + + // 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 ); - } + 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); } - 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 + ")"); } - 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 (?) + 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 ); - } - } - + { + // 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. + * $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. + * 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 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index e12fda0..e0fe1eb 100644 --- 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 @@ -23,237 +23,202 @@ 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. -*
    -*
  • By default, Surrogate works like a Map, and reading and -* writing properties to a Surrogate gets and puts values in the -* Map.
  • -*
  • 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.
  • -*
  • 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.)
  • -*
  • Subclasses can override the get(Object) method to further -* customize the behavior of a Surrogate. -*
-* -* @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 -{ + * 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. + *
    + *
  • By default, Surrogate works like a Map, and reading and writing + * properties to a Surrogate gets and puts values in the Map.
  • + *
  • 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.
  • + *
  • 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.)
  • + *
  • Subclasses can override the get(Object) method to further customize the + * behavior of a Surrogate. + *
+ * + * @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() - { + * 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 delegate object. + */ + public Surrogate(Object[] aDelegateArray) { + setDelegates(aDelegateArray); } /** - * Constructor specifying a default value. - */ - public Surrogate( Object aDefault ) - { - setDefaultValue( aDefault ); + * 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 ); + * 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; + * 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 } ); + * 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; + * 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 ) - { + * 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() - { + * 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 ) - { + * 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 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 ); + * 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 ) - { + * 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 (i = 0; i < list.length; i++) { // for each delegate - properties = - Introspector.getReadPropertiesForObject( list[i] ); - for ( j = 0; j < properties.length; j++ ) - { + properties = Introspector.getReadPropertiesForObject(list[i]); + for (j = 0; j < properties.length; j++) { // if delegate has property - if ( properties[j].equals( aKey ) ) - { + if (properties[j].equals(aKey)) { // use this delegate - return Introspector.get( list[i], aKey.toString() ); + return Introspector.get(list[i], aKey.toString()); } } } - + // return from internal map - Object result = directGet( aKey ); - if ( result == null ) - { + Object result = directGet(aKey); + if (result == null) { // if not in map, return default object result = getDefaultValue(); } - return result; + 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 ) - { + + /** + * 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 (i = 0; i < list.length; i++) { // for each delegate - properties = - Introspector.getWritePropertiesForObject( list[i] ); - for ( j = 0; j < properties.length; j++ ) - { + properties = Introspector.getWritePropertiesForObject(list[i]); + for (j = 0; j < properties.length; j++) { // if delegate has property - if ( properties[j].equals( aKey ) ) - { + if (properties[j].equals(aKey)) { // use this delegate - EOObserverCenter.notifyObserversObjectWillChange( list[i] ); - return Introspector.set( list[i], aKey.toString(), aValue ); + EOObserverCenter.notifyObserversObjectWillChange(list[i]); + return Introspector.set(list[i], aKey.toString(), aValue); } } } - + // set on internal map - EOObserverCenter.notifyObserversObjectWillChange( this ); - return directPut( aKey, aValue ); + EOObserverCenter.notifyObserversObjectWillChange(this); + return directPut(aKey, aValue); } /** - * Overridden to compare by reference. - */ - public boolean equals( Object anObject ) - { - return ( this == anObject ); + * 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. + * $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 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.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. + * Revision 1.2 2000/12/20 16:25:48 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java index be83eb4..c7f9c9b 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingController.java @@ -9,27 +9,25 @@ import net.wotonomy.ui.swing.TreeAssociation; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* A simple editor panel with a few textfields. -*/ -public class BindingController -{ - public BindingController( EODisplayGroup titlesGroup, EODisplayGroup childGroup ) - { + * A simple editor panel with a few textfields. + */ +public class BindingController { + public BindingController(EODisplayGroup titlesGroup, EODisplayGroup childGroup) { BindingPanel bindingPanel = new BindingPanel(); - + EOAssociation ta; - ta = new TreeAssociation( bindingPanel.treeChooser, "People" ); - ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "lastName" ); - ta.bindAspect( EOAssociation.ChildrenAspect, childGroup, "children" ); - ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" ); + ta = new TreeAssociation(bindingPanel.treeChooser, "People"); + ta.bindAspect(EOAssociation.TitlesAspect, titlesGroup, "lastName"); + ta.bindAspect(EOAssociation.ChildrenAspect, childGroup, "children"); + ta.bindAspect(EOAssociation.IsLeafAspect, titlesGroup, "childCount"); ta.establishConnection(); - + JDialog d = new JDialog(); - d.getContentPane().add( bindingPanel ); - d.setTitle( "Chooser Panel" ); + d.getContentPane().add(bindingPanel); + d.setTitle("Chooser Panel"); d.pack(); - WindowUtilities.cascade( d ); + WindowUtilities.cascade(d); d.show(); } - + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java index 624dc37..68740b4 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/BindingPanel.java @@ -12,161 +12,122 @@ import net.wotonomy.ui.swing.components.ButtonPanel; import net.wotonomy.ui.swing.components.TreeChooser; /** -* BindingPanel is a FileChooser-like panel that -* uses a TreeModel as a data source. It basically -* provides an alternative to JTree for rendering -* and manipulating tree-like data. -*/ -public class BindingPanel extends JPanel -{ + * BindingPanel is a FileChooser-like panel that uses a TreeModel as a data + * source. It basically provides an alternative to JTree for rendering and + * manipulating tree-like data. + */ +public class BindingPanel extends JPanel { protected TreeChooser treeChooser; protected ButtonPanel okPanel; - - public BindingPanel() - { + + public BindingPanel() { init(); } - - protected void init() - { - this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - this.setLayout( new BorderLayout( 10, 10 ) ); - - this.add( treeChooser = new TreeChooser(), BorderLayout.CENTER ); - - okPanel = new ButtonPanel( new String[] { "OK", "Cancel" } ); - this.add( okPanel, BorderLayout.SOUTH ); + + protected void init() { + this.setBorder(new EmptyBorder(10, 10, 10, 10)); + this.setLayout(new BorderLayout(10, 10)); + + this.add(treeChooser = new TreeChooser(), BorderLayout.CENTER); + + okPanel = new ButtonPanel(new String[] { "OK", "Cancel" }); + this.add(okPanel, BorderLayout.SOUTH); } - - /** - * Creates a new folder. - */ - protected class NewFolderAction extends AbstractAction - { - protected NewFolderAction() - { - super("New Folder", UIManager.getIcon("FileChooser.newFolderIcon") ); - } - public void actionPerformed(ActionEvent e) - { - } - } - - /** - * Acts on the "home" key event or equivalent event. - */ - protected class GoHomeAction extends AbstractAction - { - protected GoHomeAction() - { - super("Go Home", UIManager.getIcon("FileChooser.homeFolderIcon") ); - } - public void actionPerformed(ActionEvent e) - { - } - } - protected class ChangeToParentDirectoryAction extends AbstractAction - { - protected ChangeToParentDirectoryAction() - { - super("Go Up", UIManager.getIcon("FileChooser.upFolderIcon") ); + /** + * Creates a new folder. + */ + protected class NewFolderAction extends AbstractAction { + protected NewFolderAction() { + super("New Folder", UIManager.getIcon("FileChooser.newFolderIcon")); } - public void actionPerformed(ActionEvent e) - { + + public void actionPerformed(ActionEvent e) { } } - /** - * Responds to an Open or Save request - */ - protected class ApproveSelectionAction extends AbstractAction { - public void actionPerformed(ActionEvent e) - { + /** + * Acts on the "home" key event or equivalent event. + */ + protected class GoHomeAction extends AbstractAction { + protected GoHomeAction() { + super("Go Home", UIManager.getIcon("FileChooser.homeFolderIcon")); } - } - - /** - * Responds to a cancel request. - */ - protected class CancelSelectionAction extends AbstractAction { - public void actionPerformed(ActionEvent e) - { + public void actionPerformed(ActionEvent e) { } - } - - /** - * Rescans the files in the current directory - */ - protected class UpdateAction extends AbstractAction { - public void actionPerformed(ActionEvent e) - { + } + + protected class ChangeToParentDirectoryAction extends AbstractAction { + protected ChangeToParentDirectoryAction() { + super("Go Up", UIManager.getIcon("FileChooser.upFolderIcon")); } - } - - // - // Renderer for DirectoryComboBox - // -/* - class DirectoryComboBoxRenderer extends DefaultListCellRenderer { - IndentIcon ii = new IndentIcon(); - public Component getListCellRendererComponent(JList list, Object value, - int index, boolean isSelected, - boolean cellHasFocus) { - - super.getListCellRendererComponent(list, value, index, - isSelected, cellHasFocus); - File directory = (File) value; - if(directory == null) { - setText(""); - return this; - } - - String fileName = getFileChooser().getName(directory); - setText(fileName); - - // Find the depth of the directory - int depth = 0; - if(index != -1) { - File f = directory; - while(f.getParent() != null) { - depth++; - f = getFileChooser().getFileSystemView().createFileObject( - f.getParent() - ); - } - } - - Icon icon = getFileChooser().getIcon(directory); - - ii.icon = icon; - ii.depth = depth; - - setIcon(ii); - - return this; + + public void actionPerformed(ActionEvent e) { } - } - - final static int space = 10; - class IndentIcon implements Icon { - - Icon icon = null; - int depth = 0; - - public void paintIcon(Component c, Graphics g, int x, int y) { - icon.paintIcon(c, g, x+depth*space, y); + } + + /** + * Responds to an Open or Save request + */ + protected class ApproveSelectionAction extends AbstractAction { + public void actionPerformed(ActionEvent e) { } - - public int getIconWidth() { - return icon.getIconWidth() + depth*space; + } + + /** + * Responds to a cancel request. + */ + protected class CancelSelectionAction extends AbstractAction { + public void actionPerformed(ActionEvent e) { } - - public int getIconHeight() { - return icon.getIconHeight(); + } + + /** + * Rescans the files in the current directory + */ + protected class UpdateAction extends AbstractAction { + public void actionPerformed(ActionEvent e) { } - - } -*/ + } + + // + // Renderer for DirectoryComboBox + // + /* + * class DirectoryComboBoxRenderer extends DefaultListCellRenderer { IndentIcon + * ii = new IndentIcon(); public Component getListCellRendererComponent(JList + * list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + * + * super.getListCellRendererComponent(list, value, index, isSelected, + * cellHasFocus); File directory = (File) value; if(directory == null) { + * setText(""); return this; } + * + * String fileName = getFileChooser().getName(directory); setText(fileName); + * + * // Find the depth of the directory int depth = 0; if(index != -1) { File f = + * directory; while(f.getParent() != null) { depth++; f = + * getFileChooser().getFileSystemView().createFileObject( f.getParent() ); } } + * + * Icon icon = getFileChooser().getIcon(directory); + * + * ii.icon = icon; ii.depth = depth; + * + * setIcon(ii); + * + * return this; } } + * + * final static int space = 10; class IndentIcon implements Icon { + * + * Icon icon = null; int depth = 0; + * + * public void paintIcon(Component c, Graphics g, int x, int y) { + * icon.paintIcon(c, g, x+depth*space, y); } + * + * public int getIconWidth() { return icon.getIconWidth() + depth*space; } + * + * public int getIconHeight() { return icon.getIconHeight(); } + * + * } + */ } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java index fca6c98..a98129f 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataKeyID.java @@ -4,85 +4,69 @@ import net.wotonomy.control.EOGlobalID; import net.wotonomy.datastore.DataKey; /** -* A test implementation of EOGlobalID that -* wraps a DataKey. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class DataKeyID extends EOGlobalID -{ - private DataKey key; - - /** - * Constructor takes a data key. - */ - public DataKeyID( DataKey aKey ) - { - key = aKey; - } - - /** - * Returns the wrapped data key. - */ - public DataKey getKey() - { - return key; - } + * A test implementation of EOGlobalID that wraps a DataKey. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class DataKeyID extends EOGlobalID { + private DataKey key; + + /** + * Constructor takes a data key. + */ + public DataKeyID(DataKey aKey) { + key = aKey; + } + + /** + * Returns the wrapped data key. + */ + public DataKey getKey() { + return key; + } + + public boolean isTemporary() { + return false; + } + + public Object clone() { + return new DataKeyID((DataKey) key.clone()); + } + + public String toString() { + return "[DataKeyID:" + key.toString() + "]"; + } + + public int hashCode() { + return key.hashCode(); + } + + public boolean equals(Object anObject) { + if (anObject instanceof DataKeyID) { + if (((DataKeyID) anObject).key.equals(key)) { + return true; + } + } + return false; + } - public boolean isTemporary() - { - return false; - } - - public Object clone() - { - return new DataKeyID( (DataKey) key.clone() ); - } - - public String toString() - { - return "[DataKeyID:"+key.toString()+"]"; - } - - public int hashCode() - { - return key.hashCode(); - } - - public boolean equals( Object anObject ) - { - if ( anObject instanceof DataKeyID ) - { - if ( ((DataKeyID)anObject).key.equals( key ) ) - { - return true; - } - } - return false; - } - } /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2001/02/23 23:44:44 mpowers - * Fixes for hashcode to ensure proper key comparison. + * Revision 1.3 2001/02/23 23:44:44 mpowers Fixes for hashcode to ensure proper + * key comparison. * - * Revision 1.2 2001/02/22 20:56:07 mpowers - * Tests of notification handling. + * Revision 1.2 2001/02/22 20:56:07 mpowers Tests of notification handling. * - * Revision 1.1 2001/02/15 21:14:45 mpowers - * Test suite now using a persistent object store with editing context. + * Revision 1.1 2001/02/15 21:14:45 mpowers Test suite now using a persistent + * object store with editing context. * - * Revision 1.1 2001/02/05 03:45:37 mpowers - * Starting work on EOEditingContext. + * Revision 1.1 2001/02/05 03:45:37 mpowers Starting work on EOEditingContext. * * */ - - diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java index d175f6e..ac5ee44 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/DataObjectStore.java @@ -24,79 +24,59 @@ import net.wotonomy.foundation.NSNotificationQueue; import net.wotonomy.foundation.internal.WotonomyException; /** -* An object store that wraps a datastore for vending test objects. -*/ -public class DataObjectStore extends EOObjectStore -{ - DataSoup soup; - - /** - * Constructor specifies path to datastore. - */ - public DataObjectStore( String aPath ) - { - soup = new XMLFileSoup( aPath ); - } - - /** - * This implementation returns an appropriately configured array fault. - */ - public NSArray arrayFaultWithSourceGlobalID ( - EOGlobalID aGlobalID, - String aRelationship, - EOEditingContext aContext ) - { - return new ArrayFault( - aGlobalID, aRelationship, aContext ); - } - - /** - * This implementation returns the actual - * object for the specified id. - */ - public Object faultForGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { -System.out.println( "DataObjectStore.faultForGlobalID: * reading object * : " + aGlobalID ); - Object result = soup.getObjectByKey( - ((DataKeyID)aGlobalID).getKey() ); - - if ( result == null ) return null; - - //! transpose keys to objects - convertRelationKeysToObjects( aContext, result, aGlobalID ); - //! - - aContext.recordObject( result, aGlobalID ); - return result; - } - - /** - * 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 Object faultForRawRow ( - Map aDictionary, - String anEntityName, - EOEditingContext aContext ) - { - //TODO: faults 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) { + * An object store that wraps a datastore for vending test objects. + */ +public class DataObjectStore extends EOObjectStore { + DataSoup soup; + + /** + * Constructor specifies path to datastore. + */ + public DataObjectStore(String aPath) { + soup = new XMLFileSoup(aPath); + } + + /** + * This implementation returns an appropriately configured array fault. + */ + public NSArray arrayFaultWithSourceGlobalID(EOGlobalID aGlobalID, String aRelationship, EOEditingContext aContext) { + return new ArrayFault(aGlobalID, aRelationship, aContext); + } + + /** + * This implementation returns the actual object for the specified id. + */ + public Object faultForGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + System.out.println("DataObjectStore.faultForGlobalID: * reading object * : " + aGlobalID); + Object result = soup.getObjectByKey(((DataKeyID) aGlobalID).getKey()); + + if (result == null) + return null; + + // ! transpose keys to objects + convertRelationKeysToObjects(aContext, result, aGlobalID); + // ! + + aContext.recordObject(result, aGlobalID); + return result; + } + + /** + * 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 Object faultForRawRow(Map aDictionary, String anEntityName, EOEditingContext aContext) { + // TODO: faults 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) { if (aGlobalID.isTemporary()) { // TODO: this should never happen, but it does now until we get // faults. @@ -105,9 +85,8 @@ System.out.println( "DataObjectStore.faultForGlobalID: * reading object * : " + return; } - System.out.println("DataObjectStore.initializeObject: * reading object * : " - + aGlobalID); - //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); + System.out.println("DataObjectStore.initializeObject: * reading object * : " + aGlobalID); + // net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); Object original = soup.getObjectByKey(((DataKeyID) aGlobalID).getKey()); // ! transpose keys to objects @@ -116,368 +95,311 @@ System.out.println( "DataObjectStore.faultForGlobalID: * reading object * : " + EOObserverCenter.notifyObserversObjectWillChange(anObject); KeyValueCodingUtilities.copy(aContext, original, aContext, anObject); } - - /** - * Remove all values from all objects in memory, turning them into faults, - * and posts a notification that all objects have been invalidated. + + /** + * 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 () - { - // does nothing except 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 ) - { - // does nothing - } - - /** - * Returns false because locking is not permitted. - */ - public boolean isObjectLockedWithGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { - return false; - } - - /** - * Does nothing because locking is not 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( "DataObjectStore.objectsForSourceGlobalID: * reading object * : " + aGlobalID ); - Object object = soup.getObjectByKey(((DataKeyID)aGlobalID).getKey() ); - - if ( object == null ) return null; - - Object fault; - EOGlobalID id; - NSMutableArray result = new NSMutableArray(); - - Iterator it = ((TestObject)object).getChildList().iterator(); - while ( it.hasNext() ) - { - id = new DataKeyID((DataKey)it.next()); - fault = aContext.faultForGlobalID( id, aContext ); - - // if key still exists - if ( fault != null ) - { + public void invalidateAllObjects() { + // does nothing except 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) { + // does nothing + } + + /** + * Returns false because locking is not permitted. + */ + public boolean isObjectLockedWithGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + return false; + } + + /** + * Does nothing because locking is not 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("DataObjectStore.objectsForSourceGlobalID: * reading object * : " + aGlobalID); + Object object = soup.getObjectByKey(((DataKeyID) aGlobalID).getKey()); + + if (object == null) + return null; + + Object fault; + EOGlobalID id; + NSMutableArray result = new NSMutableArray(); + + Iterator it = ((TestObject) object).getChildList().iterator(); + while (it.hasNext()) { + id = new DataKeyID((DataKey) it.next()); + fault = aContext.faultForGlobalID(id, aContext); + + // if key still exists + if (fault != null) { //System.out.println( "objectsForSourceGlobalID: found: " + id + " : " + fault ); - result.add( fault ); + result.add(fault); // for testing purposes -((TestObject)fault).setParent( (TestObject) object ); - } - else // key no longer exists - { - // do not add -System.out.println( "objectsForSourceGlobalID: could not find fault for id: " + id ); - } - } - return result; - - } - - /** - * Returns a List of objects the meet the criteria of - * the supplied specification. - * Each object is registered with the specified editing context. - * If any object is already registered in the specified context, - * it is not refetched and that object should be used in the array. - */ - public NSArray objectsWithFetchSpecification ( - EOFetchSpecification aFetchSpec, - EOEditingContext aContext ) - { - //TODO: fetch specs are not yet supported - - DataView view = soup.queryObjects( null, null ); -System.out.println( "DataObjectStore: ** querying all objects **" ); - - // we've changed this implementation so that - // it simply calls faultForGlobalID on the context - // for each id in the result set. - // this way, child contexts inherit parent's state. - // however, it's unclear if the specification allows - // faults in the resulting array. sounds like it doesn't. - NSMutableArray result = new NSMutableArray(); - DataKeyID id; - Iterator it = view.iterator(); - while ( it.hasNext() ) - { - id = new DataKeyID( view.getKeyForObject( it.next() ) ); - result.addObject( aContext.faultForGlobalID( id, aContext ) ); - } - return result; - } - - /** - * 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 ) - { - //TODO: faults are not yet supported - // just re-initialize the object - initializeObject( anObject, aGlobalID, aContext ); - } - - /** - * Writes all changes in the specified editing context - * to the respository. - */ - public void saveChangesInEditingContext ( - EOEditingContext aContext ) - { - Object o; - DataKeyID id; - Iterator it; - - // process deletes - it = aContext.deletedObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - id = (DataKeyID) aContext.globalIDForObject( o ); -System.out.println( "DataObjectStore: * deleting object * : " + id ); - soup.removeObject( id.getKey() ); - // remove object from editing context - aContext.forgetObject( o ); - } - - // process inserts - NSMutableDictionary userInfo = null; - it = aContext.insertedObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - EOGlobalID oldId = aContext.globalIDForObject( o ); - - //! transpose objects to keys - convertRelationObjectsToKeys( aContext, (TestObject) o ); - id = new DataKeyID( soup.addObject( o ) ); - convertRelationKeysToObjects( aContext, (TestObject) o, oldId ); - //! - -System.out.println( "DataObjectStore: * adding object * : " + id ); - - // save mapping of old id to new id - if ( userInfo == null ) - { - userInfo = new NSMutableDictionary(); - } - userInfo.setObjectForKey( id, oldId ); - } - - // broadcast inserted objects' new ids if necessary - if ( userInfo != null ) - { - NSNotificationQueue.defaultQueue().enqueueNotification( - new NSNotification( - EOGlobalID.GlobalIDChangedNotification, null, userInfo ), - NSNotificationQueue.PostNow ); - } - - System.out.println( aContext.updatedObjects() ); - - // process updates - it = aContext.updatedObjects().iterator(); - while ( it.hasNext() ) - { + ((TestObject) fault).setParent((TestObject) object); + } else // key no longer exists + { + // do not add + System.out.println("objectsForSourceGlobalID: could not find fault for id: " + id); + } + } + return result; + + } + + /** + * Returns a List of objects the meet the criteria of the supplied + * specification. Each object is registered with the specified editing context. + * If any object is already registered in the specified context, it is not + * refetched and that object should be used in the array. + */ + public NSArray objectsWithFetchSpecification(EOFetchSpecification aFetchSpec, EOEditingContext aContext) { + // TODO: fetch specs are not yet supported + + DataView view = soup.queryObjects(null, null); + System.out.println("DataObjectStore: ** querying all objects **"); + + // we've changed this implementation so that + // it simply calls faultForGlobalID on the context + // for each id in the result set. + // this way, child contexts inherit parent's state. + // however, it's unclear if the specification allows + // faults in the resulting array. sounds like it doesn't. + NSMutableArray result = new NSMutableArray(); + DataKeyID id; + Iterator it = view.iterator(); + while (it.hasNext()) { + id = new DataKeyID(view.getKeyForObject(it.next())); + result.addObject(aContext.faultForGlobalID(id, aContext)); + } + return result; + } + + /** + * 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) { + // TODO: faults are not yet supported + // just re-initialize the object + initializeObject(anObject, aGlobalID, aContext); + } + + /** + * Writes all changes in the specified editing context to the respository. + */ + public void saveChangesInEditingContext(EOEditingContext aContext) { + Object o; + DataKeyID id; + Iterator it; + + // process deletes + it = aContext.deletedObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + id = (DataKeyID) aContext.globalIDForObject(o); + System.out.println("DataObjectStore: * deleting object * : " + id); + soup.removeObject(id.getKey()); + // remove object from editing context + aContext.forgetObject(o); + } + + // process inserts + NSMutableDictionary userInfo = null; + it = aContext.insertedObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + EOGlobalID oldId = aContext.globalIDForObject(o); + + // ! transpose objects to keys + convertRelationObjectsToKeys(aContext, (TestObject) o); + id = new DataKeyID(soup.addObject(o)); + convertRelationKeysToObjects(aContext, (TestObject) o, oldId); + // ! + + System.out.println("DataObjectStore: * adding object * : " + id); + + // save mapping of old id to new id + if (userInfo == null) { + userInfo = new NSMutableDictionary(); + } + userInfo.setObjectForKey(id, oldId); + } + + // broadcast inserted objects' new ids if necessary + if (userInfo != null) { + NSNotificationQueue.defaultQueue().enqueueNotification( + new NSNotification(EOGlobalID.GlobalIDChangedNotification, null, userInfo), + NSNotificationQueue.PostNow); + } + + System.out.println(aContext.updatedObjects()); + + // process updates + it = aContext.updatedObjects().iterator(); + while (it.hasNext()) { //if ( true ) // test validation error message handling //throw new RuntimeException( "Update not allowed." ); - o = it.next(); - id = (DataKeyID) aContext.globalIDForObject( o ); -System.out.println( "DataObjectStore: * updating object * : " + id ); - - //! transpose objects to keys - convertRelationObjectsToKeys( aContext, (TestObject) o ); - soup.updateObject( id.getKey(), o ); - convertRelationKeysToObjects( aContext, (TestObject) o, id ); - //! - - } - } - - private void convertRelationKeysToObjects( - EOEditingContext aContext, Object anObject, EOGlobalID aGlobalID ) - { // System.out.println( "convertRelationKeysToObjects: " + anObject ); + o = it.next(); + id = (DataKeyID) aContext.globalIDForObject(o); + System.out.println("DataObjectStore: * updating object * : " + id); + + // ! transpose objects to keys + convertRelationObjectsToKeys(aContext, (TestObject) o); + soup.updateObject(id.getKey(), o); + convertRelationKeysToObjects(aContext, (TestObject) o, id); + // ! + + } + } + + private void convertRelationKeysToObjects(EOEditingContext aContext, Object anObject, EOGlobalID aGlobalID) { // System.out.println( + // "convertRelationKeysToObjects: + // " + // + + // anObject + // ); // set editing context for testing -((TestObject)anObject).editingContext = aContext; - - Object fault; - DataKeyID id; - List result = new LinkedList(); - Iterator it = ((TestObject)anObject).getChildList().iterator(); - while ( it.hasNext() ) - { - id = new DataKeyID((DataKey)it.next()); - fault = aContext.faultForGlobalID( id, aContext ); - - // if key still exists - if ( fault != null ) - { + ((TestObject) anObject).editingContext = aContext; + + Object fault; + DataKeyID id; + List result = new LinkedList(); + Iterator it = ((TestObject) anObject).getChildList().iterator(); + while (it.hasNext()) { + id = new DataKeyID((DataKey) it.next()); + fault = aContext.faultForGlobalID(id, aContext); + + // if key still exists + if (fault != null) { //System.out.println( "convertRelationObjectsToKeys: found: " + id + " : " + fault ); - result.add( fault ); + result.add(fault); // for testing purposes -((TestObject)fault).setParent( (TestObject) anObject ); - } - else // key no longer exists - { - // do not add -System.out.println( "convertRelationObjectsToKeys: could not find fault for id: " + id ); - } - } - // this tests loading manually on-demand + ((TestObject) fault).setParent((TestObject) anObject); + } else // key no longer exists + { + // do not add + System.out.println("convertRelationObjectsToKeys: could not find fault for id: " + id); + } + } + // this tests loading manually on-demand // ((TestObject)anObject).setChildList( null ); - // this tests loading immediately - ((TestObject)anObject).setChildList( result ); - // this tests loading array faults + // this tests loading immediately + ((TestObject) anObject).setChildList(result); + // this tests loading array faults // ((TestObject)result).setChildList( null ); - ((TestObject)anObject).setChildList( - aContext.arrayFaultWithSourceGlobalID( - aGlobalID, "childList", aContext ) ); - - } - - private void convertRelationObjectsToKeys( - EOEditingContext aContext, Object anObject ) - { // System.out.println( "convertRelationObjectsToKeys: " + anObject ); - Object o; - DataKeyID id; - List result = new LinkedList(); - Iterator it = ((TestObject)anObject).getChildList().iterator(); + ((TestObject) anObject).setChildList(aContext.arrayFaultWithSourceGlobalID(aGlobalID, "childList", aContext)); + + } + + private void convertRelationObjectsToKeys(EOEditingContext aContext, Object anObject) { // System.out.println( + // "convertRelationObjectsToKeys: + // " + anObject ); + Object o; + DataKeyID id; + List result = new LinkedList(); + Iterator it = ((TestObject) anObject).getChildList().iterator(); // for testing purposes -((TestObject)anObject).setParent( null ); -((TestObject)anObject).editingContext = null; - while ( it.hasNext() ) - { - o = it.next(); + ((TestObject) anObject).setParent(null); + ((TestObject) anObject).editingContext = null; + while (it.hasNext()) { + o = it.next(); //System.out.println( "convertRelationObjectsToKeys: " + o + " : " + aContext.globalIDForObject( o ) ); - id = (DataKeyID)aContext.globalIDForObject( o ); - - // if object still exists in context - if ( id != null ) - { - result.add( id.getKey() ); - } - else // object was deleted - { - // do not add -System.out.println( "convertRelationObjectsToKeys: could not find id for object: " + o ); -System.out.println( aContext.registeredObjects() ); - } - - } - ((TestObject)anObject).setChildList( result ); - } - - -/* - * $Log$ - * Revision 1.1 2006/02/19 16:30:25 cgruber - * Update imports and maven dependencies. - * - * Revision 1.1 2006/02/16 13:18:56 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. - * - * Revision 1.18 2002/03/11 03:18:39 mpowers - * Now properly handling ObserverChangesLater. - * - * Revision 1.17 2001/10/26 18:39:44 mpowers - * Posting notifications immediately, rather than delayed. - * - * Revision 1.16 2001/05/06 18:27:10 mpowers - * More broadly catching editing contexts for now. - * - * Revision 1.15 2001/05/05 23:05:43 mpowers - * Implemented Array Faults. - * - * Revision 1.14 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.13 2001/05/04 23:24:30 mpowers - * Changes to test code. - * - * Revision 1.12 2001/05/04 16:57:56 mpowers - * Now correctly transposing references to editing contexts when - * cloning/copying between editing contexts. - * - * Revision 1.11 2001/05/02 17:33:28 mpowers - * More changes for testing. - * - * Revision 1.10 2001/04/30 13:15:24 mpowers - * Child contexts re-initializing objects invalidated in parent now - * propery transpose relationships. - * - * Revision 1.9 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. - * - * Revision 1.8 2001/04/29 02:29:31 mpowers - * Debugging relationship faulting. - * - * Revision 1.7 2001/04/28 22:17:51 mpowers - * Revised PropertyDataSource to be EOClassDescription-aware. - * - * Revision 1.6 2001/04/28 16:18:44 mpowers - * Implementing relationships. - * - * Revision 1.5 2001/04/13 16:33:36 mpowers - * Now broadcasting notifications. - * - * Revision 1.4 2001/04/08 21:00:54 mpowers - * Changes to support new objectsForFetchSpecification scheme. - * - * Revision 1.3 2001/03/22 21:37:52 mpowers - * Testing new features. - * - * Revision 1.2 2001/03/15 21:10:41 mpowers - * Implemented global id re-registration for newly saved inserts. - * - * Revision 1.1 2001/03/05 22:12:11 mpowers - * Created the control package for a datastore-specific implementation - * of EOObjectStore. - * - * - */ -} + id = (DataKeyID) aContext.globalIDForObject(o); + + // if object still exists in context + if (id != null) { + result.add(id.getKey()); + } else // object was deleted + { + // do not add + System.out.println("convertRelationObjectsToKeys: could not find id for object: " + o); + System.out.println(aContext.registeredObjects()); + } + } + ((TestObject) anObject).setChildList(result); + } + + /* + * $Log$ Revision 1.1 2006/02/19 16:30:25 cgruber Update imports and maven + * dependencies. + * + * Revision 1.1 2006/02/16 13:18:56 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. + * + * Revision 1.18 2002/03/11 03:18:39 mpowers Now properly handling + * ObserverChangesLater. + * + * Revision 1.17 2001/10/26 18:39:44 mpowers Posting notifications immediately, + * rather than delayed. + * + * Revision 1.16 2001/05/06 18:27:10 mpowers More broadly catching editing + * contexts for now. + * + * Revision 1.15 2001/05/05 23:05:43 mpowers Implemented Array Faults. + * + * Revision 1.14 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.13 2001/05/04 23:24:30 mpowers Changes to test code. + * + * Revision 1.12 2001/05/04 16:57:56 mpowers Now correctly transposing + * references to editing contexts when cloning/copying between editing contexts. + * + * Revision 1.11 2001/05/02 17:33:28 mpowers More changes for testing. + * + * Revision 1.10 2001/04/30 13:15:24 mpowers Child contexts re-initializing + * objects invalidated in parent now propery transpose relationships. + * + * Revision 1.9 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. + * + * Revision 1.8 2001/04/29 02:29:31 mpowers Debugging relationship faulting. + * + * Revision 1.7 2001/04/28 22:17:51 mpowers Revised PropertyDataSource to be + * EOClassDescription-aware. + * + * Revision 1.6 2001/04/28 16:18:44 mpowers Implementing relationships. + * + * Revision 1.5 2001/04/13 16:33:36 mpowers Now broadcasting notifications. + * + * Revision 1.4 2001/04/08 21:00:54 mpowers Changes to support new + * objectsForFetchSpecification scheme. + * + * Revision 1.3 2001/03/22 21:37:52 mpowers Testing new features. + * + * Revision 1.2 2001/03/15 21:10:41 mpowers Implemented global id + * re-registration for newly saved inserts. + * + * Revision 1.1 2001/03/05 22:12:11 mpowers Created the control package for a + * datastore-specific implementation of EOObjectStore. + * + * + */ +} diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java index b304ade..6a36db3 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditController.java @@ -28,187 +28,153 @@ import net.wotonomy.ui.swing.components.ButtonPanel; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* A simple editor panel with a few textfields. -*/ -public class EditController -{ - EODisplayGroup group; - JDialog dialog; - - public EditController( EODataSource aDataSource ) - { + * A simple editor panel with a few textfields. + */ +public class EditController { + EODisplayGroup group; + JDialog dialog; + + public EditController(EODataSource aDataSource) { EditPanel editPanel = new EditPanel(); - editPanel.infoPanel.setBorder( - BorderFactory.createCompoundBorder( - BorderFactory.createRaisedBevelBorder(), - BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ) ); - editPanel.setBorder( - BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) ); - ButtonPanel okPanel = new ButtonPanel( - new String[] { "Revert", "Refault", "Refresh", "Commit" } ); - editPanel.add( okPanel, BorderLayout.SOUTH ); - + editPanel.infoPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), + BorderFactory.createEmptyBorder(10, 10, 10, 10))); + editPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + ButtonPanel okPanel = new ButtonPanel(new String[] { "Revert", "Refault", "Refresh", "Commit" }); + editPanel.add(okPanel, BorderLayout.SOUTH); + group = new EODisplayGroup(); - group.setDataSource( aDataSource ); - group.fetch(); + group.setDataSource(aDataSource); + group.fetch(); group.selectNext(); - + // text associations - + EOAssociation ta; - - ta = new TextAssociation( editPanel.firstNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" ); + + ta = new TextAssociation(editPanel.firstNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "firstName"); ta.establishConnection(); - - ta = new TextAssociation( editPanel.middleNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" ); + + ta = new TextAssociation(editPanel.middleNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "middleName"); ta.establishConnection(); - - ta = new TextAssociation( editPanel.lastNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" ); + + ta = new TextAssociation(editPanel.lastNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "lastName"); ta.establishConnection(); - + // radio panels - - ta = new RadioPanelAssociation( editPanel.yearRadioPanel ); - ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" ); - + + ta = new RadioPanelAssociation(editPanel.yearRadioPanel); + ta.bindAspect(EOAssociation.ValueAspect, group, "createDate.year"); + EODisplayGroup yearTitles = new EODisplayGroup(); - yearTitles.setObjectArray( new NSArray( - new Object[] { "1999", "2000", "2001" } ) ); - ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" ); - + yearTitles.setObjectArray(new NSArray(new Object[] { "1999", "2000", "2001" })); + ta.bindAspect(EOAssociation.TitlesAspect, yearTitles, ""); + EODisplayGroup yearObjects = new EODisplayGroup(); - yearObjects.setObjectArray( new NSArray( - new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) ); - ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" ); - + yearObjects.setObjectArray(new NSArray(new Object[] { new Integer(99), new Integer(100), new Integer(101) })); + ta.bindAspect(EOAssociation.ObjectsAspect, yearObjects, ""); + ta.establishConnection(); - + // detail group - + final EODisplayGroup detailGroup = new EODisplayGroup(); - detailGroup.setDataSource( new PropertyDataSource( - aDataSource.editingContext(), TestObject.class ) ); - - ta = new MasterDetailAssociation( detailGroup ); - ta.bindAspect( EOAssociation.ParentAspect, group, "childList" ); + detailGroup.setDataSource(new PropertyDataSource(aDataSource.editingContext(), TestObject.class)); + + ta = new MasterDetailAssociation(detailGroup); + ta.bindAspect(EOAssociation.ParentAspect, group, "childList"); ta.establishConnection(); - - ta = new ListAssociation( editPanel.list ); - ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" ); + + ta = new ListAssociation(editPanel.list); + ta.bindAspect(EOAssociation.TitlesAspect, detailGroup, "fullName"); ta.establishConnection(); - + // display group action associations - AbstractButton button = (AbstractButton) - editPanel.addPanel.getButton( "Add" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - detailGroup.insertNewObjectAtIndex( 0 ); + AbstractButton button = (AbstractButton) editPanel.addPanel.getButton("Add"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + detailGroup.insertNewObjectAtIndex(0); } - } ); - - ta = new DisplayGroupActionAssociation( - editPanel.addPanel.getButton( "Remove" ) ); - ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); + }); + + ta = new DisplayGroupActionAssociation(editPanel.addPanel.getButton("Remove")); + ta.bindAspect(EOAssociation.ActionAspect, detailGroup, "deleteSelection"); ta.establishConnection(); - // ok / cancel buttons - - button = (AbstractButton) - okPanel.getButton( "Commit" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - group.dataSource().editingContext().saveChanges(); + // ok / cancel buttons + + button = (AbstractButton) okPanel.getButton("Commit"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + group.dataSource().editingContext().saveChanges(); } - } ); - - button = (AbstractButton) - okPanel.getButton( "Refresh" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - group.dataSource().editingContext().invalidateAllObjects(); + }); + + button = (AbstractButton) okPanel.getButton("Refresh"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + group.dataSource().editingContext().invalidateAllObjects(); } - } ); - - button = (AbstractButton) - okPanel.getButton( "Refault" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { -/* - Object o = group.displayedObjects().objectAtIndex( 0 ); - group.dataSource().editingContext().refaultObject( - o, - group.dataSource().editingContext().globalIDForObject( o ), - group.dataSource().editingContext() ); -*/ - group.dataSource().editingContext().revert(); - group.dataSource().editingContext().refaultObjects(); + }); + + button = (AbstractButton) okPanel.getButton("Refault"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + /* + * Object o = group.displayedObjects().objectAtIndex( 0 ); + * group.dataSource().editingContext().refaultObject( o, + * group.dataSource().editingContext().globalIDForObject( o ), + * group.dataSource().editingContext() ); + */ + group.dataSource().editingContext().revert(); + group.dataSource().editingContext().refaultObjects(); } - } ); - - button = (AbstractButton) - okPanel.getButton( "Revert" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - group.dataSource().editingContext().revert(); + }); + + button = (AbstractButton) okPanel.getButton("Revert"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + group.dataSource().editingContext().revert(); } - } ); - + }); + // add mouse listener for list - - editPanel.list.addMouseListener( new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - if ( e.getClickCount() == 2 ) - { + + editPanel.list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { Object item = detailGroup.selectedObject(); - if ( item != null ) - { + if (item != null) { // new InspectorController( item ); - - new EditController( - new ChildDataSource( - group.dataSource(), item ) ); - } + + new EditController(new ChildDataSource(group.dataSource(), item)); + } } } }); - + // launch - + dialog = new JDialog(); // add WindowListener for frame - dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); - dialog.getContentPane().add( editPanel ); - dialog.setTitle( "Edit Panel" ); + dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + dialog.getContentPane().add(editPanel); + dialog.setTitle("Edit Panel"); dialog.pack(); // dialog.setSize( 300, dialog.getSize().height ); - WindowUtilities.cascade( dialog ); + WindowUtilities.cascade(dialog); dialog.show(); - // workaround for memory issues on jdk1.2.2 - dialog.addWindowListener( new WindowAdapter() - { + // workaround for memory issues on jdk1.2.2 + dialog.addWindowListener(new WindowAdapter() { // exit on close - public void windowClosing(WindowEvent e) - { - ((JDialog)e.getWindow()).getContentPane().removeAll(); + public void windowClosing(WindowEvent e) { + ((JDialog) e.getWindow()).getContentPane().removeAll(); } }); } - + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java index 63b1317..19110f8 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/EditPanel.java @@ -15,47 +15,44 @@ import net.wotonomy.ui.swing.components.InfoPanel; import net.wotonomy.ui.swing.components.RadioButtonPanel; /** -* A simple editor panel with a few textfields. -*/ -public class EditPanel extends JPanel -{ + * A simple editor panel with a few textfields. + */ +public class EditPanel extends JPanel { public JTextComponent firstNameField; public JTextField middleNameField, lastNameField; public RadioButtonPanel yearRadioPanel; public InfoPanel infoPanel; public JList list; - public ButtonPanel addPanel; - + public ButtonPanel addPanel; - public EditPanel() - { - this.setLayout( new BorderLayout() ); - this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - - infoPanel = new InfoPanel(); + public EditPanel() { + this.setLayout(new BorderLayout()); + this.setBorder(new EmptyBorder(10, 10, 10, 10)); + + infoPanel = new InfoPanel(); // name fields - firstNameField = new JTextField(); - infoPanel.addPair( "First Name", firstNameField ); + firstNameField = new JTextField(); + infoPanel.addPair("First Name", firstNameField); middleNameField = new JTextField(); - infoPanel.addPair( "Middle Name", middleNameField ); + infoPanel.addPair("Middle Name", middleNameField); lastNameField = new JTextField(); - infoPanel.addPair( "Last Name", lastNameField ); + infoPanel.addPair("Last Name", lastNameField); yearRadioPanel = new RadioButtonPanel(); - infoPanel.addPair( "Year", yearRadioPanel ); - + infoPanel.addPair("Year", yearRadioPanel); + list = new JList(); - JPanel containerPanel = new JPanel(); - containerPanel.setLayout( new BorderLayout( 0, 5 ) ); - JScrollPane scrollPane = new JScrollPane( list ); - scrollPane.setPreferredSize( new java.awt.Dimension( 100, 100 ) ); - addPanel = new ButtonPanel( new String[] { "Add", "Remove" } ); - addPanel.setAlignment( FlowLayout.CENTER ); - containerPanel.add( scrollPane, BorderLayout.CENTER ); - containerPanel.add( addPanel, BorderLayout.SOUTH ); - infoPanel.addRow( "Children", containerPanel ); - - this.add( infoPanel, BorderLayout.CENTER ); - } - + JPanel containerPanel = new JPanel(); + containerPanel.setLayout(new BorderLayout(0, 5)); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setPreferredSize(new java.awt.Dimension(100, 100)); + addPanel = new ButtonPanel(new String[] { "Add", "Remove" }); + addPanel.setAlignment(FlowLayout.CENTER); + containerPanel.add(scrollPane, BorderLayout.CENTER); + containerPanel.add(addPanel, BorderLayout.SOUTH); + infoPanel.addRow("Children", containerPanel); + + this.add(infoPanel, BorderLayout.CENTER); + } + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java index 58e2d9a..eeb6dd9 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/InspectorController.java @@ -21,117 +21,103 @@ import net.wotonomy.ui.swing.TextAssociation; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* A simple editor panel with a few textfields. -*/ -public class InspectorController -{ - public InspectorController( Object o ) - { + * A simple editor panel with a few textfields. + */ +public class InspectorController { + public InspectorController(Object o) { EditPanel editPanel = new EditPanel(); - + EODisplayGroup group = new EODisplayGroup(); - group.setDataSource( new TestDataSource() ); - group.setObjectArray( new NSArray( o ) ); + group.setDataSource(new TestDataSource()); + group.setObjectArray(new NSArray(o)); group.selectNext(); - + // text associations - + EOAssociation ta; - - ta = new TextAssociation( editPanel.firstNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" ); + + ta = new TextAssociation(editPanel.firstNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "firstName"); ta.establishConnection(); - - ta = new TextAssociation( editPanel.middleNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" ); + + ta = new TextAssociation(editPanel.middleNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "middleName"); ta.establishConnection(); - - ta = new TextAssociation( editPanel.lastNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" ); + + ta = new TextAssociation(editPanel.lastNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "lastName"); ta.establishConnection(); - + // radio panels - - ta = new RadioPanelAssociation( editPanel.yearRadioPanel ); - ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" ); - + + ta = new RadioPanelAssociation(editPanel.yearRadioPanel); + ta.bindAspect(EOAssociation.ValueAspect, group, "createDate.year"); + EODisplayGroup yearTitles = new EODisplayGroup(); - yearTitles.setObjectArray( new NSArray( - new Object[] { "1999", "2000", "2001" } ) ); - ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" ); - + yearTitles.setObjectArray(new NSArray(new Object[] { "1999", "2000", "2001" })); + ta.bindAspect(EOAssociation.TitlesAspect, yearTitles, ""); + EODisplayGroup yearObjects = new EODisplayGroup(); - yearObjects.setObjectArray( new NSArray( - new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) ); - ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" ); - + yearObjects.setObjectArray(new NSArray(new Object[] { new Integer(99), new Integer(100), new Integer(101) })); + ta.bindAspect(EOAssociation.ObjectsAspect, yearObjects, ""); + ta.establishConnection(); - + // detail group - + final EODisplayGroup detailGroup = new EODisplayGroup(); - - ta = new MasterDetailAssociation( detailGroup ); - ta.bindAspect( EOAssociation.ParentAspect, group, "childList" ); + + ta = new MasterDetailAssociation(detailGroup); + ta.bindAspect(EOAssociation.ParentAspect, group, "childList"); ta.establishConnection(); - - ta = new ListAssociation( editPanel.list ); - ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" ); + + ta = new ListAssociation(editPanel.list); + ta.bindAspect(EOAssociation.TitlesAspect, detailGroup, "fullName"); ta.establishConnection(); - + // display group action associations - AbstractButton button = (AbstractButton) - editPanel.addPanel.getButton( "Add" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - detailGroup.insertNewObjectAtIndex( 0 ); + AbstractButton button = (AbstractButton) editPanel.addPanel.getButton("Add"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + detailGroup.insertNewObjectAtIndex(0); } - } ); - - ta = new DisplayGroupActionAssociation( - editPanel.addPanel.getButton( "Remove" ) ); - ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); + }); + + ta = new DisplayGroupActionAssociation(editPanel.addPanel.getButton("Remove")); + ta.bindAspect(EOAssociation.ActionAspect, detailGroup, "deleteSelection"); ta.establishConnection(); // add mouse listener for list - - editPanel.list.addMouseListener( new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - if ( e.getClickCount() == 2 ) - { + + editPanel.list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { Object item = detailGroup.selectedObject(); - if ( item != null ) - { - new InspectorController( item ); - } + if (item != null) { + new InspectorController(item); + } } } }); - + // launch - + JDialog dialog = new JDialog(); - dialog.getContentPane().add( editPanel ); - dialog.setTitle( "Inspector Panel" ); + dialog.getContentPane().add(editPanel); + dialog.setTitle("Inspector Panel"); dialog.pack(); - dialog.setSize( 300, dialog.getSize().height ); - WindowUtilities.cascade( dialog ); + dialog.setSize(300, dialog.getSize().height); + WindowUtilities.cascade(dialog); dialog.show(); - // workaround for memory issues on jdk1.2.2 - dialog.addWindowListener( new WindowAdapter() - { + // workaround for memory issues on jdk1.2.2 + dialog.addWindowListener(new WindowAdapter() { // exit on close - public void windowClosing(WindowEvent e) - { - ((JDialog)e.getWindow()).getContentPane().removeAll(); + public void windowClosing(WindowEvent e) { + ((JDialog) e.getWindow()).getContentPane().removeAll(); } }); } - + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java index ba6a1dc..5b4533d 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/Test.java @@ -18,103 +18,91 @@ import net.wotonomy.control.EOEditingContext; import net.wotonomy.control.EOObjectStore; /** -* A simple test-bed for wotonomy. -* Shows a JFrame containing the TestPanel -* which is controlled by the TestController. -*/ -public class Test -{ - static EOObjectStore objectStore; - static EOEditingContext editingContext; - static public void main( String[] argv ) - { + * A simple test-bed for wotonomy. Shows a JFrame containing the TestPanel which + * is controlled by the TestController. + */ +public class Test { + static EOObjectStore objectStore; + static EOEditingContext editingContext; + + static public void main(String[] argv) { // NSRunLoop.currentRunLoop(); - - // system l&f - try - { + + // system l&f + try { // UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); - UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); + UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - } - catch (Exception e) - { + } catch (Exception e) { // no system l&f - fail silently - } - - // launch notification monitor if desired - for ( int i = 0; i < argv.length; i++ ) - { - if ( argv[i].indexOf( "monitor" ) != -1 ) - { - new net.wotonomy.ui.swing.NotificationInspector(); - } - } - new net.wotonomy.ui.swing.NotificationInspector(); - - // set up editing context hierarchy - objectStore = new DataObjectStore( "data" ); - editingContext = new EOEditingContext( objectStore ); + } + + // launch notification monitor if desired + for (int i = 0; i < argv.length; i++) { + if (argv[i].indexOf("monitor") != -1) { + new net.wotonomy.ui.swing.NotificationInspector(); + } + } + new net.wotonomy.ui.swing.NotificationInspector(); + + // set up editing context hierarchy + objectStore = new DataObjectStore("data"); + editingContext = new EOEditingContext(objectStore); // connect panel to controller - TestPanel testPanel = new TestPanel(); - final TestController controller = new TestController( testPanel ); - + TestPanel testPanel = new TestPanel(); + final TestController controller = new TestController(testPanel); + // create frame and show - JFrame frame = new JFrame(); - frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); - + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + // setup menus JMenu menu; - JMenuItem menuItem; + JMenuItem menuItem; JMenuBar menuBar = new JMenuBar(); - menu = new JMenu( "File" ); - menu.add( "New" ); - menuItem = new JMenuItem( "Save" ); - menuItem.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_S, KeyEvent.CTRL_MASK ) ); - menuItem.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - controller.displayGroup.dataSource().editingContext().saveChanges(); - } - }); - - menu.add( menuItem ); - menu.add( "Close" ); - menuBar.add( menu ); - menu = new JMenu( "Edit" ); - menu.add( "Cut" ); - menu.add( "Copy" ); - menu.add( "Paste" ); - menuBar.add( menu ); - frame.setJMenuBar( menuBar ); - - frame.getContentPane().add( testPanel, BorderLayout.CENTER ); - frame.setTitle( "Test Frame" ); - frame.setBounds( 50, 50, 750, 500 ); + menu = new JMenu("File"); + menu.add("New"); + menuItem = new JMenuItem("Save"); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK)); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + controller.displayGroup.dataSource().editingContext().saveChanges(); + } + }); + + menu.add(menuItem); + menu.add("Close"); + menuBar.add(menu); + menu = new JMenu("Edit"); + menu.add("Cut"); + menu.add("Copy"); + menu.add("Paste"); + menuBar.add(menu); + frame.setJMenuBar(menuBar); + + frame.getContentPane().add(testPanel, BorderLayout.CENTER); + frame.setTitle("Test Frame"); + frame.setBounds(50, 50, 750, 500); frame.show(); // comment out this to avoid memory leak from jdk1.2.2 bug // add WindowListener for frame - frame.addWindowListener( new WindowAdapter() - { + frame.addWindowListener(new WindowAdapter() { // exit on close - public void windowClosing(WindowEvent e) - { - System.exit( 0 ); + public void windowClosing(WindowEvent e) { + System.exit(0); } }); - /* uncomment this to avoid memory leak from jdk1.2.2 bug - frame.getContentPane().removeAll(); - */ -/* - NSNotificationCenter.defaultCenter().addObserver( - frame, - new NSSelector( "hitMe", new Class[] { NSNotification.class } ), - null, null ); -*/ - } - + /* + * uncomment this to avoid memory leak from jdk1.2.2 bug + * frame.getContentPane().removeAll(); + */ + /* + * NSNotificationCenter.defaultCenter().addObserver( frame, new NSSelector( + * "hitMe", new Class[] { NSNotification.class } ), null, null ); + */ + } + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java index 8bbb452..83fe97c 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestController.java @@ -39,357 +39,311 @@ import net.wotonomy.ui.swing.components.IconCellRenderer; import net.wotonomy.ui.swing.components.KeyableCellEditor; /** -* Controller for the TestPanel. -*/ -public class TestController implements ActionListener -{ + * Controller for the TestPanel. + */ +public class TestController implements ActionListener { EODisplayGroup displayGroup; TestPanel panel; - public TestController( TestPanel aPanel ) - { + public TestController(TestPanel aPanel) { panel = aPanel; - - // setup display group - displayGroup = new EODisplayGroup(); - displayGroup.setSortOrderings( new NSArray( new Object[] - { "firstName", "middleName", "lastName" } ) ); + // setup display group + + displayGroup = new EODisplayGroup(); + displayGroup.setSortOrderings(new NSArray(new Object[] { "firstName", "middleName", "lastName" })); // fetch the data // displayGroup.setUsesOptimisticRefresh( true ); - displayGroup.setDataSource( new TestDataSource() ); + displayGroup.setDataSource(new TestDataSource()); // displayGroup.setSelectsFirstObjectAfterFetch( true ); - displayGroup.fetch(); - displayGroup.selectNext(); + displayGroup.fetch(); + displayGroup.selectNext(); -displayGroup.setDelegate( this ); + displayGroup.setDelegate(this); // set up associations - EOAssociation assoc; - + EOAssociation assoc; + // table association - + TableColumn column; - + column = new TableColumn(); - column.setHeaderValue( "First" ); - IconCellRenderer iconRenderer = new IconCellRenderer() - { - private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon"); - public Icon getIconForContext( - JComponent container, Object value, - int row, int col, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - return icon; - } - }; - iconRenderer.addActionListener( this ); - column.setCellRenderer( new AlternatingRowCellRenderer( iconRenderer ) ); + column.setHeaderValue("First"); + IconCellRenderer iconRenderer = new IconCellRenderer() { + private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon"); + + public Icon getIconForContext(JComponent container, Object value, int row, int col, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + return icon; + } + }; + iconRenderer.addActionListener(this); + column.setCellRenderer(new AlternatingRowCellRenderer(iconRenderer)); // column.setCellEditor( iconRenderer ); // assoc = new TableColumnAssociation( column ); - assoc = new TreeColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "firstName" ); + assoc = new TreeColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "firstName"); //new net.wotonomy.ui.swing.DisplayGroupInspector( displayGroup ); //displayGroup = new EODisplayGroup(); // assoc.bindAspect( EOAssociation.ChildrenAspect, displayGroup, "childList" ); - assoc.bindAspect( EOAssociation.EditableAspect, null, "true" ); - assoc.bindAspect( EOAssociation.IsLeafAspect, null, "childCount" ); - ((TableColumnAssociation)assoc).setTable( panel.table ); + assoc.bindAspect(EOAssociation.EditableAspect, null, "true"); + assoc.bindAspect(EOAssociation.IsLeafAspect, null, "childCount"); + ((TableColumnAssociation) assoc).setTable(panel.table); assoc.establishConnection(); - ((TreeColumnAssociation)assoc).getTreeModelAssociation().setInsertingAfter( false ); - ((TreeColumnAssociation)assoc).getTreeModelAssociation().setInsertingChild( false ); + ((TreeColumnAssociation) assoc).getTreeModelAssociation().setInsertingAfter(false); + ((TreeColumnAssociation) assoc).getTreeModelAssociation().setInsertingChild(false); // column.setCellRenderer( new AlternatingRowCellRenderer( column.getCellRenderer() ) ); -/* - // test the standalone mode of the icon cell renderer - panel.add( iconRenderer, java.awt.BorderLayout.SOUTH ); - iconRenderer.setText( "Hello World!" ); - iconRenderer.setIcon( UIManager.getIcon("FileChooser.homeFolderIcon") ); -*/ - + /* + * // test the standalone mode of the icon cell renderer panel.add( + * iconRenderer, java.awt.BorderLayout.SOUTH ); iconRenderer.setText( + * "Hello World!" ); iconRenderer.setIcon( + * UIManager.getIcon("FileChooser.homeFolderIcon") ); + */ + column = new TableColumn(); - column.setHeaderValue( "Middle" ); - column.setCellRenderer( new AlternatingRowCellRenderer() ); - assoc = new TableColumnAssociation( column ); - ((TableColumnAssociation)assoc).setSortCaseSensitive( true ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "middleName" ); - ((TableColumnAssociation)assoc).setTable( panel.table ); + column.setHeaderValue("Middle"); + column.setCellRenderer(new AlternatingRowCellRenderer()); + assoc = new TableColumnAssociation(column); + ((TableColumnAssociation) assoc).setSortCaseSensitive(true); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "middleName"); + ((TableColumnAssociation) assoc).setTable(panel.table); assoc.establishConnection(); - + column = new TableColumn(); - column.setHeaderValue( "Last" ); - column.setCellRenderer( new AlternatingRowCellRenderer() ); - column.setCellEditor( new KeyableCellEditor() ); - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "lastName" ); - assoc.bindAspect( EOAssociation.EditableAspect, null, "true" ); - ((TableColumnAssociation)assoc).setTable( panel.table ); + column.setHeaderValue("Last"); + column.setCellRenderer(new AlternatingRowCellRenderer()); + column.setCellEditor(new KeyableCellEditor()); + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "lastName"); + assoc.bindAspect(EOAssociation.EditableAspect, null, "true"); + ((TableColumnAssociation) assoc).setTable(panel.table); assoc.establishConnection(); - + column = new TableColumn(); - column.setHeaderValue( "Created" ); + column.setHeaderValue("Created"); FormattedCellRenderer renderer = new FormattedCellRenderer(); - renderer.setFormat( DateFormat.getDateInstance() ); - column.setCellRenderer( new AlternatingRowCellRenderer( renderer ) ); - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate" ); - ((TableColumnAssociation)assoc).setTable( panel.table ); + renderer.setFormat(DateFormat.getDateInstance()); + column.setCellRenderer(new AlternatingRowCellRenderer(renderer)); + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "createDate"); + ((TableColumnAssociation) assoc).setTable(panel.table); assoc.establishConnection(); - + column = new TableColumn(); - column.setHeaderValue( "Special" ); - column.setCellRenderer( new AlternatingRowCellRenderer( - panel.table.getDefaultRenderer( Boolean.class ) ) ); - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "special" ); - assoc.bindAspect( EOAssociation.EditableAspect, null, "true" ); - ((TableColumnAssociation)assoc).setTable( panel.table ); + column.setHeaderValue("Special"); + column.setCellRenderer(new AlternatingRowCellRenderer(panel.table.getDefaultRenderer(Boolean.class))); + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "special"); + assoc.bindAspect(EOAssociation.EditableAspect, null, "true"); + ((TableColumnAssociation) assoc).setTable(panel.table); assoc.establishConnection(); - - // text associations - assoc = new TextAssociation( panel.firstNameField ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "firstName" ); + // text associations + + assoc = new TextAssociation(panel.firstNameField); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "firstName"); //EODisplayGroup controllerDisplayGroup = new EODisplayGroup(); //controllerDisplayGroup.setObjectArray( new NSArray( this ) ); //controllerDisplayGroup.selectNext(); //assoc.bindAspect( EOAssociation.ValueAspect, controllerDisplayGroup, "filter" ); assoc.establishConnection(); - assoc = new TextAssociation( panel.middleNameField ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "middleName" ); + assoc = new TextAssociation(panel.middleNameField); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "middleName"); assoc.establishConnection(); - - assoc = new TextAssociation( panel.lastNameField ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "lastName" ); - assoc.bindAspect( EOAssociation.LabelAspect, displayGroup, "special" ); + + assoc = new TextAssociation(panel.lastNameField); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "lastName"); + assoc.bindAspect(EOAssociation.LabelAspect, displayGroup, "special"); assoc.establishConnection(); - - assoc = new ButtonAssociation( panel.checkbox ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "special" ); + + assoc = new ButtonAssociation(panel.checkbox); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "special"); // assoc.bindAspect( EOAssociation.EnabledAspect, displayGroup, "special" ); // assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" ); assoc.establishConnection(); - + // combo associations - - assoc = new ComboBoxAssociation( panel.dateBox ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.date" ); - // no titles aspect: uses existing combobox options + + assoc = new ComboBoxAssociation(panel.dateBox); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "createDate.date"); + // no titles aspect: uses existing combobox options assoc.establishConnection(); - - assoc = new ComboBoxAssociation( panel.monthBox ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.month" ); + + assoc = new ComboBoxAssociation(panel.monthBox); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "createDate.month"); EODisplayGroup monthTitlesGroup = new EODisplayGroup(); - monthTitlesGroup.setObjectArray( new NSArray( - new Object[] { "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" } ) ); - assoc.bindAspect( EOAssociation.TitlesAspect, monthTitlesGroup, "" ); + monthTitlesGroup.setObjectArray(new NSArray(new Object[] { "January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", "November", "December" })); + assoc.bindAspect(EOAssociation.TitlesAspect, monthTitlesGroup, ""); EODisplayGroup monthObjectsGroup = new EODisplayGroup(); - monthObjectsGroup.setObjectArray( new NSArray( - new Object[] { new Integer( 0 ), - new Integer( 1 ), new Integer( 2 ), new Integer( 3 ), - new Integer( 4 ), new Integer( 5 ), new Integer( 6 ), - new Integer( 7 ), new Integer( 8 ), new Integer( 9 ), - new Integer( 10 ), new Integer( 11 ) } ) ); - assoc.bindAspect( EOAssociation.ObjectsAspect, monthObjectsGroup, "" ); - + monthObjectsGroup.setObjectArray(new NSArray(new Object[] { new Integer(0), new Integer(1), new Integer(2), + new Integer(3), new Integer(4), new Integer(5), new Integer(6), new Integer(7), new Integer(8), + new Integer(9), new Integer(10), new Integer(11) })); + assoc.bindAspect(EOAssociation.ObjectsAspect, monthObjectsGroup, ""); + assoc.establishConnection(); - - assoc = new ComboBoxAssociation( panel.yearBox ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.year" ); - + assoc = new ComboBoxAssociation(panel.yearBox); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "createDate.year"); + EODisplayGroup yearTitlesGroup = new EODisplayGroup(); - yearTitlesGroup.setObjectArray( new NSArray( - new Object[] { "1999", "2000", "2001" } ) ); - assoc.bindAspect( EOAssociation.TitlesAspect, yearTitlesGroup, "" ); + yearTitlesGroup.setObjectArray(new NSArray(new Object[] { "1999", "2000", "2001" })); + assoc.bindAspect(EOAssociation.TitlesAspect, yearTitlesGroup, ""); EODisplayGroup yearObjectsGroup = new EODisplayGroup(); - yearObjectsGroup.setObjectArray( new NSArray( - new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) ); - assoc.bindAspect( EOAssociation.ObjectsAspect, yearObjectsGroup, "" ); - + yearObjectsGroup + .setObjectArray(new NSArray(new Object[] { new Integer(99), new Integer(100), new Integer(101) })); + assoc.bindAspect(EOAssociation.ObjectsAspect, yearObjectsGroup, ""); + assoc.establishConnection(); - - assoc = new SliderAssociation( panel.slider ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "createDate.date" ); - assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" ); + + assoc = new SliderAssociation(panel.slider); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "createDate.date"); + assoc.bindAspect(EOAssociation.VisibleAspect, displayGroup, "special"); assoc.establishConnection(); - - assoc = new TextAssociation( panel.infoPanel.getLabelForKey( "Day of Month" ) ); - assoc.bindAspect( EOAssociation.VisibleAspect, displayGroup, "special" ); + + assoc = new TextAssociation(panel.infoPanel.getLabelForKey("Day of Month")); + assoc.bindAspect(EOAssociation.VisibleAspect, displayGroup, "special"); assoc.establishConnection(); - + // display group action associations - AbstractButton button; - - button = (AbstractButton) - panel.savePanel.getButton( "Refresh All" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { // panel.lastNameField.setText( panel.lastNameField.getText().trim() ); - - // test all three ways - - displayGroup.dataSource().editingContext().invalidateAllObjects(); + AbstractButton button; + + button = (AbstractButton) panel.savePanel.getButton("Refresh All"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { // panel.lastNameField.setText( + // panel.lastNameField.getText().trim() ); + + // test all three ways + + displayGroup.dataSource().editingContext().invalidateAllObjects(); // displayGroup.dataSource().editingContext().parentObjectStore().invalidateAllObjects(); // displayGroup.dataSource().editingContext().revert(); } - } ); - - button = (AbstractButton) - panel.savePanel.getButton( "Commit" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - try - { - displayGroup.dataSource().editingContext().saveChanges(); - } - catch ( RuntimeException exc ) - { - JOptionPane.showMessageDialog( - (java.awt.Component)evt.getSource(), exc.getMessage() ); - exc.printStackTrace(); - } + }); + + button = (AbstractButton) panel.savePanel.getButton("Commit"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + try { + displayGroup.dataSource().editingContext().saveChanges(); + } catch (RuntimeException exc) { + JOptionPane.showMessageDialog((java.awt.Component) evt.getSource(), exc.getMessage()); + exc.printStackTrace(); + } } - } ); - - button = (AbstractButton) - panel.buttonPanel.getButton( "Add" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - displayGroup.insertNewObjectAtIndex( 0 ); + }); + + button = (AbstractButton) panel.buttonPanel.getButton("Add"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + displayGroup.insertNewObjectAtIndex(0); } - } ); - - assoc = new DisplayGroupActionAssociation( - panel.buttonPanel.getButton( "Remove" ) ); - assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "deleteSelection" ); - assoc.establishConnection(); -/* - assoc = new DisplayGroupActionAssociation( - panel.infoPanel.getButtonPanel().getButton( "Refresh" ) ); - assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" ); - assoc.establishConnection(); + }); - assoc = new DisplayGroupActionAssociation( - panel.infoPanel.getButtonPanel().getButton( "Commit" ) ); - assoc.bindAspect( EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" ); + assoc = new DisplayGroupActionAssociation(panel.buttonPanel.getButton("Remove")); + assoc.bindAspect(EOAssociation.ActionAspect, displayGroup, "deleteSelection"); assoc.establishConnection(); -*/ + /* + * assoc = new DisplayGroupActionAssociation( + * panel.infoPanel.getButtonPanel().getButton( "Refresh" ) ); assoc.bindAspect( + * EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" ); + * assoc.establishConnection(); + * + * assoc = new DisplayGroupActionAssociation( + * panel.infoPanel.getButtonPanel().getButton( "Commit" ) ); assoc.bindAspect( + * EOAssociation.ActionAspect, displayGroup, "updateDisplayedObjects" ); + * assoc.establishConnection(); + */ // add MouseListener for table - panel.table.addMouseListener( new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - if ( e.getClickCount() == 2 ) - { + panel.table.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { Object o = displayGroup.selectedObject(); - if ( o != null ) - { + if (o != null) { // new InspectorController( o ); - new EditController( - new ChildDataSource( - displayGroup.dataSource(), o ) ); - } + new EditController(new ChildDataSource(displayGroup.dataSource(), o)); + } } } }); - + // add ActionListener for tree button - ((JButton) panel.buttonPanel.getButton( - "Tree View" ) ).addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - EOEditingContext parentContext = Test.editingContext; - EOEditingContext childContext = new EOEditingContext( parentContext ); - - // transpose objects to ids to faults - List ids = new LinkedList(); - Iterator i = displayGroup.selectedObjects().iterator(); - while ( i.hasNext() ) - { - ids.add( parentContext.globalIDForObject( i.next() ) ); - } - List objects = new LinkedList(); - i = ids.iterator(); - while( i.hasNext() ) - { - objects.add( childContext.faultForGlobalID( (EOGlobalID) i.next(), childContext ) ); - } - + ((JButton) panel.buttonPanel.getButton("Tree View")).addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + EOEditingContext parentContext = Test.editingContext; + EOEditingContext childContext = new EOEditingContext(parentContext); + + // transpose objects to ids to faults + List ids = new LinkedList(); + Iterator i = displayGroup.selectedObjects().iterator(); + while (i.hasNext()) { + ids.add(parentContext.globalIDForObject(i.next())); + } + List objects = new LinkedList(); + i = ids.iterator(); + while (i.hasNext()) { + objects.add(childContext.faultForGlobalID((EOGlobalID) i.next(), childContext)); + } + EODisplayGroup treeGroup = new EODisplayGroup(); - treeGroup.setSortOrderings( new NSArray( "lastName" ) ); - treeGroup.setObjectArray( objects ); + treeGroup.setSortOrderings(new NSArray("lastName")); + treeGroup.setObjectArray(objects); - EODisplayGroup childGroup = new EODisplayGroup(); - - //childGroup.setDelegate( new DebuggingDelegate() ); - //new TreeInspectorController( treeGroup, childGroup ); - //new BindingController( treeGroup, childGroup ); + EODisplayGroup childGroup = new EODisplayGroup(); - new TreeController( childContext, treeGroup, childGroup ); + // childGroup.setDelegate( new DebuggingDelegate() ); + // new TreeInspectorController( treeGroup, childGroup ); + // new BindingController( treeGroup, childGroup ); - //NOTE: ChildDataSource is fundamentally broken + new TreeController(childContext, treeGroup, childGroup); + + // NOTE: ChildDataSource is fundamentally broken // new TreeController( new ChildDataSource( // displayGroup.dataSource(), displayGroup.selectedObjects() ) ); } }); -/* - NSNotificationCenter.defaultCenter().addObserver( - this, - new NSSelector( "hitMe", new Class[] { NSNotification.class } ), - null, null ); -*/ + /* + * NSNotificationCenter.defaultCenter().addObserver( this, new NSSelector( + * "hitMe", new Class[] { NSNotification.class } ), null, null ); + */ + } + + private String filter; + + public String getFilter() { + return filter; + } + + public void setFilter(String aFilter) { + filter = aFilter; + + EOQualifier qualifier = null; + if (!"".equals(aFilter)) { + qualifier = new EOKeyValueQualifier("firstName", EOQualifier.QualifierOperatorContains, filter); + } + displayGroup.setQualifier(qualifier); + displayGroup.updateDisplayedObjects(); + } + + public void hitMe(NSNotification aNote) { + System.out.println(aNote); } - - private String filter; - public String getFilter() - { - return filter; - } - - public void setFilter( String aFilter ) - { - filter = aFilter; - - EOQualifier qualifier = null; - if ( ! "".equals( aFilter ) ) - { - qualifier = new EOKeyValueQualifier( - "firstName", EOQualifier.QualifierOperatorContains, filter ); - } - displayGroup.setQualifier( qualifier ); - displayGroup.updateDisplayedObjects(); - } - - public void hitMe( NSNotification aNote ) - { - System.out.println( aNote ); - } - - public void actionPerformed( ActionEvent evt ) - { - Object o = displayGroup.selectedObject(); - if ( o != null ) - { + + public void actionPerformed(ActionEvent evt) { + Object o = displayGroup.selectedObject(); + if (o != null) { // new ObjectInspector( o ); - new InspectorController( o ); - } - } - + new InspectorController(o); + } + } + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java index 1d36bef..c55a386 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestDataSource.java @@ -7,109 +7,83 @@ import net.wotonomy.control.EOFetchSpecification; import net.wotonomy.foundation.NSArray; /** -* A custom DataSource that works with -* the datastore package for persistence. -*/ -public class TestDataSource extends EODataSource -{ - private EOEditingContext context; - private Object source; - private String key; - - public TestDataSource() - { - this( Test.editingContext ); + * A custom DataSource that works with the datastore package for persistence. + */ +public class TestDataSource extends EODataSource { + private EOEditingContext context; + private Object source; + private String key; + + public TestDataSource() { + this(Test.editingContext); } - - public TestDataSource( EOEditingContext aContext ) - { - context = aContext; + + public TestDataSource(EOEditingContext aContext) { + context = aContext; } - - public EOEditingContext editingContext() - { - return context; - } - - /** - * This implementation does nothing. - */ - public void insertObject ( Object anObject ) - { - // creates are handled by createObject(). + + public EOEditingContext editingContext() { + return context; } - /** - * Deletes the specified object from this data source. - */ - public void deleteObject ( Object anObject ) - { - editingContext().deleteObject( anObject ); + /** + * This implementation does nothing. + */ + public void insertObject(Object anObject) { + // creates are handled by createObject(). } - /** - * 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 () - { - if ( source == null ) - { - NSArray result = editingContext().objectsWithFetchSpecification( - new EOFetchSpecification() ); - if ( result.size() > 0 ) - { - result = new NSArray( result.objectAtIndex( 0 ) ); + /** + * Deletes the specified object from this data source. + */ + public void deleteObject(Object anObject) { + editingContext().deleteObject(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() { + if (source == null) { + NSArray result = editingContext().objectsWithFetchSpecification(new EOFetchSpecification()); + if (result.size() > 0) { + result = new NSArray(result.objectAtIndex(0)); //result.add( result.objectAtIndex( 0 ) ); - } - return result; - } - else - { - return new NSArray( - ((TestObject)source).getChildList() ); - } - } + } + return result; + } else { + return new NSArray(((TestObject) source).getChildList()); + } + } - /** - * 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 EODataSource - dataSourceQualifiedByKey ( String aKey ) - { - return new TestDataSource( editingContext() ); + /** + * 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 EODataSource dataSourceQualifiedByKey(String aKey) { + return new TestDataSource(editingContext()); } - /** - * 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 ) - { - key = aKey; - source = anObject; + /** + * 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) { + key = aKey; + source = 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 TestObject. - */ - public EOClassDescription - classDescriptionForObjects () - { - return EOClassDescription.classDescriptionForClass( - TestObject.class ); - } + /** + * 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 TestObject. + */ + public EOClassDescription classDescriptionForObjects() { + return EOClassDescription.classDescriptionForClass(TestObject.class); + } } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java index 8a88e68..a7f4e06 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestMap.java @@ -12,153 +12,131 @@ import net.wotonomy.datastore.SerializedFileSoup; import net.wotonomy.datastore.XMLFileSoup; import net.wotonomy.foundation.internal.ValueConverter; -public class TestMap extends HashMap -{ - public TestMap() - { - put( "date", new Date() ); - put( "firstName", randomParse( - "Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle") ); - put( "middleName", new StringBuffer( randomParse( - "Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay") ) ); - put( "lastName", randomParse( - "Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping") ); - put( "address", randomParse( "1|2|3|4" ) + randomParse( "0|1|00|10|5|50" ) + - randomParse( "0|00|1|01|5|05|9|09||000" ) + " " + randomParse( - "Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " " - + randomParse( "Road|Lane|Court|Drive|Parkway|Terrace" ) ); - put( "city", randomParse( - "Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade") ); - put( "state", randomParse( - "TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN" ) ); - put( "zip", ValueConverter.getInteger( - randomParse( "1|2|3|4" ) + "0" + randomParse( "0|1|2|3|5" ) + - randomParse( "6|7|8|9" ) + randomParse( "6|7|8|9" ) ) ); - put( "age", new Short( (short) ( new Random().nextDouble() * 40 + 18 ) ) ); +public class TestMap extends HashMap { + public TestMap() { + put("date", new Date()); + put("firstName", randomParse("Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle")); + put("middleName", + new StringBuffer(randomParse("Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay"))); + put("lastName", randomParse("Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping")); + put("address", + randomParse("1|2|3|4") + randomParse("0|1|00|10|5|50") + randomParse("0|00|1|01|5|05|9|09||000") + " " + + randomParse("Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " " + + randomParse("Road|Lane|Court|Drive|Parkway|Terrace")); + put("city", randomParse("Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade")); + put("state", randomParse("TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN")); + put("zip", ValueConverter.getInteger(randomParse("1|2|3|4") + "0" + randomParse("0|1|2|3|5") + + randomParse("6|7|8|9") + randomParse("6|7|8|9"))); + put("age", new Short((short) (new Random().nextDouble() * 40 + 18))); childCount = -1; - } - + } + protected int childCount; - public int getChildCount() - { - if ( childCount == -1 ) - { - //childCount = (int) ( random.nextDouble() * 6 ) - 3; // + 100; // tree scalability test - if ( childCount < 0 ) childCount = 0; + + public int getChildCount() { + if (childCount == -1) { + // childCount = (int) ( random.nextDouble() * 6 ) - 3; // + 100; // tree + // scalability test + if (childCount < 0) + childCount = 0; } - return childCount; + return childCount; }; - + protected TestMap[] children; - public TestMap[] getChildren() - { - if ( get( "children" ) == null ) - { + + public TestMap[] getChildren() { + if (get("children") == null) { int n = getChildCount(); - TestMap[] children = new TestMap[ n ]; - for ( int i = 0; i < n; i++ ) - { + TestMap[] children = new TestMap[n]; + for (int i = 0; i < n; i++) { children[i] = new TestMap(); } - put( "children", children ); + put("children", children); } - return (TestMap[]) get( "children" ); + return (TestMap[]) get("children"); } - public void setChildren( TestMap[] aChildArray ) - { - put( "children", aChildArray ); + + public void setChildren(TestMap[] aChildArray) { + put("children", aChildArray); } - public List getChildList() - { + + public List getChildList() { List result = new LinkedList(); TestMap[] childArray = getChildren(); - for ( int i = 0; i < childArray.length; i++ ) - { - result.add( childArray[i] ); + for (int i = 0; i < childArray.length; i++) { + result.add(childArray[i]); } return result; } - public void setChildList( List aChildList ) - { - TestMap[] children = new TestMap[ aChildList.size() ]; - for ( int i = 0; i < children.length; i++ ) - { - children[i] = (TestMap) aChildList.get( i ); + + public void setChildList(List aChildList) { + TestMap[] children = new TestMap[aChildList.size()]; + for (int i = 0; i < children.length; i++) { + children[i] = (TestMap) aChildList.get(i); } - setChildren( children ); + setChildren(children); } - - public String getFullName() - { - return get( "firstName" ) + " " + get( "middleName" ) + " " + get( "lastName" ); - } - - public boolean equals( Object anObject ) - { - return anObject == this; - } - - public String toString() - { - return "[" + getClass().getName() + ":" + getFullName() + "]"; + + public String getFullName() { + return get("firstName") + " " + get("middleName") + " " + get("lastName"); + } + + public boolean equals(Object anObject) { + return anObject == this; + } + + public String toString() { + return "[" + getClass().getName() + ":" + getFullName() + "]"; + } + + // statics + + private static Random random = new Random(); + + private static String randomParse(String aString) { + String result = ""; + StringTokenizer tokens = new StringTokenizer(aString, "|"); + int n = (int) (random.nextDouble() * tokens.countTokens()); + for (int i = 0; i <= n; i++) { + result = tokens.nextToken(); + } + return result; + } + + public static void main(String[] argv) { + int count = 100; + boolean xmlMode = false; + if (argv.length > 0) { + Integer parsed = ValueConverter.getInteger(argv[0]); + if (parsed != null) + count = parsed.intValue(); + + if (argv.length > 1) { + if (argv[1].indexOf("xml") > -1) { + xmlMode = true; + } + } + } + + long millis = System.currentTimeMillis(); + + DataSoup store = null; + if (xmlMode) { + store = new XMLFileSoup("testMaps-xml"); + } else { + store = new SerializedFileSoup("testMaps-java"); + } + + Object o; + for (int i = 0; i < count; i++) { + store.addObject(new TestMap()); + } + /* + * store.addIndex( "age", "age" ); store.addIndex( "zipCode", "zipCode" ); + * store.addIndex( "firstName", "firstName" ); store.addIndex( "lastName", + * "lastName" ); + */ + System.out.println(System.currentTimeMillis() - millis + " milliseconds"); } - - // statics - - private static Random random = new Random(); - private static String randomParse( String aString ) - { - String result = ""; - StringTokenizer tokens = new StringTokenizer( aString, "|" ); - int n = (int) ( random.nextDouble() * tokens.countTokens() ); - for ( int i = 0; i <= n; i++ ) - { - result = tokens.nextToken(); - } - return result; - } - - public static void main( String[] argv ) - { - int count = 100; - boolean xmlMode = false; - if ( argv.length > 0 ) - { - Integer parsed = ValueConverter.getInteger( argv[0] ); - if ( parsed != null ) count = parsed.intValue(); - - if ( argv.length > 1 ) - { - if ( argv[1].indexOf( "xml" ) > -1 ) - { - xmlMode = true; - } - } - } - -long millis = System.currentTimeMillis(); - - DataSoup store = null; - if ( xmlMode ) - { - store = new XMLFileSoup( "testMaps-xml" ); - } - else - { - store = new SerializedFileSoup( "testMaps-java" ); - } - - Object o; - for ( int i = 0; i < count; i++ ) - { - store.addObject( new TestMap() ); - } - /* - store.addIndex( "age", "age" ); - store.addIndex( "zipCode", "zipCode" ); - store.addIndex( "firstName", "firstName" ); - store.addIndex( "lastName", "lastName" ); -*/ -System.out.println( System.currentTimeMillis() - millis + " milliseconds" ); - } } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java index 72a3dbc..08b46b7 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObject.java @@ -16,347 +16,344 @@ import net.wotonomy.foundation.internal.ValueConverter; public class TestObject implements Serializable // , EOKeyValueCoding { - static final long serialVersionUID = -5482454640042392838L; - + static final long serialVersionUID = -5482454640042392838L; + // for testing manual array faulting -public EOEditingContext editingContext; -public EOEditingContext getEditingContext() { return editingContext; }; - - public TestObject() - { - date = new Date(); - firstName = randomParse( - "Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle"); - middleName = new StringBuffer( randomParse( - "Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay") ); - lastName = randomParse( - "Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping"); - address = randomParse( "1|2|3|4" ) + randomParse( "0|1|00|10|5|50" ) + - randomParse( "0|00|1|01|5|05|9|09||000" ) + " " + randomParse( - "Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " " - + randomParse( "Road|Lane|Court|Drive|Parkway|Terrace" ); - city = randomParse( - "Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade"); - state = randomParse( - "TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN" ); - zip = ValueConverter.getIntValue( - randomParse( "1|2|3|4" ) + "0" + randomParse( "0|1|2|3|5" ) + - randomParse( "6|7|8|9" ) + randomParse( "6|7|8|9" ) ); - age = (short) ( new Random().nextDouble() * 40 + 18 ); + public EOEditingContext editingContext; + + public EOEditingContext getEditingContext() { + return editingContext; + }; + + public TestObject() { + date = new Date(); + firstName = randomParse("Bert|Ernie|Elmo|Zoe|Arthur|Emily|DJ|Grover|Oscar|Max|Big|Twinkle"); + middleName = new StringBuffer( + randomParse("Rufus|Remy|Martin|Josephus|Ulysses|Homer|Bart|Tip|Onegin|Meredith|Jay")); + lastName = randomParse("Alejandro|Alexander|Bird|Gosling|Joy|Van Hoff|Pedia|Marr|McNealy|Ping"); + address = randomParse("1|2|3|4") + randomParse("0|1|00|10|5|50") + randomParse("0|00|1|01|5|05|9|09||000") + " " + + randomParse("Merry|Berry|Perry|Jerry|Meadow|Falls|Elm|Raspberry|Strawberry") + " " + + randomParse("Road|Lane|Court|Drive|Parkway|Terrace"); + city = randomParse("Springfield|Sterling|Cascades|Vienna|Reston|Paris|London|Runnymeade"); + state = randomParse("TX|NJ|NY|VA|DC|MD|NC|SC|WV|AR|FL|CA|TN"); + zip = ValueConverter.getIntValue(randomParse("1|2|3|4") + "0" + randomParse("0|1|2|3|5") + + randomParse("6|7|8|9") + randomParse("6|7|8|9")); + age = (short) (new Random().nextDouble() * 40 + 18); childCount = -1; // children = null; - childList = null; - } - - protected Date date; - public Date getCreateDate() { return date; } - public void setCreateDate( Date aDate ) { date = aDate; } - - protected String firstName; - public String getFirstName() { return firstName; } - public void setFirstName( String aName ) { firstName = aName; } - - protected String lastName; - public String getLastName() { return lastName; } - public void setLastName( String aName ) { - if ( "Jones".equals( aName ) ) throw new RuntimeException( "Jones not allowed" ) ; - lastName = aName; - } - - protected StringBuffer middleName; - public StringBuffer getMiddleName() { return middleName; } - public void setMiddleName( StringBuffer aName ) { middleName = aName; } - - protected String address; - public String getAddress() { return address; } - public void setAddress( String anAddress ) { address = anAddress; } - - protected String city; - public String getCity() { return city; } - public void setCity( String aCity ) { city = aCity; } - - protected String state; - public String getState() { return state; } - public void setState( String aState ) { state = aState; } - - protected int zip; - public int getZipCode() { return zip; } - public void setZipCode( int aZipCode ) { zip = aZipCode; } - - protected short age; - public short getAge() { return age; } - public void setAge( short anAge ) { age = anAge; } - - protected boolean special; - public Boolean isSpecial() { return new Boolean( special ); } - public void setSpecial( Boolean isSpecial ) { special = isSpecial.booleanValue(); } - -/* - protected Object[] children; - - private Object[] getChildren() - { - if ( children == null ) - { - int n = getChildCount(); - children = new Object[ n ]; - for ( int i = 0; i < n; i++ ) - { - children[i] = new TestObject(); - } - //System.out.println( "TestObject.getChildren: " + toString() + " : " + getChildCount() ); - } - return children; - } - private void setChildren( Object[] aChildArray ) - { - children = aChildArray; - childCount = aChildArray.length; - } - - // following child list implementation wraps child array - - public List getChildList() - { - List result = new LinkedList(); - Object[] childArray = getChildren(); - for ( int i = 0; i < childArray.length; i++ ) - { - result.add( childArray[i] ); - } - return result; + childList = null; } - public void setChildList( List aChildList ) - { - children = new Object[ aChildList.size() ]; - for ( int i = 0; i < children.length; i++ ) - { - children[i] = (TestObject) aChildList.get( i ); - } - childCount = children.length; + + protected Date date; + + public Date getCreateDate() { + return date; + } + + public void setCreateDate(Date aDate) { + date = aDate; + } + + protected String firstName; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String aName) { + firstName = aName; + } + + protected String lastName; + + public String getLastName() { + return lastName; + } + + public void setLastName(String aName) { + if ("Jones".equals(aName)) + throw new RuntimeException("Jones not allowed"); + lastName = aName; + } + + protected StringBuffer middleName; + + public StringBuffer getMiddleName() { + return middleName; + } + + public void setMiddleName(StringBuffer aName) { + middleName = aName; + } + + protected String address; + + public String getAddress() { + return address; + } + + public void setAddress(String anAddress) { + address = anAddress; + } + + protected String city; + + public String getCity() { + return city; + } + + public void setCity(String aCity) { + city = aCity; + } + + protected String state; + + public String getState() { + return state; + } + + public void setState(String aState) { + state = aState; + } + + protected int zip; + + public int getZipCode() { + return zip; + } + + public void setZipCode(int aZipCode) { + zip = aZipCode; + } + + protected short age; + + public short getAge() { + return age; + } + + public void setAge(short anAge) { + age = anAge; + } + + protected boolean special; + + public Boolean isSpecial() { + return new Boolean(special); } -*/ + + public void setSpecial(Boolean isSpecial) { + special = isSpecial.booleanValue(); + } + + /* + * protected Object[] children; + * + * private Object[] getChildren() { if ( children == null ) { int n = + * getChildCount(); children = new Object[ n ]; for ( int i = 0; i < n; i++ ) { + * children[i] = new TestObject(); } //System.out.println( + * "TestObject.getChildren: " + toString() + " : " + getChildCount() ); } return + * children; } private void setChildren( Object[] aChildArray ) { children = + * aChildArray; childCount = aChildArray.length; } + * + * // following child list implementation wraps child array + * + * public List getChildList() { List result = new LinkedList(); Object[] + * childArray = getChildren(); for ( int i = 0; i < childArray.length; i++ ) { + * result.add( childArray[i] ); } return result; } public void setChildList( + * List aChildList ) { children = new Object[ aChildList.size() ]; for ( int i = + * 0; i < children.length; i++ ) { children[i] = (TestObject) aChildList.get( i + * ); } childCount = children.length; } + */ protected int childCount; - public int getChildCount() - { - if ( childCount == -1 ) - { + + public int getChildCount() { + if (childCount == -1) { // uncomment this to enable random children // childCount = (int) ( random.nextDouble() * 6 ) - 3; // + 100; // tree scalability test - if ( childCount < 0 ) childCount = 0; + if (childCount < 0) + childCount = 0; } - + // this tests internal count // return childCount; // this tests deferred count - if ( childList != null ) - { - return childList.size(); - } - else - { - return 0; - } + if (childList != null) { + return childList.size(); + } else { + return 0; + } }; - // following child list implementation stands alone + // following child list implementation stands alone + + protected List childList; - protected List childList; - public List getChildList() - { -/* - // this tests random child population - if ( childList == null ) - { - int n = getChildCount(); + public List getChildList() { + /* + * // this tests random child population if ( childList == null ) { int n = + * getChildCount(); childList = new LinkedList(); for ( int i = 0; i < n; i++ ) + * { childList.add( new TestObject() ); } } + */ + // this tests manual loading + if (childList == null) { childList = new LinkedList(); - for ( int i = 0; i < n; i++ ) - { - childList.add( new TestObject() ); - } - } -*/ - // this tests manual loading - if ( childList == null ) - { - childList = new LinkedList(); } return childList; } - public void setChildList( List aChildList ) - { - childList = aChildList; - } - - protected TestObject parent; - public TestObject getParent() { return parent; } - public void setParent( TestObject anObject ) { parent = anObject; } - - public String getHash() { return Integer.toHexString( System.identityHashCode( this ) ); } - - public String getFullName() - { + + public void setChildList(List aChildList) { + childList = aChildList; + } + + protected TestObject parent; + + public TestObject getParent() { + return parent; + } + + public void setParent(TestObject anObject) { + parent = anObject; + } + + public String getHash() { + return Integer.toHexString(System.identityHashCode(this)); + } + + public String getFullName() { // return getHash() + ": " + firstName + " " + middleName + " " + lastName; - return firstName + " " + middleName + " " + lastName; - } - - public boolean equals( Object anObject ) - { - return anObject == this; - } - - public String toString() - { - return "[" + getClass().getName() - + "@" + Integer.toHexString( System.identityHashCode(this) ) - + ":" + getFullName() + "]"; - } - - // statics - - private static Random random = new Random(); - private static String randomParse( String aString ) - { - String result = ""; - StringTokenizer tokens = new StringTokenizer( aString, "|" ); - int n = (int) ( random.nextDouble() * tokens.countTokens() ); - for ( int i = 0; i <= n; i++ ) - { - result = tokens.nextToken(); - } - return result; - } - - - // interface EOKeyValueCoding: - // disable this interface by commenting out the "implements" declaration - - /** - * Returns the value for the specified property. - * If the property does not exist, this method should - * call handleQueryWithUnboundKey. - */ - public Object valueForKey( String aKey ) - { - System.out.println( "valueForKey: " + 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 ) - { - System.out.println( "takeValueForKey: " + aValue + " : " + aKey ); - EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey ); - } - - /** - * Returns the value for the private field that - * corresponds to the specified property. - */ - public Object storedValueForKey( String aKey ) - { - System.out.println( "storedValueForKey: " + 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 ) - { - System.out.println( "takeStoredValueForKey: " + aValue + " : " + 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 ) - { - System.out.println( "handleQueryWithUnboundKey: " + 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 ) - { - System.out.println( "handleTakeValueForUnboundKey: " + aValue + " : " + 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 ) - { - System.out.println( "unableToSetNullForKey: " + aKey ); - EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); - } - - - // main entry point - - public static void main( String[] argv ) - { - int count = 100; - boolean xmlMode = false; - if ( argv.length > 0 ) - { - Integer parsed = ValueConverter.getInteger( argv[0] ); - if ( parsed != null ) count = parsed.intValue(); - - if ( argv.length > 1 ) - { - if ( argv[1].indexOf( "xml" ) > -1 ) - { - xmlMode = true; - } - } - } - -long millis = System.currentTimeMillis(); - - DataSoup store = null; - if ( xmlMode ) - { - store = new XMLFileSoup( "testObjects-xml" ); - } - else - { - store = new SerializedFileSoup( "testObjects-java" ); - } - - Object o; - for ( int i = 0; i < count; i++ ) - { - store.addObject( new TestObject() ); - } - /* - store.addIndex( "age", "age" ); - store.addIndex( "zipCode", "zipCode" ); - store.addIndex( "firstName", "firstName" ); - store.addIndex( "lastName", "lastName" ); -*/ -System.out.println( System.currentTimeMillis() - millis + " milliseconds" ); - } + return firstName + " " + middleName + " " + lastName; + } + + public boolean equals(Object anObject) { + return anObject == this; + } + + public String toString() { + return "[" + getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this)) + ":" + + getFullName() + "]"; + } + + // statics + + private static Random random = new Random(); + + private static String randomParse(String aString) { + String result = ""; + StringTokenizer tokens = new StringTokenizer(aString, "|"); + int n = (int) (random.nextDouble() * tokens.countTokens()); + for (int i = 0; i <= n; i++) { + result = tokens.nextToken(); + } + return result; + } + + // interface EOKeyValueCoding: + // disable this interface by commenting out the "implements" declaration + + /** + * Returns the value for the specified property. If the property does not exist, + * this method should call handleQueryWithUnboundKey. + */ + public Object valueForKey(String aKey) { + System.out.println("valueForKey: " + 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) { + System.out.println("takeValueForKey: " + aValue + " : " + aKey); + EOKeyValueCodingSupport.takeValueForKey(this, aValue, aKey); + } + + /** + * Returns the value for the private field that corresponds to the specified + * property. + */ + public Object storedValueForKey(String aKey) { + System.out.println("storedValueForKey: " + 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) { + System.out.println("takeStoredValueForKey: " + aValue + " : " + 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) { + System.out.println("handleQueryWithUnboundKey: " + 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) { + System.out.println("handleTakeValueForUnboundKey: " + aValue + " : " + 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) { + System.out.println("unableToSetNullForKey: " + aKey); + EOKeyValueCodingSupport.unableToSetNullForKey(this, aKey); + } + + // main entry point + + public static void main(String[] argv) { + int count = 100; + boolean xmlMode = false; + if (argv.length > 0) { + Integer parsed = ValueConverter.getInteger(argv[0]); + if (parsed != null) + count = parsed.intValue(); + + if (argv.length > 1) { + if (argv[1].indexOf("xml") > -1) { + xmlMode = true; + } + } + } + + long millis = System.currentTimeMillis(); + + DataSoup store = null; + if (xmlMode) { + store = new XMLFileSoup("testObjects-xml"); + } else { + store = new SerializedFileSoup("testObjects-java"); + } + + Object o; + for (int i = 0; i < count; i++) { + store.addObject(new TestObject()); + } + /* + * store.addIndex( "age", "age" ); store.addIndex( "zipCode", "zipCode" ); + * store.addIndex( "firstName", "firstName" ); store.addIndex( "lastName", + * "lastName" ); + */ + System.out.println(System.currentTimeMillis() - millis + " milliseconds"); + } } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java index beb852c..409d900 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectClassDesc.java @@ -4,28 +4,22 @@ import net.wotonomy.control.EOClassDescription; import net.wotonomy.foundation.NSArray; /** -* A simple class description for testing. -*/ -public class TestObjectClassDesc extends EOClassDescription -{ - public TestObjectClassDesc() - { - super( TestObject.class ); - } + * A simple class description for testing. + */ +public class TestObjectClassDesc extends EOClassDescription { + public TestObjectClassDesc() { + super(TestObject.class); + } - public EOClassDescription classDescriptionForDestinationKey( - String detailKey ) - { - if ( "childList".equals( detailKey ) ) - { - return this; - } - return null; - } - - public NSArray toManyRelationshipKeys() - { - return new NSArray( "childList" ); - } + public EOClassDescription classDescriptionForDestinationKey(String detailKey) { + if ("childList".equals(detailKey)) { + return this; + } + return null; + } + + public NSArray toManyRelationshipKeys() { + return new NSArray("childList"); + } } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java index 8fcb8a0..316fcf7 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestObjectStore.java @@ -19,240 +19,177 @@ import net.wotonomy.foundation.internal.Duplicator; import net.wotonomy.foundation.internal.WotonomyException; /** -* An object store that wraps a datastore -* for vending test objects. -*/ -public class TestObjectStore extends EOObjectStore -{ - DataSoup soup; - - /** - * Constructor specifies path to datastore. - */ - public TestObjectStore( String aPath ) - { - soup = new XMLFileSoup( aPath ); - } - - /** - * This implementation simply returns - * objectsWithSourceGlobalID. - */ - public NSArray arrayFaultWithSourceGlobalID ( - EOGlobalID aGlobalID, - String aRelationship, - EOEditingContext aContext ) - { - return objectsForSourceGlobalID( - aGlobalID, aRelationship, aContext ); - } - - /** - * This implementation returns the actual - * object for the specified id. - */ - public Object faultForGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { -System.out.println( "TestObjectStore: * reading object * : " + aGlobalID ); - return soup.getObjectByKey( - ((DataKeyID)aGlobalID).getKey() ); - } - - /** - * 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 Object faultForRawRow ( - Map aDictionary, - String anEntityName, - EOEditingContext aContext ) - { - //TODO: faults 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( "TestObjectStore: * reading object * : " + aGlobalID ); - Object original = soup.getObjectByKey( - ((DataKeyID)aGlobalID).getKey() ); - EOObserverCenter.notifyObserversObjectWillChange( anObject ); - Duplicator.deepCopy( original, anObject ); - //TODO: need to handle child object registration in aContext - } - - /** - * 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 () - { - // does nothing - } - - /** - * 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 ) - { - // does nothing - } - - /** - * Returns false because locking is not permitted. - */ - public boolean isObjectLockedWithGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { - return false; - } - - /** - * Does nothing because locking is not 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. Faults are not allowed in the array. - * All objects should belong to the specified editing context. - */ - public NSArray objectsForSourceGlobalID ( - EOGlobalID aGlobalID, - String aRelationship, - EOEditingContext aContext ) - { - //TODO: relationships are not yet supported - throw new WotonomyException( - "Relationships are not yet supported." ); - } - - /** - * 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 registered in the specified context, - * it is not refetched and that object should be used in the array. - */ - public NSArray objectsWithFetchSpecification ( - EOFetchSpecification aFetchSpec, - EOEditingContext aContext ) - { - //TODO: fetch specs are not yet supported - - DataView view = soup.queryObjects( null, null ); -System.out.println( "TestObjectStore: ** querying all objects **" ); - NSMutableArray result = new NSMutableArray(); - Object o; - Object existing; - DataKeyID id; - Iterator it = view.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - id = new DataKeyID( view.getKeyForObject( o ) ); - existing = aContext.objectForGlobalID( id ); - if ( existing != null ) - { - o = existing; - } - else - { - aContext.recordObject( o, id ); - } - result.addObject( o ); - } - return result; - } - - /** - * 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 ) - { - //TODO: faults are not yet supported - // just re-initialize the object - initializeObject( anObject, aGlobalID, aContext ); - } - - /** - * Writes all changes in the specified editing context - * to the respository. - */ - public void saveChangesInEditingContext ( - EOEditingContext aContext ) - { - Object o; - DataKeyID id; - Iterator it; - - System.out.println( aContext.updatedObjects() ); - - // process updates - it = aContext.updatedObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - id = (DataKeyID) aContext.globalIDForObject( o ); -System.out.println( "TestObjectStore: * updating object * : " + id ); - soup.updateObject( id.getKey(), o ); - } - - // process deletes - it = aContext.deletedObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - id = (DataKeyID) aContext.globalIDForObject( o ); -System.out.println( "TestObjectStore: * deleting object * : " + id ); - soup.removeObject( id.getKey() ); - // remove object from editing context - aContext.forgetObject( o ); - } - - // process inserts - it = aContext.insertedObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - id = new DataKeyID( soup.addObject( o ) ); -System.out.println( "TestObjectStore: * adding object * : " + id ); - // register object in editing context with new id - aContext.forgetObject( o ); - aContext.recordObject( o, id ); - } - - } -} + * An object store that wraps a datastore for vending test objects. + */ +public class TestObjectStore extends EOObjectStore { + DataSoup soup; + + /** + * Constructor specifies path to datastore. + */ + public TestObjectStore(String aPath) { + soup = new XMLFileSoup(aPath); + } + + /** + * This implementation simply returns objectsWithSourceGlobalID. + */ + public NSArray arrayFaultWithSourceGlobalID(EOGlobalID aGlobalID, String aRelationship, EOEditingContext aContext) { + return objectsForSourceGlobalID(aGlobalID, aRelationship, aContext); + } + + /** + * This implementation returns the actual object for the specified id. + */ + public Object faultForGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + System.out.println("TestObjectStore: * reading object * : " + aGlobalID); + return soup.getObjectByKey(((DataKeyID) aGlobalID).getKey()); + } + + /** + * 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 Object faultForRawRow(Map aDictionary, String anEntityName, EOEditingContext aContext) { + // TODO: faults 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("TestObjectStore: * reading object * : " + aGlobalID); + Object original = soup.getObjectByKey(((DataKeyID) aGlobalID).getKey()); + EOObserverCenter.notifyObserversObjectWillChange(anObject); + Duplicator.deepCopy(original, anObject); + // TODO: need to handle child object registration in aContext + } + + /** + * 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() { + // does nothing + } + + /** + * 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) { + // does nothing + } + + /** + * Returns false because locking is not permitted. + */ + public boolean isObjectLockedWithGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + return false; + } + + /** + * Does nothing because locking is not 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. Faults are not allowed in the array. + * All objects should belong to the specified editing context. + */ + public NSArray objectsForSourceGlobalID(EOGlobalID aGlobalID, String aRelationship, EOEditingContext aContext) { + // TODO: relationships are not yet supported + throw new WotonomyException("Relationships are not yet supported."); + } + + /** + * 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 registered in + * the specified context, it is not refetched and that object should be used in + * the array. + */ + public NSArray objectsWithFetchSpecification(EOFetchSpecification aFetchSpec, EOEditingContext aContext) { + // TODO: fetch specs are not yet supported + + DataView view = soup.queryObjects(null, null); + System.out.println("TestObjectStore: ** querying all objects **"); + NSMutableArray result = new NSMutableArray(); + Object o; + Object existing; + DataKeyID id; + Iterator it = view.iterator(); + while (it.hasNext()) { + o = it.next(); + id = new DataKeyID(view.getKeyForObject(o)); + existing = aContext.objectForGlobalID(id); + if (existing != null) { + o = existing; + } else { + aContext.recordObject(o, id); + } + result.addObject(o); + } + return result; + } + + /** + * 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) { + // TODO: faults are not yet supported + // just re-initialize the object + initializeObject(anObject, aGlobalID, aContext); + } + + /** + * Writes all changes in the specified editing context to the respository. + */ + public void saveChangesInEditingContext(EOEditingContext aContext) { + Object o; + DataKeyID id; + Iterator it; + + System.out.println(aContext.updatedObjects()); + + // process updates + it = aContext.updatedObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + id = (DataKeyID) aContext.globalIDForObject(o); + System.out.println("TestObjectStore: * updating object * : " + id); + soup.updateObject(id.getKey(), o); + } + + // process deletes + it = aContext.deletedObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + id = (DataKeyID) aContext.globalIDForObject(o); + System.out.println("TestObjectStore: * deleting object * : " + id); + soup.removeObject(id.getKey()); + // remove object from editing context + aContext.forgetObject(o); + } + + // process inserts + it = aContext.insertedObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + id = new DataKeyID(soup.addObject(o)); + System.out.println("TestObjectStore: * adding object * : " + id); + // register object in editing context with new id + aContext.forgetObject(o); + aContext.recordObject(o, id); + } + + } +} diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java index 7e868d0..78eedb6 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TestPanel.java @@ -20,14 +20,12 @@ import net.wotonomy.ui.swing.components.ButtonPanel; import net.wotonomy.ui.swing.components.InfoPanel; /** -* A master-detail panel with a list, some -* textfields and some buttons. -*/ -public class TestPanel extends JPanel -{ + * A master-detail panel with a list, some textfields and some buttons. + */ +public class TestPanel extends JPanel { // public JList list; public JTable table; - public InfoPanel infoPanel; + public InfoPanel infoPanel; public ButtonPanel savePanel; public ButtonPanel buttonPanel; public JTextComponent firstNameField; @@ -35,76 +33,73 @@ public class TestPanel extends JPanel public JComboBox dateBox, monthBox, yearBox; public JSlider slider; public JCheckBox checkbox; - - public TestPanel() - { - this.setLayout( new BorderLayout( 10, 10 ) ); - this.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - - JPanel overviewPanel = new JPanel(); - overviewPanel.setLayout( new BorderLayout() ); - - //list = new JList(); - //JScrollPane scrollPane = new JScrollPane( list ); + + public TestPanel() { + this.setLayout(new BorderLayout(10, 10)); + this.setBorder(new EmptyBorder(10, 10, 10, 10)); + + JPanel overviewPanel = new JPanel(); + overviewPanel.setLayout(new BorderLayout()); + + // list = new JList(); + // JScrollPane scrollPane = new JScrollPane( list ); table = new JTable(); - JScrollPane scrollPane = new JScrollPane( table ); - - overviewPanel.add( scrollPane, BorderLayout.CENTER ); - - this.add( overviewPanel, BorderLayout.CENTER ); - - infoPanel = new InfoPanel(); - infoPanel.setColumns( 1 ); + JScrollPane scrollPane = new JScrollPane(table); + + overviewPanel.add(scrollPane, BorderLayout.CENTER); + + this.add(overviewPanel, BorderLayout.CENTER); + + infoPanel = new InfoPanel(); + infoPanel.setColumns(1); // name fields - firstNameField = new JTextField(); + firstNameField = new JTextField(); // infoPanel.addPair( "First Name", firstNameField ); middleNameField = new JTextField(); // infoPanel.addPair( "Middle Name", middleNameField ); lastNameField = new JTextField(); // infoPanel.addPair( "Last Name", lastNameField ); - checkbox = new JCheckBox(); + checkbox = new JCheckBox(); + + infoPanel.addRow("Name", new Component[] { firstNameField, middleNameField, lastNameField, checkbox }); - infoPanel.addRow( "Name", new Component[] { - firstNameField, middleNameField, lastNameField, checkbox } ); - // date comboboxen Vector datesList = new Vector(); - for ( int i = 1; i < 32; i++ ) datesList.add( new Integer( i ) ); - dateBox = new JComboBox( datesList ); - dateBox.setEditable( true ); + for (int i = 1; i < 32; i++) + datesList.add(new Integer(i)); + dateBox = new JComboBox(datesList); + dateBox.setEditable(true); monthBox = new JComboBox(); yearBox = new JComboBox(); - infoPanel.addRow( "Create Date", - dateBox, monthBox, yearBox ); - + infoPanel.addRow("Create Date", dateBox, monthBox, yearBox); + // year slider - infoPanel.addRow( "Day of Month", slider = new JSlider( - JSlider.HORIZONTAL, 1, 31, 1 ) ); - + infoPanel.addRow("Day of Month", slider = new JSlider(JSlider.HORIZONTAL, 1, 31, 1)); + // navigation buttons - - JPanel navigationPanel = new JPanel(); - navigationPanel.setLayout( new BorderLayout() ); - - buttonPanel = new ButtonPanel( new String[] { "Tree View", "Add", "Remove" } ); - buttonPanel.setAlignment( BetterFlowLayout.LEFT ); - buttonPanel.setInsets( new Insets( 0, 0, 0, 0 ) ); - navigationPanel.add( buttonPanel, BorderLayout.WEST ); - - savePanel = new ButtonPanel( new String[] { "Refresh All", "Commit" } ); - savePanel.setAlignment( BetterFlowLayout.RIGHT ); - navigationPanel.add( savePanel, BorderLayout.EAST ); - - // bottom panel layout - - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout( new BorderLayout() ); - bottomPanel.add( infoPanel, BorderLayout.NORTH ); - bottomPanel.add( navigationPanel, BorderLayout.SOUTH ); - - this.add( bottomPanel, BorderLayout.SOUTH ); - } - + + JPanel navigationPanel = new JPanel(); + navigationPanel.setLayout(new BorderLayout()); + + buttonPanel = new ButtonPanel(new String[] { "Tree View", "Add", "Remove" }); + buttonPanel.setAlignment(BetterFlowLayout.LEFT); + buttonPanel.setInsets(new Insets(0, 0, 0, 0)); + navigationPanel.add(buttonPanel, BorderLayout.WEST); + + savePanel = new ButtonPanel(new String[] { "Refresh All", "Commit" }); + savePanel.setAlignment(BetterFlowLayout.RIGHT); + navigationPanel.add(savePanel, BorderLayout.EAST); + + // bottom panel layout + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BorderLayout()); + bottomPanel.add(infoPanel, BorderLayout.NORTH); + bottomPanel.add(navigationPanel, BorderLayout.SOUTH); + + this.add(bottomPanel, BorderLayout.SOUTH); + } + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java index d61d8ff..c5f596b 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeController.java @@ -27,259 +27,203 @@ import net.wotonomy.ui.swing.util.ObjectInspector; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* A simple editor panel with a few textfields. -*/ -public class TreeController implements ActionListener -{ - final EODisplayGroup group; - final EODisplayGroup titlesGroup; - final EODisplayGroup detailGroup; - final EOEditingContext editingContext; - - public TreeController( EODataSource aDataSource ) - { - titlesGroup = new EODisplayGroup(); - group = new EODisplayGroup(); - detailGroup = new EODisplayGroup(); - titlesGroup.setDataSource( aDataSource ); - editingContext = aDataSource.editingContext(); - init(); - } - - public TreeController( EOEditingContext aContext, - EODisplayGroup aTitlesGroup, EODisplayGroup aChildGroup ) - { - titlesGroup = aTitlesGroup; - group = aChildGroup; - detailGroup = new EODisplayGroup(); - editingContext = aContext; - init(); - } - - public void init() - { + * A simple editor panel with a few textfields. + */ +public class TreeController implements ActionListener { + final EODisplayGroup group; + final EODisplayGroup titlesGroup; + final EODisplayGroup detailGroup; + final EOEditingContext editingContext; + + public TreeController(EODataSource aDataSource) { + titlesGroup = new EODisplayGroup(); + group = new EODisplayGroup(); + detailGroup = new EODisplayGroup(); + titlesGroup.setDataSource(aDataSource); + editingContext = aDataSource.editingContext(); + init(); + } + + public TreeController(EOEditingContext aContext, EODisplayGroup aTitlesGroup, EODisplayGroup aChildGroup) { + titlesGroup = aTitlesGroup; + group = aChildGroup; + detailGroup = new EODisplayGroup(); + editingContext = aContext; + init(); + } + + public void init() { final TreePanel treePanel = new TreePanel(); - treePanel.panel.setBorder( - BorderFactory.createCompoundBorder( - BorderFactory.createRaisedBevelBorder(), - BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ) ); - treePanel.setBorder( - BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) ); - ButtonPanel okPanel = new ButtonPanel( - new String[] { "Refresh", "Commit" } ); - treePanel.add( okPanel, BorderLayout.SOUTH ); -/* - - // set up renderer - IconCellRenderer iconRenderer = new IconCellRenderer() - { - private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon"); - public Icon getIconForContext( - JComponent container, Object value, - int row, int col, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - return icon; - } - }; - treePanel.tree.setCellRenderer( iconRenderer ); - - // enable icon clicking - treePanel.tree.setCellEditor( iconRenderer ); - iconRenderer.addActionListener( this ); -*/ - - treePanel.tree.setEditable( true ); - - // set up display groups - - titlesGroup.fetch(); + treePanel.panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), + BorderFactory.createEmptyBorder(10, 10, 10, 10))); + treePanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + ButtonPanel okPanel = new ButtonPanel(new String[] { "Refresh", "Commit" }); + treePanel.add(okPanel, BorderLayout.SOUTH); + /* + * + * // set up renderer IconCellRenderer iconRenderer = new IconCellRenderer() { + * private Icon icon = UIManager.getIcon("FileChooser.homeFolderIcon"); public + * Icon getIconForContext( JComponent container, Object value, int row, int col, + * boolean isSelected, boolean hasFocus, boolean isExpanded, boolean isLeaf ) { + * return icon; } }; treePanel.tree.setCellRenderer( iconRenderer ); + * + * // enable icon clicking treePanel.tree.setCellEditor( iconRenderer ); + * iconRenderer.addActionListener( this ); + */ + + treePanel.tree.setEditable(true); + + // set up display groups + + titlesGroup.fetch(); // text associations EOAssociation ta; - - ta = new TreeAssociation( treePanel.tree, "People" ); - ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "fullName" ); - ta.bindAspect( EOAssociation.ChildrenAspect, group, "childList" ); - ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" ); + + ta = new TreeAssociation(treePanel.tree, "People"); + ta.bindAspect(EOAssociation.TitlesAspect, titlesGroup, "fullName"); + ta.bindAspect(EOAssociation.ChildrenAspect, group, "childList"); + ta.bindAspect(EOAssociation.IsLeafAspect, titlesGroup, "childCount"); // ta.bindAspect( EOAssociation.IsLeafAspect, null, "false" ); ta.establishConnection(); - treePanel.tree.setEditable( true ); - - ta = new TextAssociation( treePanel.editPanel.firstNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" ); + treePanel.tree.setEditable(true); + + ta = new TextAssociation(treePanel.editPanel.firstNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "firstName"); ta.establishConnection(); - - ta = new TextAssociation( treePanel.editPanel.middleNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" ); + + ta = new TextAssociation(treePanel.editPanel.middleNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "middleName"); ta.establishConnection(); - - ta = new TextAssociation( treePanel.editPanel.lastNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" ); + + ta = new TextAssociation(treePanel.editPanel.lastNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "lastName"); ta.establishConnection(); - ta = new RadioPanelAssociation( treePanel.editPanel.yearRadioPanel ); - ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" ); - + ta = new RadioPanelAssociation(treePanel.editPanel.yearRadioPanel); + ta.bindAspect(EOAssociation.ValueAspect, group, "createDate.year"); + EODisplayGroup yearTitles = new EODisplayGroup(); - yearTitles.setObjectArray( new NSArray( - new Object[] { "1999", "2000", "2001" } ) ); - ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" ); - + yearTitles.setObjectArray(new NSArray(new Object[] { "1999", "2000", "2001" })); + ta.bindAspect(EOAssociation.TitlesAspect, yearTitles, ""); + EODisplayGroup yearObjects = new EODisplayGroup(); - yearObjects.setObjectArray( new NSArray( - new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) ); - ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" ); - + yearObjects.setObjectArray(new NSArray(new Object[] { new Integer(99), new Integer(100), new Integer(101) })); + ta.bindAspect(EOAssociation.ObjectsAspect, yearObjects, ""); + ta.establishConnection(); // detail group - - ta = new MasterDetailAssociation( detailGroup ); - ta.bindAspect( EOAssociation.ParentAspect, group, "childList" ); + + ta = new MasterDetailAssociation(detailGroup); + ta.bindAspect(EOAssociation.ParentAspect, group, "childList"); ta.establishConnection(); - - ta = new ListAssociation( treePanel.editPanel.list ); - ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" ); + + ta = new ListAssociation(treePanel.editPanel.list); + ta.bindAspect(EOAssociation.TitlesAspect, detailGroup, "fullName"); ta.establishConnection(); - + // display group action associations - AbstractButton addButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Add" ); - addButton.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - Object testObject = new TestObject(); - editingContext.insertObject( testObject ); - detailGroup.insertObjectAtIndex( testObject, 0 ); + AbstractButton addButton = (AbstractButton) treePanel.editPanel.addPanel.getButton("Add"); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + Object testObject = new TestObject(); + editingContext.insertObject(testObject); + detailGroup.insertObjectAtIndex(testObject, 0); } - } ); - - AbstractButton removeButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Remove" ); - removeButton.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - if ( detailGroup.selectedObject() != null ) - detailGroup.deleteSelection(); + }); + + AbstractButton removeButton = (AbstractButton) treePanel.editPanel.addPanel.getButton("Remove"); + removeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (detailGroup.selectedObject() != null) + detailGroup.deleteSelection(); } - } ); - - // ok / cancel buttons - - AbstractButton button; - - button = (AbstractButton) - okPanel.getButton( "Commit" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - editingContext.saveChanges(); + }); + + // ok / cancel buttons + + AbstractButton button; + + button = (AbstractButton) okPanel.getButton("Commit"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + editingContext.saveChanges(); } - } ); - - button = (AbstractButton) - okPanel.getButton( "Refresh" ); - button.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - editingContext.revert(); + }); + + button = (AbstractButton) okPanel.getButton("Refresh"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + editingContext.revert(); // editingContext.invalidateAllObjects(); } - } ); - - -/* - AbstractButton refreshButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Refresh" ); - refreshButton.addActionListener( new ActionListener() - { - int count = 0; - public void actionPerformed( ActionEvent evt ) - { - EODisplayGroup displayGroup = (EODisplayGroup) - treePanel.tree.getSelectionPath().getLastPathComponent(); -// displayGroup.insertObjectAtIndex( new TestObject(), 0 ); -// displayGroup.deleteObjectAtIndex( 0 ); - List sortOrderings = new LinkedList(); - if ( count++ % 2 == 0 ) - { - sortOrderings.add( new EOSortOrdering( - "lastName", EOSortOrdering.CompareAscending ) ); - } - else - { - sortOrderings.add( new EOSortOrdering( - "lastName", EOSortOrdering.CompareDescending ) ); - } - displayGroup.setSortOrderings( sortOrderings ); - displayGroup.updateDisplayedObjects(); - } - } ); -*/ -/* - ta = new DisplayGroupActionAssociation( - treePanel.editPanel.addPanel.getButton( "Remove" ) ); - ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); - ta.establishConnection(); - - ta = new DisplayGroupActionAssociation( - treePanel.editPanel.addPanel.getButton( "Refresh" ) ); - ta.bindAspect( EOAssociation.ActionAspect, group, "updateDisplayedObjects" ); - ta.establishConnection(); -*/ + }); + + /* + * AbstractButton refreshButton = (AbstractButton) + * treePanel.editPanel.addPanel.getButton( "Refresh" ); + * refreshButton.addActionListener( new ActionListener() { int count = 0; public + * void actionPerformed( ActionEvent evt ) { EODisplayGroup displayGroup = + * (EODisplayGroup) treePanel.tree.getSelectionPath().getLastPathComponent(); // + * displayGroup.insertObjectAtIndex( new TestObject(), 0 ); // + * displayGroup.deleteObjectAtIndex( 0 ); List sortOrderings = new LinkedList(); + * if ( count++ % 2 == 0 ) { sortOrderings.add( new EOSortOrdering( "lastName", + * EOSortOrdering.CompareAscending ) ); } else { sortOrderings.add( new + * EOSortOrdering( "lastName", EOSortOrdering.CompareDescending ) ); } + * displayGroup.setSortOrderings( sortOrderings ); + * displayGroup.updateDisplayedObjects(); } } ); + */ + /* + * ta = new DisplayGroupActionAssociation( + * treePanel.editPanel.addPanel.getButton( "Remove" ) ); ta.bindAspect( + * EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); + * ta.establishConnection(); + * + * ta = new DisplayGroupActionAssociation( + * treePanel.editPanel.addPanel.getButton( "Refresh" ) ); ta.bindAspect( + * EOAssociation.ActionAspect, group, "updateDisplayedObjects" ); + * ta.establishConnection(); + */ // add mouse listener for list - - treePanel.editPanel.list.addMouseListener( new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - if ( e.getClickCount() == 2 ) - { + + treePanel.editPanel.list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { Object item = detailGroup.selectedObject(); - if ( item != null ) - { - new InspectorController( item ); - } + if (item != null) { + new InspectorController(item); + } } } }); // launch - + JDialog d = new JDialog(); - d.getContentPane().add( treePanel ); - d.setTitle( "Tree Panel" ); + d.getContentPane().add(treePanel); + d.setTitle("Tree Panel"); d.pack(); - WindowUtilities.cascade( d ); + WindowUtilities.cascade(d); d.show(); - // workaround for memory issues on jdk1.2.2 - d.addWindowListener( new WindowAdapter() - { + // workaround for memory issues on jdk1.2.2 + d.addWindowListener(new WindowAdapter() { // exit on close - public void windowClosing(WindowEvent e) - { - ((JDialog)e.getWindow()).getContentPane().removeAll(); + public void windowClosing(WindowEvent e) { + ((JDialog) e.getWindow()).getContentPane().removeAll(); } }); } - - public void actionPerformed( ActionEvent evt ) - { - Object item = group.selectedObject(); - if ( item != null ) - { + + public void actionPerformed(ActionEvent evt) { + Object item = group.selectedObject(); + if (item != null) { // new InspectorController( item ); - new ObjectInspector( item ); - } - } - + new ObjectInspector(item); + } + } + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java index 8ec9554..1695103 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreeInspectorController.java @@ -21,164 +21,134 @@ import net.wotonomy.ui.swing.TreeAssociation; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* A simple editor panel with a few textfields. -*/ -public class TreeInspectorController -{ - public TreeInspectorController( EODisplayGroup titlesGroup, EODisplayGroup childrenGroup ) - { - final EODisplayGroup group = childrenGroup; + * A simple editor panel with a few textfields. + */ +public class TreeInspectorController { + public TreeInspectorController(EODisplayGroup titlesGroup, EODisplayGroup childrenGroup) { + final EODisplayGroup group = childrenGroup; // final EODisplayGroup group = titlesGroup; final TreePanel treePanel = new TreePanel(); - + // text associations - + EOAssociation ta; - - ta = new TreeAssociation( treePanel.tree, "People" ); - ta.bindAspect( EOAssociation.TitlesAspect, titlesGroup, "lastName" ); - ta.bindAspect( EOAssociation.ChildrenAspect, group, "childList" ); - ta.bindAspect( EOAssociation.IsLeafAspect, titlesGroup, "childCount" ); + + ta = new TreeAssociation(treePanel.tree, "People"); + ta.bindAspect(EOAssociation.TitlesAspect, titlesGroup, "lastName"); + ta.bindAspect(EOAssociation.ChildrenAspect, group, "childList"); + ta.bindAspect(EOAssociation.IsLeafAspect, titlesGroup, "childCount"); // ta.bindAspect( EOAssociation.IsLeafAspect, null, "false" ); ta.establishConnection(); - treePanel.tree.setEditable( true ); - - ta = new TextAssociation( treePanel.editPanel.firstNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "firstName" ); + treePanel.tree.setEditable(true); + + ta = new TextAssociation(treePanel.editPanel.firstNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "firstName"); ta.establishConnection(); - - ta = new TextAssociation( treePanel.editPanel.middleNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "middleName" ); + + ta = new TextAssociation(treePanel.editPanel.middleNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "middleName"); ta.establishConnection(); - - ta = new TextAssociation( treePanel.editPanel.lastNameField ); - ta.bindAspect( EOAssociation.ValueAspect, group, "lastName" ); + + ta = new TextAssociation(treePanel.editPanel.lastNameField); + ta.bindAspect(EOAssociation.ValueAspect, group, "lastName"); ta.establishConnection(); - ta = new RadioPanelAssociation( treePanel.editPanel.yearRadioPanel ); - ta.bindAspect( EOAssociation.ValueAspect, group, "createDate.year" ); - + ta = new RadioPanelAssociation(treePanel.editPanel.yearRadioPanel); + ta.bindAspect(EOAssociation.ValueAspect, group, "createDate.year"); + EODisplayGroup yearTitles = new EODisplayGroup(); - yearTitles.setObjectArray( new NSArray( - new Object[] { "1999", "2000", "2001" } ) ); - ta.bindAspect( EOAssociation.TitlesAspect, yearTitles, "" ); - + yearTitles.setObjectArray(new NSArray(new Object[] { "1999", "2000", "2001" })); + ta.bindAspect(EOAssociation.TitlesAspect, yearTitles, ""); + EODisplayGroup yearObjects = new EODisplayGroup(); - yearObjects.setObjectArray( new NSArray( - new Object[] { new Integer( 99 ), new Integer( 100 ), new Integer( 101 ) } ) ); - ta.bindAspect( EOAssociation.ObjectsAspect, yearObjects, "" ); - + yearObjects.setObjectArray(new NSArray(new Object[] { new Integer(99), new Integer(100), new Integer(101) })); + ta.bindAspect(EOAssociation.ObjectsAspect, yearObjects, ""); + ta.establishConnection(); // detail group - + final EODisplayGroup detailGroup = new EODisplayGroup(); - - ta = new MasterDetailAssociation( detailGroup ); - ta.bindAspect( EOAssociation.ParentAspect, group, "childList" ); + + ta = new MasterDetailAssociation(detailGroup); + ta.bindAspect(EOAssociation.ParentAspect, group, "childList"); ta.establishConnection(); - - ta = new ListAssociation( treePanel.editPanel.list ); - ta.bindAspect( EOAssociation.TitlesAspect, detailGroup, "fullName" ); + + ta = new ListAssociation(treePanel.editPanel.list); + ta.bindAspect(EOAssociation.TitlesAspect, detailGroup, "fullName"); ta.establishConnection(); - + // display group action associations - AbstractButton addButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Add" ); - addButton.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - detailGroup.insertObjectAtIndex( new TestObject(), 0 ); - } - } ); - - AbstractButton removeButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Remove" ); - removeButton.addActionListener( new ActionListener() - { - public void actionPerformed( ActionEvent evt ) - { - if ( detailGroup.selectedObject() != null ) - detailGroup.deleteSelection(); + AbstractButton addButton = (AbstractButton) treePanel.editPanel.addPanel.getButton("Add"); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + detailGroup.insertObjectAtIndex(new TestObject(), 0); } - } ); -/* - AbstractButton refreshButton = (AbstractButton) - treePanel.editPanel.addPanel.getButton( "Refresh" ); - refreshButton.addActionListener( new ActionListener() - { - int count = 0; - public void actionPerformed( ActionEvent evt ) - { - EODisplayGroup displayGroup = (EODisplayGroup) - treePanel.tree.getSelectionPath().getLastPathComponent(); -// displayGroup.insertObjectAtIndex( new TestObject(), 0 ); -// displayGroup.deleteObjectAtIndex( 0 ); - List sortOrderings = new LinkedList(); - if ( count++ % 2 == 0 ) - { - sortOrderings.add( new EOSortOrdering( - "lastName", EOSortOrdering.CompareAscending ) ); - } - else - { - sortOrderings.add( new EOSortOrdering( - "lastName", EOSortOrdering.CompareDescending ) ); - } - displayGroup.setSortOrderings( sortOrderings ); - displayGroup.updateDisplayedObjects(); + }); + + AbstractButton removeButton = (AbstractButton) treePanel.editPanel.addPanel.getButton("Remove"); + removeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (detailGroup.selectedObject() != null) + detailGroup.deleteSelection(); } - } ); -*/ -/* - ta = new DisplayGroupActionAssociation( - treePanel.editPanel.addPanel.getButton( "Remove" ) ); - ta.bindAspect( EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); - ta.establishConnection(); - - ta = new DisplayGroupActionAssociation( - treePanel.editPanel.addPanel.getButton( "Refresh" ) ); - ta.bindAspect( EOAssociation.ActionAspect, group, "updateDisplayedObjects" ); - ta.establishConnection(); -*/ + }); + /* + * AbstractButton refreshButton = (AbstractButton) + * treePanel.editPanel.addPanel.getButton( "Refresh" ); + * refreshButton.addActionListener( new ActionListener() { int count = 0; public + * void actionPerformed( ActionEvent evt ) { EODisplayGroup displayGroup = + * (EODisplayGroup) treePanel.tree.getSelectionPath().getLastPathComponent(); // + * displayGroup.insertObjectAtIndex( new TestObject(), 0 ); // + * displayGroup.deleteObjectAtIndex( 0 ); List sortOrderings = new LinkedList(); + * if ( count++ % 2 == 0 ) { sortOrderings.add( new EOSortOrdering( "lastName", + * EOSortOrdering.CompareAscending ) ); } else { sortOrderings.add( new + * EOSortOrdering( "lastName", EOSortOrdering.CompareDescending ) ); } + * displayGroup.setSortOrderings( sortOrderings ); + * displayGroup.updateDisplayedObjects(); } } ); + */ + /* + * ta = new DisplayGroupActionAssociation( + * treePanel.editPanel.addPanel.getButton( "Remove" ) ); ta.bindAspect( + * EOAssociation.ActionAspect, detailGroup, "deleteSelection" ); + * ta.establishConnection(); + * + * ta = new DisplayGroupActionAssociation( + * treePanel.editPanel.addPanel.getButton( "Refresh" ) ); ta.bindAspect( + * EOAssociation.ActionAspect, group, "updateDisplayedObjects" ); + * ta.establishConnection(); + */ // add mouse listener for list - - treePanel.editPanel.list.addMouseListener( new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - if ( e.getClickCount() == 2 ) - { + + treePanel.editPanel.list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { Object item = detailGroup.selectedObject(); - if ( item != null ) - { - new InspectorController( item ); - } + if (item != null) { + new InspectorController(item); + } } } }); // launch - + JDialog d = new JDialog(); - d.getContentPane().add( treePanel ); - d.setTitle( "Tree Panel" ); + d.getContentPane().add(treePanel); + d.setTitle("Tree Panel"); d.pack(); - WindowUtilities.cascade( d ); + WindowUtilities.cascade(d); d.show(); - // workaround for memory issues on jdk1.2.2 - d.addWindowListener( new WindowAdapter() - { + // workaround for memory issues on jdk1.2.2 + d.addWindowListener(new WindowAdapter() { // exit on close - public void windowClosing(WindowEvent e) - { - ((JDialog)e.getWindow()).getContentPane().removeAll(); + public void windowClosing(WindowEvent e) { + ((JDialog) e.getWindow()).getContentPane().removeAll(); } }); } - + } diff --git a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java index 48ebd81..48c2571 100644 --- a/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java +++ b/projects/net.wotonomy.test/src/main/java/net/wotonomy/test/TreePanel.java @@ -9,31 +9,29 @@ import javax.swing.JTree; import javax.swing.border.EmptyBorder; /** -* A simple editor panel with a few textfields. -*/ -public class TreePanel extends JPanel -{ + * A simple editor panel with a few textfields. + */ +public class TreePanel extends JPanel { public JTree tree; public EditPanel editPanel; - public JPanel panel; - - public TreePanel() - { - panel = new JPanel(); - panel.setLayout( new BorderLayout() ); - panel.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - + public JPanel panel; + + public TreePanel() { + panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + tree = new JTree(); - tree.setRootVisible( false ); - tree.setShowsRootHandles( true ); - JScrollPane scrollPane = new JScrollPane( tree ); - scrollPane.setPreferredSize( new Dimension( 150, 200 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + JScrollPane scrollPane = new JScrollPane(tree); + scrollPane.setPreferredSize(new Dimension(150, 200)); + panel.add(scrollPane, BorderLayout.CENTER); editPanel = new EditPanel(); - panel.add( editPanel, BorderLayout.EAST ); - - this.setLayout( new BorderLayout() ); - this.add( panel, BorderLayout.CENTER ); + panel.add(editPanel, BorderLayout.EAST); + + this.setLayout(new BorderLayout()); + this.add(panel, BorderLayout.CENTER); } - + } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java index cc3af69..ae3fa50 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ActionAssociation.java @@ -31,305 +31,228 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ActionAssociation binds any ActionEvent broadcaster -* (typically Buttons and the like) to a display group. -* Actions are invoked on all selected objects in the -* display group bound to the action aspect. -* Bindings are: -*
    -*
  • action: a method to be invoked on selected objects. -* If the argument aspect is bound, the method must take -* one argument. Otherwise, the method must take no arguments.
  • -*
  • argument: the attribute of the selected object(s) (possibly -* from a different display group) that will be used as an argument -* to the action method
  • -*
  • enabled: a boolean property that determines whether -* the controlled component is enabled
  • -*
  • visible: a boolean property that determines whether -* the controlled component is visible
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ActionAssociation extends EOAssociation - implements ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "target" - } ); - - static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); + * ActionAssociation binds any ActionEvent broadcaster (typically Buttons and + * the like) to a display group. Actions are invoked on all selected objects in + * the display group bound to the action aspect. Bindings are: + *
    + *
  • action: a method to be invoked on selected objects. If the argument + * aspect is bound, the method must take one argument. Otherwise, the method + * must take no arguments.
  • + *
  • argument: the attribute of the selected object(s) (possibly from a + * different display group) that will be used as an argument to the action + * method
  • + *
  • enabled: a boolean property that determines whether the controlled + * component is enabled
  • + *
  • visible: a boolean property that determines whether the controlled + * component is visible
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ActionAssociation extends EOAssociation implements ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "target" }); - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public ActionAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return - ( addActionListener.implementedByObject( anObject ) ) - && ( removeActionListener.implementedByObject( anObject ) ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. - */ - public static String primaryAspect () - { - return ActionAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - try - { - addActionListener.invoke( object(), this ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "EOActionAssociation: " + - "could not add action listener to object:" + object() ); - } - super.establishConnection(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - try - { - removeActionListener.invoke( object(), this ); + static NSSelector addActionListener = new NSSelector("addActionListener", new Class[] { ActionListener.class }); + static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public ActionAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (addActionListener.implementedByObject(anObject)) + && (removeActionListener.implementedByObject(anObject)); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. + */ + public static String primaryAspect() { + return ActionAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + try { + addActionListener.invoke(object(), this); + } catch (Exception exc) { + throw new WotonomyException( + "EOActionAssociation: " + "could not add action listener to object:" + object()); } - catch ( Exception exc ) - { - throw new WotonomyException( "EOActionAssociation: " + - "could not add action listener to object:" + object() ); + super.establishConnection(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + try { + removeActionListener.invoke(object(), this); + } catch (Exception exc) { + throw new WotonomyException( + "EOActionAssociation: " + "could not add action listener to object:" + object()); } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - * This implementation does nothing. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. This implementation does nothing. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; - - if ( component instanceof Component ) - { + + if (component instanceof Component) { // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != - ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( - converted.booleanValue() ); - } + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } - + // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( VisibleAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - if ( converted.booleanValue() != - ((Component)component).isVisible() ) - { - ((Component)component).setVisible( - converted.booleanValue() ); + displayGroup = displayGroupForAspect(VisibleAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(VisibleAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); } - } + } } } - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { EODisplayGroup actionDisplayGroup = null; String actionKey = null; - + // action aspect - actionDisplayGroup = displayGroupForAspect( ActionAspect ); - if ( actionDisplayGroup != null ) - { - actionKey = displayGroupKeyForAspect( ActionAspect ); + actionDisplayGroup = displayGroupForAspect(ActionAspect); + if (actionDisplayGroup != null) { + actionKey = displayGroupKeyForAspect(ActionAspect); - //TODO: argument aspect not implemented + // TODO: argument aspect not implemented - try - { - - NSSelector selector = new NSSelector( actionKey ); - Enumeration e = - actionDisplayGroup.selectedObjects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - selector.invoke( e.nextElement() ); + try { + + NSSelector selector = new NSSelector(actionKey); + Enumeration e = actionDisplayGroup.selectedObjects().objectEnumerator(); + while (e.hasMoreElements()) { + selector.invoke(e.nextElement()); } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "ActionAssociation: error invoking action: " + actionKey, exc ); + } catch (Exception exc) { + throw new WotonomyException("ActionAssociation: error invoking action: " + actionKey, exc); } } - } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.4 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.3 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:48:28 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:28 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java index 2dc7fec..4f90c79 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/AdjustableAssociation.java @@ -29,299 +29,234 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* AdjustableAssociation binds any Adjustable component to -* a display group. Components implementing the adjustable -* interface include: ScrollBar and JScrollBar. -* Bindings are: -*
    -*
  • value: a property convertable to/from a string
  • -*
  • enabled: a boolean property that determines whether -* the user can select the text in the field
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class AdjustableAssociation extends EOAssociation - implements AdjustmentListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "value" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public AdjustableAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof Adjustable ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - component().addAdjustmentListener( this ); - super.establishConnection(); - + * AdjustableAssociation binds any Adjustable component to a display group. + * Components implementing the adjustable interface include: ScrollBar and + * JScrollBar. Bindings are: + *
    + *
  • value: a property convertable to/from a string
  • + *
  • enabled: a boolean property that determines whether the user can select + * the text in the field
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class AdjustableAssociation extends EOAssociation implements AdjustmentListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "value" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public AdjustableAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof Adjustable); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + component().addAdjustmentListener(this); + super.establishConnection(); + // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - component().removeAdjustmentListener( this ); - super.breakConnection(); - } + } - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + component().removeAdjustmentListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Adjustable component = component(); EODisplayGroup displayGroup; String key; Object value; - + // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - value = displayGroup.selectedObjectValueForKey( key ); - + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + value = displayGroup.selectedObjectValueForKey(key); + // convert value to int - value = ValueConverter.convertObjectToClass( - value, Integer.class ); + value = ValueConverter.convertObjectToClass(value, Integer.class); int intValue; - if ( value == null ) - { + if (value == null) { intValue = 0; + } else { + intValue = ((Integer) value).intValue(); } - else - { - intValue = ((Integer)value).intValue(); - } - - if ( component.getValue() != intValue ) - { - component.setValue( intValue ); + + if (component.getValue() != intValue) { + component.setValue(intValue); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if (((displayGroup != null) || (key != null)) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Integer( component().getValue() ); - return displayGroup.setSelectedObjectValue( value, key ); + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Integer(component().getValue()); + return displayGroup.setSelectedObjectValue(value, key); } return false; } - // interface AdjustmentListener - + // interface AdjustmentListener + /** - * Updates object on action performed. - */ - public void adjustmentValueChanged(AdjustmentEvent e) - { + * Updates object on action performed. + */ + public void adjustmentValueChanged(AdjustmentEvent e) { writeValueToDisplayGroup(); } - - private Adjustable component() - { - return (Adjustable) object(); + + private Adjustable component() { + return (Adjustable) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.3 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.2 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:48:35 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:35 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java index 38cf38b..5a1c85c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ButtonAssociation.java @@ -33,412 +33,312 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ButtonAssociation binds any component that uses a ButtonModel -* (all Swing button classes) to a display group. This association -* should be used to handle individual JRadioButtons and JCheckBoxes. -* Bindings are: -*
    -*
  • value: a boolean property that determines the -* selected state of the button model. This will set -* the value for radio buttons and check boxes.
  • -*
  • enabled: a boolean property that determines the -* enabled state of the button model.
  • -*
  • visible: a boolean property that determines the -* visible state of the button model.
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ButtonAssociation extends EOAssociation - implements ChangeListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "model.selected" - } ); - - static NSSelector getModel = - new NSSelector( "getModel", new Class[] {} ); - - protected ButtonModel buttonModel; - protected boolean lastKnownValue; - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - * This implementation expects a ButtonModel or a class - * that has a "getModel" method that returns a ButtonModel. - */ - public ButtonAssociation ( Object anObject ) - { - super( anObject ); - - if ( anObject instanceof ButtonModel ) - { - buttonModel = (ButtonModel) anObject; - } - else - { - try - { - buttonModel = (ButtonModel) getModel.invoke( anObject ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "EOButtonAssociation: " + - "could not retrieve a button model from object:" + anObject ); - } - } - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - * This implementation expects a ButtonModel or a class - * that has a "getModel" method that returns a ButtonModel. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return - ( anObject instanceof ButtonModel ) - || ( getModel.implementedByObject( anObject ) ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - buttonModel.addChangeListener( this ); - super.establishConnection(); - subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - buttonModel.removeChangeListener( this ); - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - * This implementation does nothing. - */ - public void subjectChanged () - { - Object component = object(); - EODisplayGroup displayGroup; - String key; - - // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - - Object value; - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } - else // displayGroup has only one object - { - value = - displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup - - buttonModel.setArmed( false ); - buttonModel.setPressed( false ); - - if ( value != null ) - { - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - lastKnownValue = converted.booleanValue(); - if ( converted.booleanValue() != - buttonModel.isSelected() ) - { - buttonModel.removeChangeListener( this ); - buttonModel.setSelected( - converted.booleanValue() ); - buttonModel.addChangeListener( this ); - } - } // end checking converted == null - } - else - { - buttonModel.setArmed( true ); - buttonModel.setPressed( true ); - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != - buttonModel.isEnabled() ) - { - buttonModel.removeChangeListener( this ); - buttonModel.setEnabled( - converted.booleanValue() ); - buttonModel.addChangeListener( this ); - } - } - - // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( VisibleAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - if ( converted != null ) - { - if ( converted.booleanValue() != - ((Component)component).isVisible() ) - { - ((Component)component).setVisible( - converted.booleanValue() ); - } - } - } - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - boolean returnValue = true; - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Boolean( buttonModel.isSelected() ); - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } - return false; - } - // interface ChangeListener - - public void stateChanged(ChangeEvent e) - { - if ( buttonModel.isSelected() != lastKnownValue ) - { - lastKnownValue = buttonModel.isSelected(); - writeValueToDisplayGroup(); - } - } + * ButtonAssociation binds any component that uses a ButtonModel (all Swing + * button classes) to a display group. This association should be used to handle + * individual JRadioButtons and JCheckBoxes. Bindings are: + *
    + *
  • value: a boolean property that determines the selected state of the + * button model. This will set the value for radio buttons and check boxes.
  • + *
  • enabled: a boolean property that determines the enabled state of the + * button model.
  • + *
  • visible: a boolean property that determines the visible state of the + * button model.
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ButtonAssociation extends EOAssociation implements ChangeListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "model.selected" }); + + static NSSelector getModel = new NSSelector("getModel", new Class[] {}); + + protected ButtonModel buttonModel; + protected boolean lastKnownValue; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. This implementation expects a ButtonModel or a + * class that has a "getModel" method that returns a ButtonModel. + */ + public ButtonAssociation(Object anObject) { + super(anObject); + + if (anObject instanceof ButtonModel) { + buttonModel = (ButtonModel) anObject; + } else { + try { + buttonModel = (ButtonModel) getModel.invoke(anObject); + } catch (Exception exc) { + throw new WotonomyException( + "EOButtonAssociation: " + "could not retrieve a button model from object:" + anObject); + } + } + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. This + * implementation expects a ButtonModel or a class that has a "getModel" method + * that returns a ButtonModel. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof ButtonModel) || (getModel.implementedByObject(anObject)); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + buttonModel.addChangeListener(this); + super.establishConnection(); + subjectChanged(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + buttonModel.removeChangeListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. This implementation does nothing. + */ + public void subjectChanged() { + Object component = object(); + EODisplayGroup displayGroup; + String key; + + // value aspect + displayGroup = displayGroupForAspect(ValueAspect); + + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + + Object value; + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else // displayGroup has only one object + { + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup + + buttonModel.setArmed(false); + buttonModel.setPressed(false); + + if (value != null) { + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + lastKnownValue = converted.booleanValue(); + if (converted.booleanValue() != buttonModel.isSelected()) { + buttonModel.removeChangeListener(this); + buttonModel.setSelected(converted.booleanValue()); + buttonModel.addChangeListener(this); + } + } // end checking converted == null + } else { + buttonModel.setArmed(true); + buttonModel.setPressed(true); + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != buttonModel.isEnabled()) { + buttonModel.removeChangeListener(this); + buttonModel.setEnabled(converted.booleanValue()); + buttonModel.addChangeListener(this); + } + } + + // visible aspect + displayGroup = displayGroupForAspect(VisibleAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(VisibleAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); + } + } + } + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + boolean returnValue = true; + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Boolean(buttonModel.isSelected()); + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } + return false; + } + // interface ChangeListener + + public void stateChanged(ChangeEvent e) { + if (buttonModel.isSelected() != lastKnownValue) { + lastKnownValue = buttonModel.isSelected(); + writeValueToDisplayGroup(); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.8 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.7 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.6 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.5 2001/06/29 22:28:19 mpowers - * Tabs to spaces. + * Revision 1.5 2001/06/29 22:28:19 mpowers Tabs to spaces. * - * Revision 1.4 2001/06/29 22:17:31 mpowers - * Now updating the component on establishConnection. + * Revision 1.4 2001/06/29 22:17:31 mpowers Now updating the component on + * establishConnection. * - * Revision 1.3 2001/02/27 02:10:38 mpowers - * No longer updating values to the display group if the value - * has not changed. + * Revision 1.3 2001/02/27 02:10:38 mpowers No longer updating values to the + * display group if the value has not changed. * - * Revision 1.2 2001/02/21 20:33:01 mpowers - * Fixed bug with change listener. + * Revision 1.2 2001/02/21 20:33:01 mpowers Fixed bug with change listener. * - * Revision 1.1.1.1 2000/12/21 15:48:38 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:38 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java index d0a087e..e6cd0aa 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ComboBoxAssociation.java @@ -37,664 +37,529 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* ComboBoxAssociation binds JComboBoxes to -* display groups. Bindings are: -*
    -* -*
  • value: optional - a property of the selected object in the -* display group that will be bind to the item the user -* selects or the text that the user enters in the field. -* If the value aspect is not bound, then the combo box works -* as an "overview" assocation and changing the selected object -* in the combobox will modify the selection of the display group -* bound to the objects or the titles display groups (in that order).
  • -* -*
  • titles: optional - a property of the objects in the bound -* display group that will appear in the list. If the -* objects aspect is not bound, this property is also -* used to populate the value binding. If the titles -* aspect itself is not bound, the items already in the -* combobox will be used to update the value in the -* selected object in the bound display group.
  • -* -*
  • objects: optional - if specified, when the user -* selects a title in the list, the property of the -* object at the corresponding index of the bound display -* group will be used to populate the value binding. -* If the objects aspect is used with an editable combo -* box, any value entered that does not match one of the -* titles in the list will produce a null value.
  • -* -*
  • enabled: optional - a boolean property of the -* selected object in the display group that determines whether -* the user can edit the field.
  • -* -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ComboBoxAssociation extends EOAssociation - implements FocusListener, ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ValueAspect, - ObjectsAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text" - } ); - - private boolean wasNull; - private static final String EMPTY_STRING = ""; - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public ComboBoxAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JComboBox ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - super.establishConnection(); - - // prepopulate titles - EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); - } - addAsListener(); - subjectChanged(); - } - - protected void addAsListener() - { - component().addActionListener( this ); - component().addFocusListener( this ); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void removeAsListener() - { - component().removeActionListener( this ); - component().removeFocusListener( this ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - removeAsListener(); - - JComboBox component = component(); - EODisplayGroup displayGroup; - String key; - - // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - ComboBoxModel model = component().getModel(); - // if first time, or if backing group has changed + * ComboBoxAssociation binds JComboBoxes to display groups. Bindings are: + *
    + * + *
  • value: optional - a property of the selected object in the display group + * that will be bind to the item the user selects or the text that the user + * enters in the field. If the value aspect is not bound, then the combo box + * works as an "overview" assocation and changing the selected object in the + * combobox will modify the selection of the display group bound to the objects + * or the titles display groups (in that order).
  • + * + *
  • titles: optional - a property of the objects in the bound display group + * that will appear in the list. If the objects aspect is not bound, this + * property is also used to populate the value binding. If the titles aspect + * itself is not bound, the items already in the combobox will be used to update + * the value in the selected object in the bound display group.
  • + * + *
  • objects: optional - if specified, when the user selects a title in the + * list, the property of the object at the corresponding index of the bound + * display group will be used to populate the value binding. If the objects + * aspect is used with an editable combo box, any value entered that does not + * match one of the titles in the list will produce a null value.
  • + * + *
  • enabled: optional - a boolean property of the selected object in the + * display group that determines whether the user can edit the field.
  • + * + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ComboBoxAssociation extends EOAssociation implements FocusListener, ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { TitlesAspect, ValueAspect, ObjectsAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text" }); + + private boolean wasNull; + private static final String EMPTY_STRING = ""; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public ComboBoxAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JComboBox); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + super.establishConnection(); + + // prepopulate titles + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); + } + addAsListener(); + subjectChanged(); + } + + protected void addAsListener() { + component().addActionListener(this); + component().addFocusListener(this); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void removeAsListener() { + component().removeActionListener(this); + component().removeFocusListener(this); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + removeAsListener(); + + JComboBox component = component(); + EODisplayGroup displayGroup; + String key; + + // titles aspect + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + ComboBoxModel model = component().getModel(); + // if first time, or if backing group has changed // if ( ( ! ( model instanceof ComboBoxAssociationModel ) ) // || ( displayGroup.contentsChanged() ) ) // { - key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); // } - } - - // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - //Object value = displayGroup.selectedObjectValueForKey( key ); - Object value; - - - if ( displayGroup.selectedObjects().size() > 1 ) - { - - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - - // objects aspect - EODisplayGroup objectsDisplayGroup = - displayGroupForAspect( ObjectsAspect ); - if ( ( objectsDisplayGroup != null ) && ( value != null ) ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - Object match; - int index = NSArray.NotFound; - int count = objectsDisplayGroup.displayedObjects().count(); - for ( int i = 0; i < count; i++ ) - { - match = objectsDisplayGroup.valueForObjectAtIndex( i, objectKey ); - if ( value.equals( match ) ) - { - index = i; - } - } - if ( index == NSArray.NotFound ) - { - if ( component.getSelectedItem() != null ) - { - component.setSelectedItem( null ); - } - } - else - { - if ( component.getSelectedIndex() != index ) - { - component.setSelectedIndex( index ); - } - } - } - else - { - component.setSelectedItem( value ); - } - } - else // values aspect not bound - { - // use objects group if specified - EODisplayGroup sourceGroup = - displayGroupForAspect( ObjectsAspect ); - if ( sourceGroup == null ) - { - // fall back on titles group - sourceGroup = displayGroupForAspect( TitlesAspect ); - } - - if ( sourceGroup != null ) - { - List selection = sourceGroup.selectionIndexes(); - if ( ( selection != null ) && ( selection.size() > 0 ) ) - { - component.setSelectedIndex( ((Integer)selection.get(0)).intValue() ); - } - else - { - // the combo box model decides what to do with this value - component.setSelectedItem( null ); - } - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } - } - - addAsListener(); - } - - /** - * Called to repopulate the title list from the - * specified display group. - */ - protected void populateTitles( - EODisplayGroup displayGroup, String key ) - { - component().setModel( - new ComboBoxAssociationModel( displayGroup, key ) ); - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - return writeValueToDisplayGroup(); - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - JComboBox component = component(); - EODisplayGroup displayGroup; - String key; - - // selected title aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - Object value = null; - - // selected object aspect, if any - EODisplayGroup objectsGroup = - displayGroupForAspect( ObjectsAspect ); - if ( objectsGroup != null ) - { - try - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - int index = component.getSelectedIndex(); - if ( index != -1 ) - { - value = objectsGroup - .valueForObjectAtIndex( index, objectKey ); - } - else // selected index is -1 - { - // the combo box is probably editable, - // so there is no corresponding object. - value = null; - } - } - catch ( NullPointerException npe ) - { - // catches NPE on line 436 of JComboBox.java: - // this is a common developer error - throw new WotonomyException( "ComboBoxAssociation: " + - "The object in the VALUE property may not have been found in the " + - "objects in the TITLES group.", npe ); - } - } - else // just use the selected item - { - value = component.getSelectedItem(); - } - - boolean returnValue = true; - if ( displayGroup.selectedObjects().size() == 1 ) - { // displayGroup has only one object - // only set value if changed - Object existingValue = displayGroup.selectedObjectValueForKey( key ); - if ( value == existingValue ) return true; - if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true; - - // value has changed: update the value. - return displayGroup.setSelectedObjectValue( value, key ); - } - else if ( displayGroup.selectedObjects().size() > 1 ) - { - // displayGroup has more than one object - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } // end checking size of displayGroup - - } - else // values aspect not bound - { - // use objects group if specified - EODisplayGroup sourceGroup = - displayGroupForAspect( ObjectsAspect ); - if ( sourceGroup == null ) - { - // fall back on titles group - sourceGroup = displayGroupForAspect( TitlesAspect ); - } - - if ( sourceGroup != null ) - { - int index = component.getSelectedIndex(); - if ( index != -1 ) - { - sourceGroup.setSelectionIndexes( new NSArray( new Integer( index ) ) ); - } - else - { - sourceGroup.setSelectedObject( null ); - } - return true; - } - } - - return false; - } - - // interface ActionListener - - /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - writeValueToDisplayGroup(); - } - - // interface FocusListener - - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); - } - } - } - - /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( component().isEditable() ) - { - if ( endEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } - } - - // convenience - - private JComboBox component() - { - return (JComboBox) object(); - } - - /** - * Used as the data model for the controlled combo box. - */ - private class ComboBoxAssociationModel extends AbstractListModel - implements ComboBoxModel - { - EODisplayGroup displayGroup; - String key; - Object selectedItem; - - ComboBoxAssociationModel( - EODisplayGroup aDisplayGroup, String aKey ) - { - displayGroup = aDisplayGroup; - key = aKey; - selectedItem = null; - } - - public Object getElementAt(int index) - { - return displayGroup.valueForObjectAtIndex( index, key ); - } - - public int getSize() - { - return displayGroup.displayedObjects().count(); - } - - public void setSelectedItem(Object anItem) - { //System.out.println( "setSelectedItem: " + anItem ); - selectedItem = anItem; - - // must do this to notify an editable combo, - // otherwise the wrong value appears. - fireContentsChanged( this, -1, -1 ); - } - - public Object getSelectedItem() - { //System.out.println( "getSelectedItem: " + selectedItem ); - return selectedItem; - } - } + } + + // value aspect + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + // Object value = displayGroup.selectedObjectValueForKey( key ); + Object value; + + if (displayGroup.selectedObjects().size() > 1) { + + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // objects aspect + EODisplayGroup objectsDisplayGroup = displayGroupForAspect(ObjectsAspect); + if ((objectsDisplayGroup != null) && (value != null)) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + Object match; + int index = NSArray.NotFound; + int count = objectsDisplayGroup.displayedObjects().count(); + for (int i = 0; i < count; i++) { + match = objectsDisplayGroup.valueForObjectAtIndex(i, objectKey); + if (value.equals(match)) { + index = i; + } + } + if (index == NSArray.NotFound) { + if (component.getSelectedItem() != null) { + component.setSelectedItem(null); + } + } else { + if (component.getSelectedIndex() != index) { + component.setSelectedIndex(index); + } + } + } else { + component.setSelectedItem(value); + } + } else // values aspect not bound + { + // use objects group if specified + EODisplayGroup sourceGroup = displayGroupForAspect(ObjectsAspect); + if (sourceGroup == null) { + // fall back on titles group + sourceGroup = displayGroupForAspect(TitlesAspect); + } + + if (sourceGroup != null) { + List selection = sourceGroup.selectionIndexes(); + if ((selection != null) && (selection.size() > 0)) { + component.setSelectedIndex(((Integer) selection.get(0)).intValue()); + } else { + // the combo box model decides what to do with this value + component.setSelectedItem(null); + } + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); + } + } + + addAsListener(); + } + + /** + * Called to repopulate the title list from the specified display group. + */ + protected void populateTitles(EODisplayGroup displayGroup, String key) { + component().setModel(new ComboBoxAssociationModel(displayGroup, key)); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + return writeValueToDisplayGroup(); + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + JComboBox component = component(); + EODisplayGroup displayGroup; + String key; + + // selected title aspect + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + Object value = null; + + // selected object aspect, if any + EODisplayGroup objectsGroup = displayGroupForAspect(ObjectsAspect); + if (objectsGroup != null) { + try { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + int index = component.getSelectedIndex(); + if (index != -1) { + value = objectsGroup.valueForObjectAtIndex(index, objectKey); + } else // selected index is -1 + { + // the combo box is probably editable, + // so there is no corresponding object. + value = null; + } + } catch (NullPointerException npe) { + // catches NPE on line 436 of JComboBox.java: + // this is a common developer error + throw new WotonomyException( + "ComboBoxAssociation: " + "The object in the VALUE property may not have been found in the " + + "objects in the TITLES group.", + npe); + } + } else // just use the selected item + { + value = component.getSelectedItem(); + } + + boolean returnValue = true; + if (displayGroup.selectedObjects().size() == 1) { // displayGroup has only one object + // only set value if changed + Object existingValue = displayGroup.selectedObjectValueForKey(key); + if (value == existingValue) + return true; + if ((existingValue != null) && (existingValue.equals(value))) + return true; + + // value has changed: update the value. + return displayGroup.setSelectedObjectValue(value, key); + } else if (displayGroup.selectedObjects().size() > 1) { + // displayGroup has more than one object + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } // end checking size of displayGroup + + } else // values aspect not bound + { + // use objects group if specified + EODisplayGroup sourceGroup = displayGroupForAspect(ObjectsAspect); + if (sourceGroup == null) { + // fall back on titles group + sourceGroup = displayGroupForAspect(TitlesAspect); + } + + if (sourceGroup != null) { + int index = component.getSelectedIndex(); + if (index != -1) { + sourceGroup.setSelectionIndexes(new NSArray(new Integer(index))); + } else { + sourceGroup.setSelectedObject(null); + } + return true; + } + } + + return false; + } + + // interface ActionListener + + /** + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + writeValueToDisplayGroup(); + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); + } + } + } + + /** + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (component().isEditable()) { + if (endEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } + } + + // convenience + + private JComboBox component() { + return (JComboBox) object(); + } + + /** + * Used as the data model for the controlled combo box. + */ + private class ComboBoxAssociationModel extends AbstractListModel implements ComboBoxModel { + EODisplayGroup displayGroup; + String key; + Object selectedItem; + + ComboBoxAssociationModel(EODisplayGroup aDisplayGroup, String aKey) { + displayGroup = aDisplayGroup; + key = aKey; + selectedItem = null; + } + + public Object getElementAt(int index) { + return displayGroup.valueForObjectAtIndex(index, key); + } + + public int getSize() { + return displayGroup.displayedObjects().count(); + } + + public void setSelectedItem(Object anItem) { // System.out.println( "setSelectedItem: " + anItem ); + selectedItem = anItem; + + // must do this to notify an editable combo, + // otherwise the wrong value appears. + fireContentsChanged(this, -1, -1); + } + + public Object getSelectedItem() { // System.out.println( "getSelectedItem: " + selectedItem ); + return selectedItem; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.13 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.13 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.12 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.12 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.11 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.11 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.10 2001/07/23 20:17:56 mpowers - * Now works as an overview association if the values aspect is not bound. + * Revision 1.10 2001/07/23 20:17:56 mpowers Now works as an overview + * association if the values aspect is not bound. * - * Revision 1.9 2001/06/30 14:57:29 mpowers - * Removed a println. + * Revision 1.9 2001/06/30 14:57:29 mpowers Removed a println. * - * Revision 1.8 2001/06/29 22:28:19 mpowers - * Tabs to spaces. + * Revision 1.8 2001/06/29 22:28:19 mpowers Tabs to spaces. * - * Revision 1.7 2001/06/29 22:17:31 mpowers - * Now updating the component on establishConnection. + * Revision 1.7 2001/06/29 22:17:31 mpowers Now updating the component on + * establishConnection. * - * Revision 1.6 2001/05/14 15:24:49 mpowers - * Only updating if change was made. Feels like I had fixed this here before. + * Revision 1.6 2001/05/14 15:24:49 mpowers Only updating if change was made. + * Feels like I had fixed this here before. * - * Revision 1.5 2001/04/09 21:41:08 mpowers - * Fixed a bug I thought that I had fixed before. + * Revision 1.5 2001/04/09 21:41:08 mpowers Fixed a bug I thought that I had + * fixed before. * - * Revision 1.4 2001/03/01 20:37:17 mpowers - * Updated docs to emphasize that titles aspect is optional. + * Revision 1.4 2001/03/01 20:37:17 mpowers Updated docs to emphasize that + * titles aspect is optional. * - * Revision 1.3 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.2 2001/01/10 17:01:08 mpowers - * Caught a common developer error. + * Revision 1.2 2001/01/10 17:01:08 mpowers Caught a common developer error. * - * Revision 1.1.1.1 2000/12/21 15:48:43 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:43 mpowers Contributing wotonomy. * - * Revision 1.8 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.8 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java index ba50879..c201ae3 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DateAssociation.java @@ -36,578 +36,430 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* DateAssociation binds any component that has a set and get Date methods and -* fire actions events when the date has been changed. Bindings are: -*
    -*
  • value: a property convertable to/from a date
  • -*
  • editable: a boolean property that determines whether -* the user can edit the date in the component
  • -*
  • enabled: a boolean property that determines whether -* the component is enabled or disabled
  • -*
-* -* @author rob@yahoo.com -* @version $Revision: 904 $ -*/ -public class DateAssociation extends EOAssociation - implements ActionListener, FocusListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "date", "enabled", "editable" - } ); - - private final static NSSelector getDate = - new NSSelector( "getDate" ); - private final static NSSelector setDate = - new NSSelector( "setDate", - new Class[] { Date.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector setEditable = - new NSSelector( "setEditable", - new Class[] { boolean.class } ); + * DateAssociation binds any component that has a set and get Date methods and + * fire actions events when the date has been changed. Bindings are: + *
    + *
  • value: a property convertable to/from a date
  • + *
  • editable: a boolean property that determines whether the user can edit + * the date in the component
  • + *
  • enabled: a boolean property that determines whether the component is + * enabled or disabled
  • + *
+ * + * @author rob@yahoo.com + * @version $Revision: 904 $ + */ +public class DateAssociation extends EOAssociation implements ActionListener, FocusListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, EditableAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "date", "enabled", "editable" }); + + private final static NSSelector getDate = new NSSelector("getDate"); + private final static NSSelector setDate = new NSSelector("setDate", new Class[] { Date.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector setEditable = new NSSelector("setEditable", new Class[] { boolean.class }); // dirty handling private boolean needsUpdate; - private Date nullValue; // placeholder for null value flag - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public DateAssociation ( Object anObject ) - { - super( anObject ); + private Date nullValue; // placeholder for null value flag + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public DateAssociation(Object anObject) { + super(anObject); needsUpdate = false; - nullValue = null; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setDate.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and Focus Listener to the specified object. - */ - public void establishConnection () - { + nullValue = null; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setDate.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and Focus + * Listener to the specified object. + */ + public void establishConnection() { Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); } - super.establishConnection(); + super.establishConnection(); // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; Object value; // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - } - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup - - // convert value to date - try - { - Date dateValue = null; - // (Date) ValueConverter.convertObjectToClass( value, Date.class ); - - if ( value instanceof Date ) - { - dateValue = (Date) value; - } - - if ( ( dateValue == null ) && ( value instanceof Calendar ) ) - { - dateValue = ( ( Calendar )value ).getTime(); - } - - if ( dateValue == null ) - { - // current time (placeholder) - nullValue = new Date(); - dateValue = nullValue; - } - else - { - nullValue = null; - } - - if ( !dateValue.equals( getDate.invoke( component ) ) ) - { // No need to update if there is no change. - setDate.invoke( component, dateValue ); - needsUpdate = false; - } - - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + } + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup + + // convert value to date + try { + Date dateValue = null; + // (Date) ValueConverter.convertObjectToClass( value, Date.class ); + + if (value instanceof Date) { + dateValue = (Date) value; + } + + if ((dateValue == null) && (value instanceof Calendar)) { + dateValue = ((Calendar) value).getTime(); + } + + if (dateValue == null) { + // current time (placeholder) + nullValue = new Date(); + dateValue = nullValue; + } else { + nullValue = null; + } + + if (!dateValue.equals(getDate.invoke(component))) { // No need to update if there is no change. + setDate.invoke(component, dateValue); + needsUpdate = false; + } + + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if (((displayGroup != null) || (key != null)) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != ((Component)component).isEnabled() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != ((Component) component).isEnabled()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( ( displayGroup != null ) || ( key != null ) ) - && ( setEditable.implementedByObject( component ) ) ) - { - try - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - setEditable.invoke( component, converted ); - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection (editable aspect)", exc ); + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if (((displayGroup != null) || (key != null)) && (setEditable.implementedByObject(component))) { + try { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + setEditable.invoke(component, converted); + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection (editable aspect)", exc); } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } + } /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - if ( !needsUpdate ) return true; - - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object component = object(); - Object value = null; - try - { - if ( getDate.implementedByObject( component ) ) - { - value = getDate.invoke( component ); - } - if ( nullValue != null ) - { - if ( nullValue.equals( value ) ) - { - value = null; - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - needsUpdate = false; - - boolean returnValue = true; - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; - - } - return false; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + if (!needsUpdate) + return true; + + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object component = object(); + Object value = null; + try { + if (getDate.implementedByObject(component)) { + value = getDate.invoke(component); + } + if (nullValue != null) { + if (nullValue.equals(value)) { + value = null; + } + } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + needsUpdate = false; + + boolean returnValue = true; + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; + + } + return false; } - // interface ActionListener + // interface ActionListener /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - needsUpdate = true; - writeValueToDisplayGroup(); // TODO: Should we do this here or on focus lost? + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + needsUpdate = true; + writeValueToDisplayGroup(); // TODO: Should we do this here or on focus lost? } - // interface FocusListener + // interface FocusListener /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( endEditing() ) - { + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (endEditing()) { Object o; EODisplayGroup displayGroup; Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); } } - } - else - { + } else { // probably should notify of a validation error here, // but how to also handle actionPerformed without copying code? -/* - Object value = null; - try - { - if ( getText.implementedByObject( object() ) ) - { - value = getText.invoke( object() ); - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - String key = displayGroupKeyForAspect( ValueAspect ); - if ( displayGroup != null ) - { - if ( displayGroup.associationFailedToValidateValue( - this, (String) value, key, object(), - "That format was not recognized." ) ) - { - new net.wotonomy.ui.swing.util.StackTraceInspector(); - } - if ( object() instanceof Component ) - { - ((Component)object()).requestFocus(); - } - } -*/ + /* + * Object value = null; try { if ( getText.implementedByObject( object() ) ) { + * value = getText.invoke( object() ); } } catch ( Exception exc ) { throw new + * WotonomyException( "Error updating display group", exc ); } + * + * EODisplayGroup displayGroup = displayGroupForAspect( ValueAspect ); String + * key = displayGroupKeyForAspect( ValueAspect ); if ( displayGroup != null ) { + * if ( displayGroup.associationFailedToValidateValue( this, (String) value, + * key, object(), "That format was not recognized." ) ) { new + * net.wotonomy.ui.swing.util.StackTraceInspector(); } if ( object() instanceof + * Component ) { ((Component)object()).requestFocus(); } } + */ } - } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.7 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.6 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.5 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.5 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.4 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.4 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.3 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.2 2001/01/17 16:25:26 mpowers - * Now catching null values from data object. + * Revision 1.2 2001/01/17 16:25:26 mpowers Now catching null values from data + * object. * - * Revision 1.1 2001/01/10 22:26:32 mpowers - * Contributing DateAssociation. + * Revision 1.1 2001/01/10 22:26:32 mpowers Contributing DateAssociation. * - * Revision 1.1 2001/01/10 21:30:27 rglista - * Initial checkin + * Revision 1.1 2001/01/10 21:30:27 rglista Initial checkin * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java index 290480d..f891ede 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupActionAssociation.java @@ -27,108 +27,82 @@ import net.wotonomy.foundation.internal.WotonomyException; import net.wotonomy.ui.EODisplayGroup; /** -* ActionAssociation binds any ActionEvent broadcaster -* (typically Buttons and the like) to a display group, -* but invokes actions directly on the bound display -* group rather than the selected objects. -* Bindings are: -*
    -*
  • action: a method to be invoked on the bound display group. -* If the argument aspect is bound, the method must take -* one argument. Otherwise, the method must take no arguments.
  • -*
  • argument: the attribute of the selected object(s) (possibly -* from a different display group) that will be used as an argument -* to the action method
  • -*
  • enabled: a boolean property that determines whether -* the controlled component is enabled
  • -*
  • visible: a boolean property that determines whether -* the controlled component is visible
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class DisplayGroupActionAssociation extends ActionAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "target" - } ); - - static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public DisplayGroupActionAssociation ( Object anObject ) - { - super( anObject ); - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { + * ActionAssociation binds any ActionEvent broadcaster (typically Buttons and + * the like) to a display group, but invokes actions directly on the bound + * display group rather than the selected objects. Bindings are: + *
    + *
  • action: a method to be invoked on the bound display group. If the + * argument aspect is bound, the method must take one argument. Otherwise, the + * method must take no arguments.
  • + *
  • argument: the attribute of the selected object(s) (possibly from a + * different display group) that will be used as an argument to the action + * method
  • + *
  • enabled: a boolean property that determines whether the controlled + * component is enabled
  • + *
  • visible: a boolean property that determines whether the controlled + * component is visible
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DisplayGroupActionAssociation extends ActionAssociation { + static final NSArray aspects = new NSArray( + new Object[] { ActionAspect, ArgumentAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "target" }); + + static NSSelector addActionListener = new NSSelector("addActionListener", new Class[] { ActionListener.class }); + static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public DisplayGroupActionAssociation(Object anObject) { + super(anObject); + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { EODisplayGroup actionDisplayGroup = null; String actionKey = null; - + // action aspect - actionDisplayGroup = displayGroupForAspect( ActionAspect ); - if ( actionDisplayGroup != null ) - { - actionKey = displayGroupKeyForAspect( ActionAspect ); + actionDisplayGroup = displayGroupForAspect(ActionAspect); + if (actionDisplayGroup != null) { + actionKey = displayGroupKeyForAspect(ActionAspect); - //TODO: argument aspect not implemented + // TODO: argument aspect not implemented - try - { - NSSelector.invoke( actionKey, actionDisplayGroup ); - } - catch ( Exception exc ) - { - throw new WotonomyException( "DisplayGroupActionAssociation: " - + "error invoking action: " + actionKey, exc ); + try { + NSSelector.invoke(actionKey, actionDisplayGroup); + } catch (Exception exc) { + throw new WotonomyException("DisplayGroupActionAssociation: " + "error invoking action: " + actionKey, + exc); } } } - + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:48:46 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:46 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java index c8ecd36..d782252 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupInspector.java @@ -35,86 +35,75 @@ import net.wotonomy.ui.swing.util.ObjectInspector; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* The DisplayGroupInspector displays a JFrame that -* shows allows you to view and manipulate a display group. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * The DisplayGroupInspector displays a JFrame that shows allows you to view and + * manipulate a display group. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class DisplayGroupInspector -{ - protected JList list; - protected EODisplayGroup displayGroup; - -/** -* Displays and manipulats the specified display group. -*/ - public DisplayGroupInspector( EODisplayGroup aDisplayGroup ) - { - displayGroup = aDisplayGroup; - - list = new JList(); - list.addMouseListener( new MouseAdapter() - { - public void mouseClicked( MouseEvent e ) - { - if ( e.getClickCount() == 2 ) - { - Object selection = displayGroup.selectedObject(); - if ( selection != null ) - { - new ObjectInspector( selection ); - } - } - } - } ); - - EOAssociation assoc = new ListAssociation( list ); - assoc.bindAspect( EOAssociation.TitlesAspect, displayGroup, "" ); - assoc.establishConnection(); - - initLayout(); - - } - - protected void initLayout() - { - JPanel panel = new JPanel(); - panel.setLayout( new BorderLayout() ); - panel.setBorder( new EmptyBorder( 10, 10, 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( list ); - scrollPane.setPreferredSize( new Dimension( 200, 200 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Display Group Inspector" ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } +public class DisplayGroupInspector { + protected JList list; + protected EODisplayGroup displayGroup; + + /** + * Displays and manipulats the specified display group. + */ + public DisplayGroupInspector(EODisplayGroup aDisplayGroup) { + displayGroup = aDisplayGroup; + + list = new JList(); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + Object selection = displayGroup.selectedObject(); + if (selection != null) { + new ObjectInspector(selection); + } + } + } + }); + + EOAssociation assoc = new ListAssociation(list); + assoc.bindAspect(EOAssociation.TitlesAspect, displayGroup, ""); + assoc.establishConnection(); + + initLayout(); + + } + + protected void initLayout() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setPreferredSize(new Dimension(200, 200)); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle("Display Group Inspector"); + window.getContentPane().add(panel); + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } } - + /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2003/06/26 23:28:00 mpowers - * Added double click. + * Revision 1.2 2003/06/26 23:28:00 mpowers Added double click. * - * Revision 1.1 2001/05/29 19:57:47 mpowers - * Added some neglected files. + * Revision 1.1 2001/05/29 19:57:47 mpowers Added some neglected files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java index 1756285..d530f36 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/DisplayGroupNode.java @@ -46,901 +46,727 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.TreeModelAssociation.DelegatingTreeDataSource; /** -* DisplayGroupNodes are used as nodes in the -* TreeModelAssociation's implementation of TreeModel, -* and is tightly coupled with TreeModelAssociation -* and MasterDetailAssociation.

-* -* Even though it is no longer package access, -* don't rely on this class because we want to -* have the option of completely replacing this -* approach in the future. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ - abstract public class DisplayGroupNode - extends EODisplayGroup - { - protected TreeModelAssociation parentAssociation; - protected EODelayedObserver targetObserver; - protected NSMutableDictionary childNodes; - protected EODisplayGroup parentGroup; - protected Object target; - protected boolean isFetched; - protected boolean isFetchNeeded; - protected boolean useParentOrderings; - protected boolean useParentQualifier; - - /** - * Constructor for all nodes. - * Root node must have a null target. - */ - public DisplayGroupNode( - TreeModelAssociation aParentAssociation, - EODisplayGroup aParentGroup, - Object aTarget ) - { + * DisplayGroupNodes are used as nodes in the TreeModelAssociation's + * implementation of TreeModel, and is tightly coupled with TreeModelAssociation + * and MasterDetailAssociation.
+ *
+ * + * Even though it is no longer package access, don't rely on this class because + * we want to have the option of completely replacing this approach in the + * future. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +abstract public class DisplayGroupNode extends EODisplayGroup { + protected TreeModelAssociation parentAssociation; + protected EODelayedObserver targetObserver; + protected NSMutableDictionary childNodes; + protected EODisplayGroup parentGroup; + protected Object target; + protected boolean isFetched; + protected boolean isFetchNeeded; + protected boolean useParentOrderings; + protected boolean useParentQualifier; + + /** + * Constructor for all nodes. Root node must have a null target. + */ + public DisplayGroupNode(TreeModelAssociation aParentAssociation, EODisplayGroup aParentGroup, Object aTarget) { //new net.wotonomy.ui.swing.util.StackTraceInspector( ""+aTarget ); //System.out.println( "DisplayGroupNode.new: " + aTarget ); - parentAssociation = aParentAssociation; - target = null; - targetObserver = null; - parentGroup = aParentGroup; - childNodes = new NSMutableDictionary(); - isFetched = false; - isFetchNeeded = false; - useParentOrderings = true; - useParentQualifier = true; - - EODataSource parentSource = null; - if ( parentGroup != null ) - { - parentSource = parentGroup.dataSource(); - } - else - if ( parentAssociation.titlesDisplayGroup != null ) - { - parentSource = parentAssociation.titlesDisplayGroup.dataSource(); - } - - // create child datasource - if ( aTarget != null ) // not root node - { - if ( parentAssociation.childrenKey != null ) - { - if ( parentSource == null ) - { - throw new WotonomyException( - "Need a data source when children aspect is bound." ); - } - - NSArray displayedObjects = parentGroup.displayedObjects(); - EODataSource childSource = parentSource.dataSourceQualifiedByKey( - parentAssociation.childrenKey ); - childSource.qualifyWithRelationshipKey( - parentAssociation.childrenKey, aTarget ); - - // create new display group using child data source - this.setDataSource( childSource ); - - // establish observer for target object - setTarget( aTarget ); - } - else // only titles is bound - { - // establish observer for target object - setTarget( aTarget ); - - setDataSource( new PropertyDataSource() - { - public NSArray fetchObjects() - { - return new NSArray(); - } - } ); - } - } - else // else root node - { - // root node uses PropertyDataSource by default - if ( parentSource == null ) - { - setDataSource( new PropertyDataSource() - { - public NSArray fetchObjects() - { - if ( parentGroup != null ) - { - return parentGroup.displayedObjects(); - } - return null; - } - } ); - } - else - { - // root node uses parent source directly - setDataSource( parentSource ); - } - } - } - - /** - * Overridden to unregister as an editor of the editing context, - * since we don't directly present a user interface. - */ - public void setDataSource ( EODataSource aDataSource ) - { - super.setDataSource( aDataSource ); - if ( ( aDataSource != null ) - && ( aDataSource.editingContext() != null ) ) - { - aDataSource.editingContext().removeEditor( this ); - } - } - - /** - * Returns whether the node should call fetch(). - */ - protected boolean isFetched() - { - if ( isFetchNeeded() ) - { - setFetchNeeded( false ); - fetch(); - } - return isFetched; - } - - /** - * Sets whether the node should call fetch(). - */ - protected void setFetched( boolean fetched ) - { + parentAssociation = aParentAssociation; + target = null; + targetObserver = null; + parentGroup = aParentGroup; + childNodes = new NSMutableDictionary(); + isFetched = false; + isFetchNeeded = false; + useParentOrderings = true; + useParentQualifier = true; + + EODataSource parentSource = null; + if (parentGroup != null) { + parentSource = parentGroup.dataSource(); + } else if (parentAssociation.titlesDisplayGroup != null) { + parentSource = parentAssociation.titlesDisplayGroup.dataSource(); + } + + // create child datasource + if (aTarget != null) // not root node + { + if (parentAssociation.childrenKey != null) { + if (parentSource == null) { + throw new WotonomyException("Need a data source when children aspect is bound."); + } + + NSArray displayedObjects = parentGroup.displayedObjects(); + EODataSource childSource = parentSource.dataSourceQualifiedByKey(parentAssociation.childrenKey); + childSource.qualifyWithRelationshipKey(parentAssociation.childrenKey, aTarget); + + // create new display group using child data source + this.setDataSource(childSource); + + // establish observer for target object + setTarget(aTarget); + } else // only titles is bound + { + // establish observer for target object + setTarget(aTarget); + + setDataSource(new PropertyDataSource() { + public NSArray fetchObjects() { + return new NSArray(); + } + }); + } + } else // else root node + { + // root node uses PropertyDataSource by default + if (parentSource == null) { + setDataSource(new PropertyDataSource() { + public NSArray fetchObjects() { + if (parentGroup != null) { + return parentGroup.displayedObjects(); + } + return null; + } + }); + } else { + // root node uses parent source directly + setDataSource(parentSource); + } + } + } + + /** + * Overridden to unregister as an editor of the editing context, since we don't + * directly present a user interface. + */ + public void setDataSource(EODataSource aDataSource) { + super.setDataSource(aDataSource); + if ((aDataSource != null) && (aDataSource.editingContext() != null)) { + aDataSource.editingContext().removeEditor(this); + } + } + + /** + * Returns whether the node should call fetch(). + */ + protected boolean isFetched() { + if (isFetchNeeded()) { + setFetchNeeded(false); + fetch(); + } + return isFetched; + } + + /** + * Sets whether the node should call fetch(). + */ + protected void setFetched(boolean fetched) { //System.out.println( "DisplayGroupNode.setFetched: " + fetched + " : " + this + " : " + target ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); - isFetched = fetched; - } - - /** - * Returns whether the node is in need of a refetch. - */ - protected boolean isFetchNeeded() - { - return isFetchNeeded; - } - - /** - * Returns whether the node should call fetch(). - */ - protected void setFetchNeeded( boolean fetchNeeded ) - { + isFetched = fetched; + } + + /** + * Returns whether the node is in need of a refetch. + */ + protected boolean isFetchNeeded() { + return isFetchNeeded; + } + + /** + * Returns whether the node should call fetch(). + */ + protected void setFetchNeeded(boolean fetchNeeded) { //System.out.println( "DisplayGroupNode.setFetchNeeded: " + fetchNeeded + " : " + this + " : " + target ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); - isFetchNeeded = fetchNeeded; - } - - /** - * Subclasses should override this method to fire an appropriate insertion event. - */ - protected void fireNodesInserted( Object[] path, int[] indexes, Object[] objects ) - { + isFetchNeeded = fetchNeeded; + } + + /** + * Subclasses should override this method to fire an appropriate insertion + * event. + */ + protected void fireNodesInserted(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesInserted: " + this ); - parentAssociation.fireTreeNodesInserted( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate change event. - */ - protected void fireNodesChanged( Object[] path, int[] indexes, Object[] objects ) - { + parentAssociation.fireTreeNodesInserted(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate change event. + */ + protected void fireNodesChanged(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesChanged: " + this ); - parentAssociation.fireTreeNodesChanged( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate deletion event. - */ - protected void fireNodesRemoved( Object[] path, int[] indexes, Object[] objects ) - { + parentAssociation.fireTreeNodesChanged(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate deletion event. + */ + protected void fireNodesRemoved(Object[] path, int[] indexes, Object[] objects) { //System.out.println( "fireNodesRemoved: " + this ); - parentAssociation.fireTreeNodesRemoved( - this, path, indexes, objects ); - } - - /** - * Subclasses should override this method to fire an appropriate event. - */ - protected void fireStructureChanged( Object[] path, int[] indexes, Object[] objects ) - { - parentAssociation.fireTreeStructureChanged( - this, path, indexes, objects ); - } - - /** - * Overridden to broadcast a tree event after super executes. - */ - public void insertObjectAtIndex ( Object anObject, int anIndex ) - { - int count = getChildCount(); // gets old count - if ( target == null ) - { - // if root node, forward to parent: - // circumventing delegating data source, if any - EODataSource dataSource = parentGroup.dataSource(); - if ( dataSource instanceof DelegatingTreeDataSource ) - { - parentGroup.setDataSource( - ((DelegatingTreeDataSource)dataSource).delegateDataSource ); - } - parentGroup.insertObjectAtIndex( anObject, anIndex ); - if ( dataSource instanceof DelegatingTreeDataSource ) - { - parentGroup.setDataSource( dataSource ); - } - return; // prevent event from firing (?) - } - else // not root node - { - super.insertObjectAtIndex( anObject, anIndex ); - } - } - - /** - * Overridden to broadcast a tree event after super executes. - */ - public boolean deleteObjectAtIndex ( int anIndex ) - { - boolean result; - Object node = getChildNodeAt( anIndex ); - if ( target == null ) - { - // if root node, forward to parent: - result = parentGroup.deleteObjectAtIndex( anIndex ); - } - else // not root node - { - result = super.deleteObjectAtIndex( anIndex ); - } - - return result; - } - - /** - * Returns the child node that corresponds to the - * specified index, creating it if necessary. - * The index must be within bounds or an exception - * is thrown. - */ - public DisplayGroupNode getChildNodeAt( int anIndex ) - { - boolean wasFetched = isFetched(); - if ( ! wasFetched ) fetch(); - Object o = displayedObjects.objectAtIndex( anIndex ); - DisplayGroupNode result = getChildNodeForObject( o ); - if ( result == null ) - { - result = createChildNodeForObject( o ); - } - return result; - } - - /** - * Returns a child node that corresponds to the - * specified object, returning null if not found. - */ - protected DisplayGroupNode getChildNodeForObject( Object anObject ) - { - return (DisplayGroupNode) - childNodes.objectForKey( new ReferenceKey( anObject ) ); - } - - /** - * Creates a child node that corresponds to the - * specified object. - */ - private DisplayGroupNode createChildNodeForObject( Object anObject ) - { - DisplayGroupNode result = parentAssociation.createNode( this, anObject ); - childNodes.setObjectForKey( result, new ReferenceKey( anObject ) ); - return result; - } - - /** - * Returns a tree path of all DisplayGroupNodes leading - * to this node, including the root node (but excluding the - * titles display group). - */ - public TreePath treePath() - { - List path = new LinkedList(); - EODisplayGroup node = this; - while ( node instanceof DisplayGroupNode ) - { - // insert at head of list - path.add( 0, node ); - node = ((DisplayGroupNode)node).parentGroup; - } - return new TreePath( path.toArray() ); - } - - /** - * Overridden to return the parent group's - * sort ordering if useParentOrderings is true. - * useParentOrderings is true by default. - */ - public NSArray sortOrderings() - { - if ( ( useParentOrderings ) - && ( parentGroup != null ) ) - { - return parentGroup.sortOrderings(); - } - return super.sortOrderings(); - } - - /** - * Overridden to set useParentOrderings to false, - * or true if aList is null. - */ - public void setSortOrderings ( List aList ) - { - if ( aList == null ) - { - useParentOrderings = true; - } - else - { - useParentOrderings = false; - super.setSortOrderings( aList ); - } - } - - /** - * Overridden to return the parent group's - * qualifier if useParentQualifier is true. - * useParentQualifier is true by default. - */ - public EOQualifier qualifier() - { - if ( ( useParentQualifier ) - && ( parentGroup != null ) ) - { - return parentGroup.qualifier(); - } - return super.qualifier(); - } - - /** - * Overridden to set useParentQualifier to false, - * or true if aList is null. - */ - public void setQualifier ( EOQualifier aQualifier ) - { - if ( aQualifier == null ) - { - useParentQualifier = true; - } - else - { - useParentQualifier = false; - super.setQualifier( aQualifier ); - } - } - - /** - * Overridden to set isFetched to true. - */ - public boolean fetch() - { + parentAssociation.fireTreeNodesRemoved(this, path, indexes, objects); + } + + /** + * Subclasses should override this method to fire an appropriate event. + */ + protected void fireStructureChanged(Object[] path, int[] indexes, Object[] objects) { + parentAssociation.fireTreeStructureChanged(this, path, indexes, objects); + } + + /** + * Overridden to broadcast a tree event after super executes. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + int count = getChildCount(); // gets old count + if (target == null) { + // if root node, forward to parent: + // circumventing delegating data source, if any + EODataSource dataSource = parentGroup.dataSource(); + if (dataSource instanceof DelegatingTreeDataSource) { + parentGroup.setDataSource(((DelegatingTreeDataSource) dataSource).delegateDataSource); + } + parentGroup.insertObjectAtIndex(anObject, anIndex); + if (dataSource instanceof DelegatingTreeDataSource) { + parentGroup.setDataSource(dataSource); + } + return; // prevent event from firing (?) + } else // not root node + { + super.insertObjectAtIndex(anObject, anIndex); + } + } + + /** + * Overridden to broadcast a tree event after super executes. + */ + public boolean deleteObjectAtIndex(int anIndex) { + boolean result; + Object node = getChildNodeAt(anIndex); + if (target == null) { + // if root node, forward to parent: + result = parentGroup.deleteObjectAtIndex(anIndex); + } else // not root node + { + result = super.deleteObjectAtIndex(anIndex); + } + + return result; + } + + /** + * Returns the child node that corresponds to the specified index, creating it + * if necessary. The index must be within bounds or an exception is thrown. + */ + public DisplayGroupNode getChildNodeAt(int anIndex) { + boolean wasFetched = isFetched(); + if (!wasFetched) + fetch(); + Object o = displayedObjects.objectAtIndex(anIndex); + DisplayGroupNode result = getChildNodeForObject(o); + if (result == null) { + result = createChildNodeForObject(o); + } + return result; + } + + /** + * Returns a child node that corresponds to the specified object, returning null + * if not found. + */ + protected DisplayGroupNode getChildNodeForObject(Object anObject) { + return (DisplayGroupNode) childNodes.objectForKey(new ReferenceKey(anObject)); + } + + /** + * Creates a child node that corresponds to the specified object. + */ + private DisplayGroupNode createChildNodeForObject(Object anObject) { + DisplayGroupNode result = parentAssociation.createNode(this, anObject); + childNodes.setObjectForKey(result, new ReferenceKey(anObject)); + return result; + } + + /** + * Returns a tree path of all DisplayGroupNodes leading to this node, including + * the root node (but excluding the titles display group). + */ + public TreePath treePath() { + List path = new LinkedList(); + EODisplayGroup node = this; + while (node instanceof DisplayGroupNode) { + // insert at head of list + path.add(0, node); + node = ((DisplayGroupNode) node).parentGroup; + } + return new TreePath(path.toArray()); + } + + /** + * Overridden to return the parent group's sort ordering if useParentOrderings + * is true. useParentOrderings is true by default. + */ + public NSArray sortOrderings() { + if ((useParentOrderings) && (parentGroup != null)) { + return parentGroup.sortOrderings(); + } + return super.sortOrderings(); + } + + /** + * Overridden to set useParentOrderings to false, or true if aList is null. + */ + public void setSortOrderings(List aList) { + if (aList == null) { + useParentOrderings = true; + } else { + useParentOrderings = false; + super.setSortOrderings(aList); + } + } + + /** + * Overridden to return the parent group's qualifier if useParentQualifier is + * true. useParentQualifier is true by default. + */ + public EOQualifier qualifier() { + if ((useParentQualifier) && (parentGroup != null)) { + return parentGroup.qualifier(); + } + return super.qualifier(); + } + + /** + * Overridden to set useParentQualifier to false, or true if aList is null. + */ + public void setQualifier(EOQualifier aQualifier) { + if (aQualifier == null) { + useParentQualifier = true; + } else { + useParentQualifier = false; + super.setQualifier(aQualifier); + } + } + + /** + * Overridden to set isFetched to true. + */ + public boolean fetch() { //System.out.println( "DisplayGroupNode.fetch: " + this + " : " ); //if ( getClass().getName().indexOf( "Activity" ) != -1 ) //{ // new net.wotonomy.ui.swing.util.StackTraceInspector( this.toString() ); //} - // set flag - setFetched( true ); + // set flag + setFetched(true); - // skip root node - if ( target == null ) return true; + // skip root node + if (target == null) + return true; - // requalify - dataSource().qualifyWithRelationshipKey( - parentAssociation.childrenKey, target ); + // requalify + dataSource().qualifyWithRelationshipKey(parentAssociation.childrenKey, target); - // call to super - return super.fetch(); + // call to super + return super.fetch(); //boolean result = super.fetch(); //System.out.println( displayedObjects() ); //return result; - } - - /** - * Returns the object at the appropriate index - * in the parent display group. - */ - public Object object() - { - // if root node - if ( target == null ) - { - return parentAssociation.rootLabel(); - } - return target; - } - - /** - * Returns the string value of the title property - * on the object in the parent display group corresponding - * to this index. The tree renderer asks JTrees to - * call this method to retrieve a value for display. - */ - public String toString() - { - Object result = getUserObject(); - if ( result == null ) result = "[null]"; - return result.toString(); - } - - // parts of interface TreeNode - - public int getChildCount() - { - if ( ! isFetched() ) fetch(); + } + + /** + * Returns the object at the appropriate index in the parent display group. + */ + public Object object() { + // if root node + if (target == null) { + return parentAssociation.rootLabel(); + } + return target; + } + + /** + * Returns the string value of the title property on the object in the parent + * display group corresponding to this index. The tree renderer asks JTrees to + * call this method to retrieve a value for display. + */ + public String toString() { + Object result = getUserObject(); + if (result == null) + result = "[null]"; + return result.toString(); + } + + // parts of interface TreeNode + + public int getChildCount() { + if (!isFetched()) + fetch(); //if ( toString().indexOf("154.16406")!=-1){ //System.out.println( "getChildCount: " + displayedObjects.count() + " : " + this ); //new RuntimeException().printStackTrace(); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); //} - return displayedObjects.count(); - } - - public int getIndex(DisplayGroupNode node) - { - if ( ! isFetched() ) fetch(); - return displayedObjects.indexOfObject( - ((DisplayGroupNode)node).target ); - } - - public boolean getAllowsChildren() - { - return true; - } - - public boolean isLeaf() - { - // if not root node and isLeaf aspect is bound - if ( ( target != null ) - && ( parentGroup != null ) - && ( parentAssociation.leafKey != null ) ) - { - Object value; - if ( parentAssociation.leafDisplayGroup != null ) - { - value = parentGroup.valueForObject( - target, parentAssociation.leafKey ); - } - else - { - value = parentAssociation.leafKey; - } - - // getBoolean returns true for zero, among other things - Object result = ValueConverter.getBoolean( value ); - if ( result != null ) - { - return ((Boolean)result).booleanValue(); - } - } - - // otherwise, we have to fetch and return count - return ( getChildCount() == 0 ); - } - - public Enumeration children() - { - int count = getChildCount(); - Vector v = new Vector(); - for ( int i = 0; i < count; i++ ) - { - v.add( getChildNodeAt( i ) ); - } - return v.elements(); - } - - // parts of interface MutableTreeNode - - public void insert(DisplayGroupNode aChild, int anIndex) - { - insertObjectAtIndex( - ((DisplayGroupNode)aChild).object(), anIndex ); - } - - public void remove(int index) - { - deleteObjectAtIndex( index ); - } - - /** - * Removes the node at the index corresponding - * to the index of the object. - */ - public void remove(DisplayGroupNode node) - { - remove( getIndex( node ) ); - } - - /** - * Removes our object from the parent display group. - */ - public void removeFromParent() - { - int index = parentGroup.displayedObjects().indexOfIdenticalObject( target ); - if ( index != NSArray.NotFound ) - { - parentGroup.deleteObjectAtIndex( index ); - } - else - { - throw new WotonomyException( - "Object not found in parent group: " + target ); - } - } - - /** - * Removes our object from the parent display group - * and adds it to the end of the specified node's children. - */ - public void setParent(DisplayGroupNode newParent) - { - removeFromParent(); - newParent.insertObjectAtIndex( - object(), newParent.displayedObjects.size() ); - } - - /** - * Returns the value of the displayed property in the parent display group - * at the index that corresponds to the index of this node. - */ - public Object getUserObject() - { - return valueForKey( parentAssociation.titlesKey ); - } - - /** - * Sets the value of the displayed property in the parent display group - * at the index that corresponds to the index of this node. - */ - public void setUserObject( Object aValue ) - { - setValueForKey( aValue, parentAssociation.titlesKey ); - } - - /** - * Returns a value from the object in the parent display group - * at the index that corresponds to the index of this node. - * For the root node, if the titles key is specified, the root - * label is returned, otherwise null is returned. - */ - public Object valueForKey( String aKey ) - { - // if root node - if ( target == null ) - { - // compare by ref is okay for strings - if ( aKey == parentAssociation.titlesKey ) - { - return parentAssociation.rootLabel(); - } - return null; - } - return parentGroup.valueForObject( target, aKey ); - } - - /** - * Sets a value on the object in the parent display group - * at the index that corresponds to the index of this node. - * For the root node, this method only works if aKey is the - * titlesAspect's key, otherwise does nothing. - */ - public void setValueForKey(Object aValue, String aKey) - { - // if root node, return. - if ( target == null ) - { - // compare by ref is okay for strings - if ( aKey == parentAssociation.titlesKey ) - { - parentAssociation.setRootLabel( aValue ); - - // how to handle root node? tree event docs don't say. - fireNodesChanged ( treePath().getPath(), - new int[] { 0 }, - new Object[] { this } ); - } - return; - } - - parentGroup.setValueForObject( - aValue, target, aKey ); - } - - /** - * Perform any clean up in this method. - * The node will not be reused after this method is called. - * This implementation removes itself from the parent's - * set of child nodes, sets target and datasource to null, - * and then calls disposeChildNodes(). - */ - protected void dispose() - { //System.out.println( "dispose: " + this.getClass().getName() + " : " + this ); - if ( parentGroup != null ) - { - ((DisplayGroupNode)parentGroup).childNodes.remove( - new ReferenceKey( target ) ); - } - setTarget( (Object) null ); - setDataSource( null ); - disposeChildNodes(); - } - - /** - * Calls dispose() on all child nodes. - */ - protected void disposeChildNodes() - { - Iterator i = new LinkedList(childNodes.values()).iterator(); - while ( i.hasNext() ) - { - ((DisplayGroupNode) i.next()).dispose(); - } - } - - /** - * Called after the target object posts a change notification. - * This implementation re-fetches which triggers - * updateDisplayedObjects to broadcast any tree events. - * This method marks the parent object as changed if: - * (1) this object is not registered in the editing context - * of the titles display group's data source (if any), AND - * (2) the children key is not in the list of attributes - * of the parent object's EOClassDescription. - */ - public void targetChanged() - { - // if not root node - if ( target != null ) - { - // if we're not root and not fetched, stop here. - //FIXME: with this, some nodes have old values when moved. - //FIXME: without this, nodes are unnecessarily fetched. - //FIXME: might have parent modify isFetched of certain child nodes. - if ( isFetched() ) - { - fetch(); - } - else // not fetched - just update the display - { - updateDisplayedObjects(); - } -/* -//disabling this for performance reasons: -//might reenable later or find an alternate approach - // check to see if we need to mark the parent object as changed - EOEditingContext context = dataSource().editingContext(); - if ( ( context == null ) - || ( context.globalIDForObject( target ) == null ) ) - { - DisplayGroupNode parentNode = (DisplayGroupNode) parentGroup; - if ( parentNode.target != null ) - { - // only notify if childrenKey is an attribute of parentDesc - // (and therefore not a toOne or toMany relationship) - EOClassDescription parentDesc = - EOClassDescription.classDescriptionForClass( - parentNode.target.getClass() ); - if ( parentDesc.attributeKeys().contains( parentAssociation.childrenKey ) ) - { - // only notify if no context is already observing the object - // and we are an attribute key - EOObserverCenter.notifyObserversObjectWillChange( parentNode.target ); - } - } - } -*/ - } - else // root node - { - setObjectArray( parentAssociation.titlesDisplayGroup.displayedObjects() ); - } - - // finally, broadcast change event for this node - // even though we're not sure if the displayed value changed. - fireNodeChanged(); - } - - /** - * Fires a change event for this node. - */ - public void fireNodeChanged() - { - // if not root node - if ( target != null ) - { - int index = ((DisplayGroupNode)parentGroup).getIndex( this ); - if ( ( index != -1 ) - && ( treePath().getParentPath() != null ) ) - { - fireNodesChanged ( - treePath().getParentPath().getPath(), - new int[] { index }, - new Object[] { this } ); - } - } - } - -Object[] previouslyDisplayedObjects = new Object[0]; - /** - * Overridden to call to super, fire any tree events, and then - * call updateDisplayedObjects on all fetched child nodes. - * This method compares this node's displayed objects against - * the list of child nodes, synchronizes them, and then broadcasts - * only the necessary events to bring the view component up to date. - */ - public void updateDisplayedObjects() - { + return displayedObjects.count(); + } + + public int getIndex(DisplayGroupNode node) { + if (!isFetched()) + fetch(); + return displayedObjects.indexOfObject(((DisplayGroupNode) node).target); + } + + public boolean getAllowsChildren() { + return true; + } + + public boolean isLeaf() { + // if not root node and isLeaf aspect is bound + if ((target != null) && (parentGroup != null) && (parentAssociation.leafKey != null)) { + Object value; + if (parentAssociation.leafDisplayGroup != null) { + value = parentGroup.valueForObject(target, parentAssociation.leafKey); + } else { + value = parentAssociation.leafKey; + } + + // getBoolean returns true for zero, among other things + Object result = ValueConverter.getBoolean(value); + if (result != null) { + return ((Boolean) result).booleanValue(); + } + } + + // otherwise, we have to fetch and return count + return (getChildCount() == 0); + } + + public Enumeration children() { + int count = getChildCount(); + Vector v = new Vector(); + for (int i = 0; i < count; i++) { + v.add(getChildNodeAt(i)); + } + return v.elements(); + } + + // parts of interface MutableTreeNode + + public void insert(DisplayGroupNode aChild, int anIndex) { + insertObjectAtIndex(((DisplayGroupNode) aChild).object(), anIndex); + } + + public void remove(int index) { + deleteObjectAtIndex(index); + } + + /** + * Removes the node at the index corresponding to the index of the object. + */ + public void remove(DisplayGroupNode node) { + remove(getIndex(node)); + } + + /** + * Removes our object from the parent display group. + */ + public void removeFromParent() { + int index = parentGroup.displayedObjects().indexOfIdenticalObject(target); + if (index != NSArray.NotFound) { + parentGroup.deleteObjectAtIndex(index); + } else { + throw new WotonomyException("Object not found in parent group: " + target); + } + } + + /** + * Removes our object from the parent display group and adds it to the end of + * the specified node's children. + */ + public void setParent(DisplayGroupNode newParent) { + removeFromParent(); + newParent.insertObjectAtIndex(object(), newParent.displayedObjects.size()); + } + + /** + * Returns the value of the displayed property in the parent display group at + * the index that corresponds to the index of this node. + */ + public Object getUserObject() { + return valueForKey(parentAssociation.titlesKey); + } + + /** + * Sets the value of the displayed property in the parent display group at the + * index that corresponds to the index of this node. + */ + public void setUserObject(Object aValue) { + setValueForKey(aValue, parentAssociation.titlesKey); + } + + /** + * Returns a value from the object in the parent display group at the index that + * corresponds to the index of this node. For the root node, if the titles key + * is specified, the root label is returned, otherwise null is returned. + */ + public Object valueForKey(String aKey) { + // if root node + if (target == null) { + // compare by ref is okay for strings + if (aKey == parentAssociation.titlesKey) { + return parentAssociation.rootLabel(); + } + return null; + } + return parentGroup.valueForObject(target, aKey); + } + + /** + * Sets a value on the object in the parent display group at the index that + * corresponds to the index of this node. For the root node, this method only + * works if aKey is the titlesAspect's key, otherwise does nothing. + */ + public void setValueForKey(Object aValue, String aKey) { + // if root node, return. + if (target == null) { + // compare by ref is okay for strings + if (aKey == parentAssociation.titlesKey) { + parentAssociation.setRootLabel(aValue); + + // how to handle root node? tree event docs don't say. + fireNodesChanged(treePath().getPath(), new int[] { 0 }, new Object[] { this }); + } + return; + } + + parentGroup.setValueForObject(aValue, target, aKey); + } + + /** + * Perform any clean up in this method. The node will not be reused after this + * method is called. This implementation removes itself from the parent's set of + * child nodes, sets target and datasource to null, and then calls + * disposeChildNodes(). + */ + protected void dispose() { // System.out.println( "dispose: " + this.getClass().getName() + " : " + this ); + if (parentGroup != null) { + ((DisplayGroupNode) parentGroup).childNodes.remove(new ReferenceKey(target)); + } + setTarget((Object) null); + setDataSource(null); + disposeChildNodes(); + } + + /** + * Calls dispose() on all child nodes. + */ + protected void disposeChildNodes() { + Iterator i = new LinkedList(childNodes.values()).iterator(); + while (i.hasNext()) { + ((DisplayGroupNode) i.next()).dispose(); + } + } + + /** + * Called after the target object posts a change notification. This + * implementation re-fetches which triggers updateDisplayedObjects to broadcast + * any tree events. This method marks the parent object as changed if: (1) this + * object is not registered in the editing context of the titles display group's + * data source (if any), AND (2) the children key is not in the list of + * attributes of the parent object's EOClassDescription. + */ + public void targetChanged() { + // if not root node + if (target != null) { + // if we're not root and not fetched, stop here. + // FIXME: with this, some nodes have old values when moved. + // FIXME: without this, nodes are unnecessarily fetched. + // FIXME: might have parent modify isFetched of certain child nodes. + if (isFetched()) { + fetch(); + } else // not fetched - just update the display + { + updateDisplayedObjects(); + } + /* + * //disabling this for performance reasons: //might reenable later or find an + * alternate approach // check to see if we need to mark the parent object as + * changed EOEditingContext context = dataSource().editingContext(); if ( ( + * context == null ) || ( context.globalIDForObject( target ) == null ) ) { + * DisplayGroupNode parentNode = (DisplayGroupNode) parentGroup; if ( + * parentNode.target != null ) { // only notify if childrenKey is an attribute + * of parentDesc // (and therefore not a toOne or toMany relationship) + * EOClassDescription parentDesc = EOClassDescription.classDescriptionForClass( + * parentNode.target.getClass() ); if ( parentDesc.attributeKeys().contains( + * parentAssociation.childrenKey ) ) { // only notify if no context is already + * observing the object // and we are an attribute key + * EOObserverCenter.notifyObserversObjectWillChange( parentNode.target ); } } } + */ + } else // root node + { + setObjectArray(parentAssociation.titlesDisplayGroup.displayedObjects()); + } + + // finally, broadcast change event for this node + // even though we're not sure if the displayed value changed. + fireNodeChanged(); + } + + /** + * Fires a change event for this node. + */ + public void fireNodeChanged() { + // if not root node + if (target != null) { + int index = ((DisplayGroupNode) parentGroup).getIndex(this); + if ((index != -1) && (treePath().getParentPath() != null)) { + fireNodesChanged(treePath().getParentPath().getPath(), new int[] { index }, new Object[] { this }); + } + } + } + + Object[] previouslyDisplayedObjects = new Object[0]; + + /** + * Overridden to call to super, fire any tree events, and then call + * updateDisplayedObjects on all fetched child nodes. This method compares this + * node's displayed objects against the list of child nodes, synchronizes them, + * and then broadcasts only the necessary events to bring the view component up + * to date. + */ + public void updateDisplayedObjects() { //System.out.println( "updateDisplayedObjects: " + " : " + this ); //net.wotonomy.ui.swing.util.StackTraceInspector.printShortStackTrace(); //new RuntimeException().printStackTrace(); - super.updateDisplayedObjects(); - - // diff lists - boolean proceed = true; - Object[] oldObjects = previouslyDisplayedObjects; - Object[] newObjects = displayedObjects.toArray(); - if ( oldObjects.length == newObjects.length ) - { - proceed = false; - for ( int i = 0; i < newObjects.length; i++ ) - { - if ( oldObjects[i] != newObjects[i] ) - { - proceed = true; - break; - } - } - } - - // this should be set before firing the change events - // in case some clients end up calling this again. - previouslyDisplayedObjects = newObjects; - - DisplayGroupNode node; - Iterator i = childNodes.values().iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - if ( !node.isFetchNeeded() ) - { - node.updateDisplayedObjects(); - } - } - - if ( proceed ) - { + super.updateDisplayedObjects(); + + // diff lists + boolean proceed = true; + Object[] oldObjects = previouslyDisplayedObjects; + Object[] newObjects = displayedObjects.toArray(); + if (oldObjects.length == newObjects.length) { + proceed = false; + for (int i = 0; i < newObjects.length; i++) { + if (oldObjects[i] != newObjects[i]) { + proceed = true; + break; + } + } + } + + // this should be set before firing the change events + // in case some clients end up calling this again. + previouslyDisplayedObjects = newObjects; + + DisplayGroupNode node; + Iterator i = childNodes.values().iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + if (!node.isFetchNeeded()) { + node.updateDisplayedObjects(); + } + } + + if (proceed) { //System.out.println( "DisplayGroupNode.firingEventsForChanges: " ); //new RuntimeException().printStackTrace(); - fireEventsForChanges( oldObjects, newObjects ); - } - - } - - /** - * Called by processRecentChanges to analyze the - * differences between the lists and broadcast the - * appropriate events. - */ - protected void fireEventsForChanges( - Object[] oldObjects, Object[] newObjects ) - { - // structure changed causes havoc while - // establishing connection in some cases - //if ( oldObjects.length == 0 || newObjects.length == 0 ) - //{ - // fireStructureChanged( treePath().getPath(), null, null ); - // return; - //} - - int insertCount = 0; - int deleteCount = 0; - Object[] inserts = new Object[ newObjects.length ]; - Object[] deletes = new Object[ oldObjects.length ]; - - int i; - int n = -1, o = -1; // last match - int n1 = 0, o1 = 0; // current match test - int n2 = 0, o2 = 0; // scan ahead - - while ( o1 < oldObjects.length && n1 < newObjects.length ) - { - if ( newObjects[n1] == oldObjects[o1] ) - { - // mark as match and continue - o = o1; - n = n1; - } - else - { - // scan ahead for the next match, if any - o2 = o1; - n2 = n1; - - while ( o2 < oldObjects.length || n2 < newObjects.length ) - { - if ( o2 < oldObjects.length && newObjects[n1] == oldObjects[o2] ) - { - // run o1 to o2: mark as deletes - for ( i = o1; i < o2; i++ ) - { // System.out.println( "delete : " + i ); - deletes[i] = oldObjects[i]; - deleteCount++; - } - o1 = o2; // reset test - o = o1; // set match - n = n1; // set match - break; - } - if ( n2 < newObjects.length && newObjects[n2] == oldObjects[o1] ) - { - // run n1 to n2: mark as inserts - for ( i = n1; i < n2; i++ ) - { // System.out.println( "insert : " + i ); - inserts[i] = newObjects[i]; - insertCount++; - } - n1 = n2; // reset test - n = n1; // set match - o = o1; // set match - break; - } - o2++; - n2++; - } - } - if (n != n1) - { - inserts[n1] = newObjects[n1]; - insertCount++; - deletes[o1] = oldObjects[o1]; - deleteCount++; - //increment even though no match: - //the new object was marked as inserted and - //the old object was marked as deleted. - n = n1; - o = o1; - } - o1++; - n1++; - } - - // run o to end of oldObjects: mark as deletes - for ( i = o+1; i < oldObjects.length; i++ ) - { // System.out.println( "delete : " + i ); - deletes[i] = oldObjects[i]; - deleteCount++; - } - - // run n to end of newObjects: mark as inserts - for ( i = n+1; i < newObjects.length; i++ ) - { // System.out.println( "insert : " + i ); - inserts[i] = newObjects[i]; - insertCount++; - } + fireEventsForChanges(oldObjects, newObjects); + } + + } + + /** + * Called by processRecentChanges to analyze the differences between the lists + * and broadcast the appropriate events. + */ + protected void fireEventsForChanges(Object[] oldObjects, Object[] newObjects) { + // structure changed causes havoc while + // establishing connection in some cases + // if ( oldObjects.length == 0 || newObjects.length == 0 ) + // { + // fireStructureChanged( treePath().getPath(), null, null ); + // return; + // } + + int insertCount = 0; + int deleteCount = 0; + Object[] inserts = new Object[newObjects.length]; + Object[] deletes = new Object[oldObjects.length]; + + int i; + int n = -1, o = -1; // last match + int n1 = 0, o1 = 0; // current match test + int n2 = 0, o2 = 0; // scan ahead + + while (o1 < oldObjects.length && n1 < newObjects.length) { + if (newObjects[n1] == oldObjects[o1]) { + // mark as match and continue + o = o1; + n = n1; + } else { + // scan ahead for the next match, if any + o2 = o1; + n2 = n1; + + while (o2 < oldObjects.length || n2 < newObjects.length) { + if (o2 < oldObjects.length && newObjects[n1] == oldObjects[o2]) { + // run o1 to o2: mark as deletes + for (i = o1; i < o2; i++) { // System.out.println( "delete : " + i ); + deletes[i] = oldObjects[i]; + deleteCount++; + } + o1 = o2; // reset test + o = o1; // set match + n = n1; // set match + break; + } + if (n2 < newObjects.length && newObjects[n2] == oldObjects[o1]) { + // run n1 to n2: mark as inserts + for (i = n1; i < n2; i++) { // System.out.println( "insert : " + i ); + inserts[i] = newObjects[i]; + insertCount++; + } + n1 = n2; // reset test + n = n1; // set match + o = o1; // set match + break; + } + o2++; + n2++; + } + } + if (n != n1) { + inserts[n1] = newObjects[n1]; + insertCount++; + deletes[o1] = oldObjects[o1]; + deleteCount++; + // increment even though no match: + // the new object was marked as inserted and + // the old object was marked as deleted. + n = n1; + o = o1; + } + o1++; + n1++; + } + + // run o to end of oldObjects: mark as deletes + for (i = o + 1; i < oldObjects.length; i++) { // System.out.println( "delete : " + i ); + deletes[i] = oldObjects[i]; + deleteCount++; + } + + // run n to end of newObjects: mark as inserts + for (i = n + 1; i < newObjects.length; i++) { // System.out.println( "insert : " + i ); + inserts[i] = newObjects[i]; + insertCount++; + } //System.out.println( "done : " //+ o + " : " + o1 + " : " + o2 + " :: " + n + " : " + n1 + " : " + n2 ); @@ -948,571 +774,481 @@ Object[] previouslyDisplayedObjects = new Object[0]; //System.out.println( new NSArray( inserts ) ); //System.out.println( new NSArray( deletes ) ); //System.out.println( new NSArray( oldObjects ) ); - - int c; - Object[] nodes; - int[] indices; - - // broadcast delete event - c = 0; - nodes = new Object[ deleteCount ]; - indices = new int[ deleteCount ]; - for ( i = 0; i < deletes.length; i++ ) - { - if ( deletes[i] != null ) - { - indices[c] = i; - nodes[c] = getChildNodeForObject( deletes[i] ); - c++; - } - } - if ( c > 0 ) - { + + int c; + Object[] nodes; + int[] indices; + + // broadcast delete event + c = 0; + nodes = new Object[deleteCount]; + indices = new int[deleteCount]; + for (i = 0; i < deletes.length; i++) { + if (deletes[i] != null) { + indices[c] = i; + nodes[c] = getChildNodeForObject(deletes[i]); + c++; + } + } + if (c > 0) { // fireNodeChanged(); // force the jtree to get the correct child count - fireNodesRemoved( treePath().getPath(), indices, nodes ); - } - deletes = nodes; // retain for dispose check - - // broadcast insert event - c = 0; - nodes = new Object[ insertCount ]; - indices = new int[ insertCount ]; - for ( i = 0; i < inserts.length; i++ ) - { - if ( inserts[i] != null ) - { - indices[c] = i; - nodes[c] = getChildNodeForObject( inserts[i] ); - if ( nodes[c] == null ) - { - nodes[c] = createChildNodeForObject( newObjects[i] ); - } - c++; - } - } - if ( c > 0 ) - { - fireNodesInserted( treePath().getPath(), indices, nodes ); - } - - // dispose any delete nodes not on insert list - int j; - boolean found; - for ( i = 0; i < deletes.length; i++ ) - { - for ( j = 0; j < nodes.length; j++ ) - { - if ( deletes[i] == nodes[j] ) break; - } - - // did not break early, so not found, so dispose - if ( j == nodes.length ) - { - ((DisplayGroupNode)deletes[i]).dispose(); - } - } - } - - /** - * Sets the target object and creates an registers a target observer. - * If target was not previously null, the existing observer is unregistered. - * Protected access so subclasses and TreeModelAssociation can update our target. - */ - public void setTarget( Object aTarget ) - { - if ( target != null ) - { - EOObserverCenter.removeObserver( targetObserver, target ); - targetObserver.discardPendingNotification(); - } - - if ( aTarget != null ) - { - target = aTarget; - targetObserver = new TargetObserver( this ); - EOObserverCenter.addObserver( targetObserver, target ); - } - } - - /** - * Returns the parent display group, or null if parent is root. - */ - public DisplayGroupNode getParentGroup() - { - if ( parentGroup instanceof DisplayGroupNode ) - { - return (DisplayGroupNode)parentGroup; - } - // presumably the root node - return null; - } - - /** - * Gets all descendants of the this node. - */ - public List getDescendants() - { - return getDescendants( this, true ); - } - - /** - * Gets only the descendants of the this node - * whose children has been loaded - no fetching - * will occur. Useful for load-on-demand trees. - */ - public List getLoadedDescendants() - { - return getDescendants( this, false ); - } - - // breadth first traversal implementation - - /** - * Returns a list of all descendants of the - * specified node. Unfetched nodes are traversed - * only if forceLoad is true. - * This implementation is a breadth-first traversal - * of the nodes starting at the specified node. - */ - static private List getDescendants( DisplayGroupNode aNode, boolean forceLoad ) - { - if ( !forceLoad && !aNode.isFetched ) return NSArray.EmptyArray; - - LinkedList result = new LinkedList(); - LinkedList queue = new LinkedList(); - - queue.add( aNode ); - while ( ! queue.isEmpty() ) - { - checkNode( (DisplayGroupNode) queue.removeFirst(), - queue, result, forceLoad ); - } - - return result; - } - - /** - * Adds each fetched child node of the specified node to - * the result set (optionally forcing the child node to load) - * and adding child node to the end of the queue. - */ - static private void checkNode( DisplayGroupNode aNode, - LinkedList aQueue, LinkedList aResult, boolean forceLoad ) - { - DisplayGroupNode child; - int count = aNode.getChildCount(); - - for ( int i = 0; i < count; i++ ) - { - child = aNode.getChildNodeAt( i ); - - // add to queue if node has fetched children - if ( ( !child.isFetched ) && ( forceLoad ) ) - { - child.fetch(); - } - if ( child.isFetched ) - { - aQueue.addLast( child ); - } - - aResult.add( child ); - } - } - - /** - * Overridden to not fetch on InvalidateAllObjectsInStoreNotification - * unless we've already been fetched, preserving the load-on-demand - * functionality. - */ - public void objectsInvalidatedInEditingContext( NSNotification aNotification ) - { - if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification - .equals( aNotification.name() ) ) - { + fireNodesRemoved(treePath().getPath(), indices, nodes); + } + deletes = nodes; // retain for dispose check + + // broadcast insert event + c = 0; + nodes = new Object[insertCount]; + indices = new int[insertCount]; + for (i = 0; i < inserts.length; i++) { + if (inserts[i] != null) { + indices[c] = i; + nodes[c] = getChildNodeForObject(inserts[i]); + if (nodes[c] == null) { + nodes[c] = createChildNodeForObject(newObjects[i]); + } + c++; + } + } + if (c > 0) { + fireNodesInserted(treePath().getPath(), indices, nodes); + } + + // dispose any delete nodes not on insert list + int j; + boolean found; + for (i = 0; i < deletes.length; i++) { + for (j = 0; j < nodes.length; j++) { + if (deletes[i] == nodes[j]) + break; + } + + // did not break early, so not found, so dispose + if (j == nodes.length) { + ((DisplayGroupNode) deletes[i]).dispose(); + } + } + } + + /** + * Sets the target object and creates an registers a target observer. If target + * was not previously null, the existing observer is unregistered. Protected + * access so subclasses and TreeModelAssociation can update our target. + */ + public void setTarget(Object aTarget) { + if (target != null) { + EOObserverCenter.removeObserver(targetObserver, target); + targetObserver.discardPendingNotification(); + } + + if (aTarget != null) { + target = aTarget; + targetObserver = new TargetObserver(this); + EOObserverCenter.addObserver(targetObserver, target); + } + } + + /** + * Returns the parent display group, or null if parent is root. + */ + public DisplayGroupNode getParentGroup() { + if (parentGroup instanceof DisplayGroupNode) { + return (DisplayGroupNode) parentGroup; + } + // presumably the root node + return null; + } + + /** + * Gets all descendants of the this node. + */ + public List getDescendants() { + return getDescendants(this, true); + } + + /** + * Gets only the descendants of the this node whose children has been loaded - + * no fetching will occur. Useful for load-on-demand trees. + */ + public List getLoadedDescendants() { + return getDescendants(this, false); + } + + // breadth first traversal implementation + + /** + * Returns a list of all descendants of the specified node. Unfetched nodes are + * traversed only if forceLoad is true. This implementation is a breadth-first + * traversal of the nodes starting at the specified node. + */ + static private List getDescendants(DisplayGroupNode aNode, boolean forceLoad) { + if (!forceLoad && !aNode.isFetched) + return NSArray.EmptyArray; + + LinkedList result = new LinkedList(); + LinkedList queue = new LinkedList(); + + queue.add(aNode); + while (!queue.isEmpty()) { + checkNode((DisplayGroupNode) queue.removeFirst(), queue, result, forceLoad); + } + + return result; + } + + /** + * Adds each fetched child node of the specified node to the result set + * (optionally forcing the child node to load) and adding child node to the end + * of the queue. + */ + static private void checkNode(DisplayGroupNode aNode, LinkedList aQueue, LinkedList aResult, boolean forceLoad) { + DisplayGroupNode child; + int count = aNode.getChildCount(); + + for (int i = 0; i < count; i++) { + child = aNode.getChildNodeAt(i); + + // add to queue if node has fetched children + if ((!child.isFetched) && (forceLoad)) { + child.fetch(); + } + if (child.isFetched) { + aQueue.addLast(child); + } + + aResult.add(child); + } + } + + /** + * Overridden to not fetch on InvalidateAllObjectsInStoreNotification unless + * we've already been fetched, preserving the load-on-demand functionality. + */ + public void objectsInvalidatedInEditingContext(NSNotification aNotification) { + if (EOObjectStore.InvalidatedAllObjectsInStoreNotification.equals(aNotification.name())) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - else - if ( ( EOEditingContext.ObjectsChangedInEditingContextNotification - .equals( aNotification.name() ) ) - || ( EOEditingContext.EditingContextDidSaveChangesNotification - .equals( aNotification.name() ) ) ) - { - int index; - Enumeration e; - boolean didChange = false; - NSDictionary userInfo = aNotification.userInfo(); - - // if our target object was deleted - NSArray deletes = (NSArray) userInfo.objectForKey( - EOObjectStore.DeletedKey ); - if ( deletes.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } else if ((EOEditingContext.ObjectsChangedInEditingContextNotification.equals(aNotification.name())) + || (EOEditingContext.EditingContextDidSaveChangesNotification.equals(aNotification.name()))) { + int index; + Enumeration e; + boolean didChange = false; + NSDictionary userInfo = aNotification.userInfo(); + + // if our target object was deleted + NSArray deletes = (NSArray) userInfo.objectForKey(EOObjectStore.DeletedKey); + if (deletes.indexOfIdenticalObject(target) != NSArray.NotFound) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: delete: " + this + " : " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - - // if our target object was invalidated - NSArray invalidates = (NSArray) userInfo.objectForKey( - EOObjectStore.InvalidatedKey ); - if ( invalidates != null && - invalidates.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + + // if our target object was invalidated + NSArray invalidates = (NSArray) userInfo.objectForKey(EOObjectStore.InvalidatedKey); + if (invalidates != null && invalidates.indexOfIdenticalObject(target) != NSArray.NotFound) { //System.out.println( "DisplayGroupNode.objectsInvalidatedInEditingContext: invalidate: " + this + " : " + aNotification.name() ); - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - - // if our target object was updated, set fetchNeeded plus fire changed event - NSArray updates = (NSArray) userInfo.objectForKey( - EOObjectStore.UpdatedKey ); - if ( updates.indexOfIdenticalObject( target ) != NSArray.NotFound ) - { - if ( parentAssociation.isVisible( this ) && targetObserver != null ) - { - targetObserver.objectWillChange( target ); // force ui to update - fireNodeChanged(); - if ( object() instanceof Component ) ((Component)object()).repaint(); - } - else // make sure we fetch children when we do become visible - setFetchNeeded( true ); - return; - } - } - - super.objectsInvalidatedInEditingContext( aNotification ); - - } - - // inner classes - - /** - * Private class used to force a hashmap to - * perform key comparisons by reference. - */ - private class ReferenceKey - { - private int hashCode; - private Object referent; - - public ReferenceKey( 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 instanceof ReferenceKey ) - { - return ((ReferenceKey)anObject).referent == referent; - } - return false; - } - } - - /** - * A private class to observe the target object of this node. - */ - private class TargetObserver extends EODelayedObserver - { - Reference ref; - - /** - * Pass in the display group node that will be updated - * when the target changes. - */ - public TargetObserver( DisplayGroupNode aDisplayGroup ) - { - ref = new WeakReference( aDisplayGroup ); - } - - /** - * Repopulate our display group, and calculate the deltas - * so we can broadcast appropriate events. - */ - public void subjectChanged () - { - DisplayGroupNode node = (DisplayGroupNode) ref.get(); - if ( node == null ) return; // node is null if gc'd. - //FIXME: should un-register self from observer center?? - - node.targetChanged(); - } - } + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + + // if our target object was updated, set fetchNeeded plus fire changed event + NSArray updates = (NSArray) userInfo.objectForKey(EOObjectStore.UpdatedKey); + if (updates.indexOfIdenticalObject(target) != NSArray.NotFound) { + if (parentAssociation.isVisible(this) && targetObserver != null) { + targetObserver.objectWillChange(target); // force ui to update + fireNodeChanged(); + if (object() instanceof Component) + ((Component) object()).repaint(); + } else // make sure we fetch children when we do become visible + setFetchNeeded(true); + return; + } + } + + super.objectsInvalidatedInEditingContext(aNotification); + + } + + // inner classes + + /** + * Private class used to force a hashmap to perform key comparisons by + * reference. + */ + private class ReferenceKey { + private int hashCode; + private Object referent; + + public ReferenceKey(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 instanceof ReferenceKey) { + return ((ReferenceKey) anObject).referent == referent; + } + return false; + } + } + + /** + * A private class to observe the target object of this node. + */ + private class TargetObserver extends EODelayedObserver { + Reference ref; + + /** + * Pass in the display group node that will be updated when the target changes. + */ + public TargetObserver(DisplayGroupNode aDisplayGroup) { + ref = new WeakReference(aDisplayGroup); + } + + /** + * Repopulate our display group, and calculate the deltas so we can broadcast + * appropriate events. + */ + public void subjectChanged() { + DisplayGroupNode node = (DisplayGroupNode) ref.get(); + if (node == null) + return; // node is null if gc'd. + // FIXME: should un-register self from observer center?? + + node.targetChanged(); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.64 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.64 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.63 2003/06/06 14:20:07 mpowers - * getLoadedDescendants was forcing a fetch of the node it was called on. + * Revision 1.63 2003/06/06 14:20:07 mpowers getLoadedDescendants was forcing a + * fetch of the node it was called on. * - * Revision 1.62 2003/06/03 14:48:33 mpowers - * Clean-up of notification handling for updates/invalidation/etc. - * Now fetching immediately on notification if the node is visible. - * This averts the infamous IndexOutOfBoundsException that occurs - * if fetching happens during repaint, because the BasicTreeUI is - * caching the number of child nodes before painting begins. + * Revision 1.62 2003/06/03 14:48:33 mpowers Clean-up of notification handling + * for updates/invalidation/etc. Now fetching immediately on notification if the + * node is visible. This averts the infamous IndexOutOfBoundsException that + * occurs if fetching happens during repaint, because the BasicTreeUI is caching + * the number of child nodes before painting begins. * - * Revision 1.61 2003/01/18 23:33:29 mpowers - * Fixing the build. + * Revision 1.61 2003/01/18 23:33:29 mpowers Fixing the build. * - * Revision 1.60 2002/05/31 15:03:10 mpowers - * Fixes for the previous fix. Fat props to yjcheung. + * Revision 1.60 2002/05/31 15:03:10 mpowers Fixes for the previous fix. Fat + * props to yjcheung. * - * Revision 1.59 2002/05/28 15:31:36 mpowers - * Fix for updateDisplayedObjects for a subtle case where a node appears in - * the position that another node was moved from. + * Revision 1.59 2002/05/28 15:31:36 mpowers Fix for updateDisplayedObjects for + * a subtle case where a node appears in the position that another node was + * moved from. * - * Revision 1.58 2002/05/24 14:42:02 mpowers - * Prevent repeat events from firing if firing events loops back. + * Revision 1.58 2002/05/24 14:42:02 mpowers Prevent repeat events from firing + * if firing events loops back. * - * Revision 1.57 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.57 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.56 2002/04/19 21:18:45 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.56 2002/04/19 21:18:45 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.55 2002/04/19 20:53:22 mpowers - * Now firing event fewer events in fireEventsForChanges. + * Revision 1.55 2002/04/19 20:53:22 mpowers Now firing event fewer events in + * fireEventsForChanges. * - * Revision 1.54 2002/04/15 21:52:50 mpowers - * Tightening up TreeModelAssociation and DisplayGroupNode. - * Now only firing root structure changed once. - * Now disposing of root's children. - * Better event coalescing. + * Revision 1.54 2002/04/15 21:52:50 mpowers Tightening up TreeModelAssociation + * and DisplayGroupNode. Now only firing root structure changed once. Now + * disposing of root's children. Better event coalescing. * - * Revision 1.53 2002/04/12 20:35:20 mpowers - * Now correctly setting parent display group and data source on creation. + * Revision 1.53 2002/04/12 20:35:20 mpowers Now correctly setting parent + * display group and data source on creation. * - * Revision 1.52 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.52 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.51 2002/04/03 20:13:36 mpowers - * Now differentiating between node instantiation caused by model expansion - * (user initiated) and by modifications to the model. - * Dispose now disposes all children. + * Revision 1.51 2002/04/03 20:13:36 mpowers Now differentiating between node + * instantiation caused by model expansion (user initiated) and by modifications + * to the model. Dispose now disposes all children. * - * Revision 1.50 2002/03/23 16:20:27 mpowers - * Optimized processRecentChanges, minimized tree events. + * Revision 1.50 2002/03/23 16:20:27 mpowers Optimized processRecentChanges, + * minimized tree events. * - * Revision 1.49 2002/03/11 03:15:06 mpowers - * Optimized processRecentChanges, minimize event firing, coalescing changes. - * Still need a better diff algorithm to avoid removing nodes. + * Revision 1.49 2002/03/11 03:15:06 mpowers Optimized processRecentChanges, + * minimize event firing, coalescing changes. Still need a better diff algorithm + * to avoid removing nodes. * - * Revision 1.48 2002/03/10 00:59:39 mpowers - * Interim version: coalesces calls to process recent changes. - * Still does not handle rearranged nodes. + * Revision 1.48 2002/03/10 00:59:39 mpowers Interim version: coalesces calls to + * process recent changes. Still does not handle rearranged nodes. * - * Revision 1.47 2002/03/09 17:33:45 mpowers - * Nodes now track their child nodes by reference, not index. + * Revision 1.47 2002/03/09 17:33:45 mpowers Nodes now track their child nodes + * by reference, not index. * - * Revision 1.46 2002/03/08 23:19:07 mpowers - * Added getParentGroup to DisplayGroupNode. + * Revision 1.46 2002/03/08 23:19:07 mpowers Added getParentGroup to + * DisplayGroupNode. * - * Revision 1.45 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.45 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.44 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.44 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.42 2002/02/19 22:28:46 mpowers - * DisplayGroupNodes immediately unregister themselves as editors. + * Revision 1.42 2002/02/19 22:28:46 mpowers DisplayGroupNodes immediately + * unregister themselves as editors. * - * Revision 1.41 2002/02/13 16:27:38 mpowers - * Exposing setTarget. + * Revision 1.41 2002/02/13 16:27:38 mpowers Exposing setTarget. * - * Revision 1.40 2001/11/02 20:55:46 mpowers - * Now using fixed index to send node removed events. This preserves the - * expanded state of the nodes in the corresponding jtree. + * Revision 1.40 2001/11/02 20:55:46 mpowers Now using fixed index to send node + * removed events. This preserves the expanded state of the nodes in the + * corresponding jtree. * - * Revision 1.39 2001/09/21 21:09:25 mpowers - * Exposed more fields as protected. + * Revision 1.39 2001/09/21 21:09:25 mpowers Exposed more fields as protected. * - * Revision 1.38 2001/09/19 15:36:08 mpowers - * Refined behavior for isFetched after notification handling. + * Revision 1.38 2001/09/19 15:36:08 mpowers Refined behavior for isFetched + * after notification handling. * - * Revision 1.37 2001/09/13 14:51:18 mpowers - * DisplayGroupNodes now dispose themselves and mark their parent for update - * when they receive notification that their target has been deleted. + * Revision 1.37 2001/09/13 14:51:18 mpowers DisplayGroupNodes now dispose + * themselves and mark their parent for update when they receive notification + * that their target has been deleted. * - * Revision 1.36 2001/09/10 14:10:24 mpowers - * Fix for notification handling. + * Revision 1.36 2001/09/10 14:10:24 mpowers Fix for notification handling. * - * Revision 1.35 2001/07/30 16:17:01 mpowers - * Minor code cleanup. + * Revision 1.35 2001/07/30 16:17:01 mpowers Minor code cleanup. * - * Revision 1.34 2001/07/18 22:13:39 mpowers - * getLoadedDescendants now works as advertised. - * Now correctly handling invalidateAllObjects notification. + * Revision 1.34 2001/07/18 22:13:39 mpowers getLoadedDescendants now works as + * advertised. Now correctly handling invalidateAllObjects notification. * - * Revision 1.33 2001/07/18 13:03:32 mpowers - * TreeNodes now refetch only on demand. Previously, once a node had - * been fetched, it was always refetched after an invalidate, even if - * the node was not being displayed. + * Revision 1.33 2001/07/18 13:03:32 mpowers TreeNodes now refetch only on + * demand. Previously, once a node had been fetched, it was always refetched + * after an invalidate, even if the node was not being displayed. * - * Revision 1.32 2001/06/18 14:10:28 mpowers - * Cleaned up event firing: no longer firing insert or remove events twice. + * Revision 1.32 2001/06/18 14:10:28 mpowers Cleaned up event firing: no longer + * firing insert or remove events twice. * - * Revision 1.31 2001/06/09 16:15:39 mpowers - * Revised the targetChanged scheme because oldObjects and newObjects were - * identical after the target object is invalidated. + * Revision 1.31 2001/06/09 16:15:39 mpowers Revised the targetChanged scheme + * because oldObjects and newObjects were identical after the target object is + * invalidated. * - * Revision 1.30 2001/05/21 22:17:19 mpowers - * Fix for tree out-of-synch problems when nodes are inserted. + * Revision 1.30 2001/05/21 22:17:19 mpowers Fix for tree out-of-synch problems + * when nodes are inserted. * - * Revision 1.29 2001/05/18 21:07:46 mpowers - * Playing with refresh options. + * Revision 1.29 2001/05/18 21:07:46 mpowers Playing with refresh options. * - * Revision 1.28 2001/05/14 15:25:43 mpowers - * DisplayGroupNodes now only respond to InvalidateAllObjectsInStore - * if they are already fetched. + * Revision 1.28 2001/05/14 15:25:43 mpowers DisplayGroupNodes now only respond + * to InvalidateAllObjectsInStore if they are already fetched. * - * Revision 1.27 2001/05/08 19:55:58 mpowers - * Fix for node children not refreshing after sibling was inserted. + * Revision 1.27 2001/05/08 19:55:58 mpowers Fix for node children not + * refreshing after sibling was inserted. * - * Revision 1.26 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.26 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.25 2001/05/06 22:22:55 mpowers - * Debugging. + * Revision 1.25 2001/05/06 22:22:55 mpowers Debugging. * - * Revision 1.24 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.24 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.23 2001/05/02 18:00:43 mpowers - * Removed debug code. + * Revision 1.23 2001/05/02 18:00:43 mpowers Removed debug code. * - * Revision 1.22 2001/05/02 17:31:20 mpowers - * DisplayGroupNode now does a better job determining when to mark its - * parent dirty. + * Revision 1.22 2001/05/02 17:31:20 mpowers DisplayGroupNode now does a better + * job determining when to mark its parent dirty. * - * Revision 1.21 2001/05/01 00:52:32 mpowers - * Implemented breadth-first traversal of tree for node. + * Revision 1.21 2001/05/01 00:52:32 mpowers Implemented breadth-first traversal + * of tree for node. * - * Revision 1.20 2001/04/26 01:15:19 mpowers - * Major clean-up of DisplayGroupNode: fitter, happier, more productive. + * Revision 1.20 2001/04/26 01:15:19 mpowers Major clean-up of DisplayGroupNode: + * fitter, happier, more productive. * - * Revision 1.19 2001/04/22 23:13:35 mpowers - * Minor bug. + * Revision 1.19 2001/04/22 23:13:35 mpowers Minor bug. * - * Revision 1.18 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.18 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.17 2001/04/21 23:05:12 mpowers - * A fairly major revisiting. I've decided to scrap the pass-thru approach - * where every node simply represents an index and not an object. - * The next update will have each node correspond to a specific object. + * Revision 1.17 2001/04/21 23:05:12 mpowers A fairly major revisiting. I've + * decided to scrap the pass-thru approach where every node simply represents an + * index and not an object. The next update will have each node correspond to a + * specific object. * - * Revision 1.16 2001/04/13 16:37:37 mpowers - * Handling bounds checking. + * Revision 1.16 2001/04/13 16:37:37 mpowers Handling bounds checking. * - * Revision 1.15 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.15 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.14 2001/03/27 17:45:51 mpowers - * More index bounds checking. + * Revision 1.14 2001/03/27 17:45:51 mpowers More index bounds checking. * - * Revision 1.13 2001/03/22 21:25:42 mpowers - * Fixed some nasty issues with jtree's internal state and array bounds. + * Revision 1.13 2001/03/22 21:25:42 mpowers Fixed some nasty issues with + * jtree's internal state and array bounds. * - * Revision 1.12 2001/03/19 22:18:58 mpowers - * Root node now mirrors contents of titles display group. + * Revision 1.12 2001/03/19 22:18:58 mpowers Root node now mirrors contents of + * titles display group. * - * Revision 1.11 2001/03/19 21:38:36 mpowers - * Improved redisplay after edit. Editing nodes off root now works. + * Revision 1.11 2001/03/19 21:38:36 mpowers Improved redisplay after edit. + * Editing nodes off root now works. * - * Revision 1.10 2001/03/09 22:08:38 mpowers - * Removed unused line. + * Revision 1.10 2001/03/09 22:08:38 mpowers Removed unused line. * - * Revision 1.9 2001/03/07 16:41:04 mpowers - * Now checking size of parent displayed objects array so that we don't - * get array out of bounds execeptions from isLeaf() or object() when - * those messages are called after the TreeAssociation fires a - * nodesDeleted event. I believe that JTree is mistakenly rendering - * those nodes one last time before erasing them. + * Revision 1.9 2001/03/07 16:41:04 mpowers Now checking size of parent + * displayed objects array so that we don't get array out of bounds execeptions + * from isLeaf() or object() when those messages are called after the + * TreeAssociation fires a nodesDeleted event. I believe that JTree is + * mistakenly rendering those nodes one last time before erasing them. * - * Revision 1.8 2001/03/06 23:21:27 mpowers - * Now only notifying parent if the object is not registered in the - * editing context, if any. + * Revision 1.8 2001/03/06 23:21:27 mpowers Now only notifying parent if the + * object is not registered in the editing context, if any. * - * Revision 1.7 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.7 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.6 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.6 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.5 2001/01/31 17:59:52 mpowers - * Fixed isLeaf aspect of TreeAssociation. + * Revision 1.5 2001/01/31 17:59:52 mpowers Fixed isLeaf aspect of + * TreeAssociation. * - * Revision 1.4 2001/01/25 02:16:25 mpowers - * TreeAssociation now returns DisplayGroupNode.getUserObject. + * Revision 1.4 2001/01/25 02:16:25 mpowers TreeAssociation now returns + * DisplayGroupNode.getUserObject. * - * Revision 1.3 2001/01/24 18:14:40 mpowers - * Fixed problem with leaving children aspect unspecified. + * Revision 1.3 2001/01/24 18:14:40 mpowers Fixed problem with leaving children + * aspect unspecified. * - * Revision 1.2 2001/01/24 16:35:37 mpowers - * Improved documentation on TreeAssociation. - * SortOrderings are now inherited from parent nodes. - * Updates after sorting are still lost on TreeController. + * Revision 1.2 2001/01/24 16:35:37 mpowers Improved documentation on + * TreeAssociation. SortOrderings are now inherited from parent nodes. Updates + * after sorting are still lost on TreeController. * - * Revision 1.1 2001/01/24 14:17:12 mpowers - * Major revision to TreeAssociation. Can now add and remove nodes. - * DisplayGroupNode is now it's own class. + * Revision 1.1 2001/01/24 14:17:12 mpowers Major revision to TreeAssociation. + * Can now add and remove nodes. DisplayGroupNode is now it's own class. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java index ec22bfd..440765c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ListAssociation.java @@ -34,206 +34,166 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TextAssociation binds a JList to a display group's -* list of displayable objects. Bindings are: -*
    -*
  • titles: a property convertable to a string for -* display in the cells of the list
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ListAssociation extends EOAssociation - implements ListSelectionListener, - ListDataListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "items" - } ); - - protected DefaultListModel model; - - /** - * Constructor expecting a JList. Throws an - * exception if it doesn't receive one. - * Note: This sets the JList's model to a DefaultListModel. - */ - public ListAssociation ( Object anObject ) - { - super( anObject ); - model = new DefaultListModel(); - ((JList)anObject).setModel( model ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JList ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return TitlesAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - addAsListener(); - super.establishConnection(); - populateFromDisplayGroup(); - selectFromDisplayGroup(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { // System.out.println( "ListAssociation.addAsListener: " + ++count ); - component().getModel().addListDataListener( this ); - component().addListSelectionListener( this ); - } - - protected void removeAsListener() - { // System.out.println( "ListAssociation.removeAsListener: " + --count ); - component().getModel().removeListDataListener( this ); - component().removeListSelectionListener( this ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + * TextAssociation binds a JList to a display group's list of displayable + * objects. Bindings are: + *
    + *
  • titles: a property convertable to a string for display in the cells of + * the list
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ListAssociation extends EOAssociation implements ListSelectionListener, ListDataListener { + static final NSArray aspects = new NSArray(new Object[] { TitlesAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "items" }); + + protected DefaultListModel model; + + /** + * Constructor expecting a JList. Throws an exception if it doesn't receive one. + * Note: This sets the JList's model to a DefaultListModel. + */ + public ListAssociation(Object anObject) { + super(anObject); + model = new DefaultListModel(); + ((JList) anObject).setModel(model); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JList); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return TitlesAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + addAsListener(); + super.establishConnection(); + populateFromDisplayGroup(); + selectFromDisplayGroup(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { // System.out.println( "ListAssociation.addAsListener: " + ++count ); + component().getModel().addListDataListener(this); + component().addListSelectionListener(this); + } + + protected void removeAsListener() { // System.out.println( "ListAssociation.removeAsListener: " + --count ); + component().getModel().removeListDataListener(this); + component().removeListSelectionListener(this); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { EODisplayGroup displayGroup; - + // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { //System.out.println( "subjectChanged: " + //displayGroup.contentsChanged() + " : " + displayGroup.selectionChanged() //+ " : " + displayGroup.updatedObjectIndex() ); //new net.wotonomy.ui.swing.util.StackTraceInspector(); - if ( displayGroup.contentsChanged() ) - { + if (displayGroup.contentsChanged()) { populateFromDisplayGroup(); } - if ( displayGroup.selectionChanged() ) - { + if (displayGroup.selectionChanged()) { selectFromDisplayGroup(); } } - } - - private void populateFromDisplayGroup() - { + } + + private void populateFromDisplayGroup() { JList component = component(); - EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect ); - String key = displayGroupKeyForAspect( TitlesAspect ); - + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + String key = displayGroupKeyForAspect(TitlesAspect); + removeAsListener(); - + // remember selection int[] selectedIndices = component().getSelectedIndices(); - + // clear the model model.removeAllElements(); @@ -241,128 +201,111 @@ public class ListAssociation extends EOAssociation Object value; int size = displayGroup.displayedObjects().count(); //System.out.println( "populateFromDisplayGroup: " + size ); - for ( int i = 0; i < size; i++ ) - { - value = displayGroup.valueForObjectAtIndex( i, key ); - if ( value == null ) value = "[null]"; - model.addElement( value ); + for (int i = 0; i < size; i++) { + value = displayGroup.valueForObjectAtIndex(i, key); + if (value == null) + value = "[null]"; + model.addElement(value); } - + // select the same indexes - for ( int i = 0; i < selectedIndices.length; i++ ) - { - component.addSelectionInterval( - selectedIndices[i], selectedIndices[i] ); // adds one row + for (int i = 0; i < selectedIndices.length; i++) { + component.addSelectionInterval(selectedIndices[i], selectedIndices[i]); // adds one row } addAsListener(); } - - private void selectFromDisplayGroup() - { + + private void selectFromDisplayGroup() { JList component = component(); - EODisplayGroup displayGroup = displayGroupForAspect( TitlesAspect ); + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); removeAsListener(); - + int index; component.clearSelection(); - Enumeration e = - displayGroup.selectionIndexes().objectEnumerator(); - - while ( e.hasMoreElements() ) - { // add selections one-by-one to support non-contiguous - index = ((Number)e.nextElement()).intValue(); - component.addSelectionInterval( - index, index ); // adds one row + Enumeration e = displayGroup.selectionIndexes().objectEnumerator(); + + while (e.hasMoreElements()) { // add selections one-by-one to support non-contiguous + index = ((Number) e.nextElement()).intValue(); + component.addSelectionInterval(index, index); // adds one row } - + addAsListener(); } - + // interface ListSelectionListener - public void valueChanged(ListSelectionEvent e) - { - final EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( ( displayGroup != null ) && ( ! e.getValueIsAdjusting() ) ) - { - int[] selectedIndices = component().getSelectedIndices(); + public void valueChanged(ListSelectionEvent e) { + final EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if ((displayGroup != null) && (!e.getValueIsAdjusting())) { + int[] selectedIndices = component().getSelectedIndices(); final NSMutableArray indexList = new NSMutableArray(); - for ( int i = 0; i < selectedIndices.length; i++ ) - { - indexList.addObject( new Integer( selectedIndices[i] ) ); + for (int i = 0; i < selectedIndices.length; i++) { + indexList.addObject(new Integer(selectedIndices[i])); } - - // invoke later so the component is repainted before - // any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - SwingUtilities.invokeLater( new Runnable() { - public void run() - { - displayGroup.setSelectionIndexes( indexList ); - } - }); - } - } - - // interface ListDataListener - - public void intervalAdded(ListDataEvent e) - { - // System.out.println( "intervalAdded" ); - contentsChanged(e); - } - public void intervalRemoved(ListDataEvent e) - { - // System.out.println( "intervalRemoved" ); - contentsChanged(e); - } - public void contentsChanged(ListDataEvent e) - { - // System.out.println( "contentsChanged" ); - - // if we were editing a property, + + // invoke later so the component is repainted before + // any potentially lengthy second-order effects happen: + // this improves user-perceived responsiveness of big apps + SwingUtilities.invokeLater(new Runnable() { + public void run() { + displayGroup.setSelectionIndexes(indexList); + } + }); + } + } + + // interface ListDataListener + + public void intervalAdded(ListDataEvent e) { + // System.out.println( "intervalAdded" ); + contentsChanged(e); + } + + public void intervalRemoved(ListDataEvent e) { + // System.out.println( "intervalRemoved" ); + contentsChanged(e); + } + + public void contentsChanged(ListDataEvent e) { + // System.out.println( "contentsChanged" ); + + // if we were editing a property, // we'd notify our display group now. - } - + } + // convenience - private JList component() - { - return (JList) object(); - } + private JList component() { + return (JList) object(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/05/15 14:05:55 mpowers - * Now appropriately selectingFromDisplayGroup on establishConnection. + * Revision 1.4 2002/05/15 14:05:55 mpowers Now appropriately + * selectingFromDisplayGroup on establishConnection. * - * Revision 1.3 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.3 2001/09/14 13:40:26 mpowers User-initiated selection changes are + * now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:48:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:49 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java index f1568ec..5293bc9 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/MutableDisplayGroupNode.java @@ -32,185 +32,143 @@ import net.wotonomy.foundation.internal.WotonomyException; import net.wotonomy.ui.EODisplayGroup; /** -* A DisplayGroupNode that exposes the MutableTreeNode interface. -* This was required so that other subclasses of DisplayGroupNode -* could opt out of supporting MutableTreeNode (so that they can -* implement IlvActivity, for example). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ - public class MutableDisplayGroupNode - extends DisplayGroupNode implements MutableTreeNode - { - /** - * Constructor for all nodes. - * Root node must have a null delegate. - */ - public MutableDisplayGroupNode( - TreeModelAssociation aParentAssociation, - EODisplayGroup aParentGroup, - Object anObject ) - { - super( aParentAssociation, aParentGroup, anObject ); - } - - public int getIndex(TreeNode node) - { - return getIndex( (DisplayGroupNode) node ); - } - - public TreeNode getChildAt(int childIndex) - { - return (TreeNode) getChildNodeAt( childIndex ); - } - - public TreeNode getParent() - { - Object parent = getParentGroup(); - if ( parent instanceof TreeNode ) - { - return (TreeNode) parent; - } - return null; - } - - public void insert(MutableTreeNode aChild, int anIndex) - { - if ( aChild instanceof DisplayGroupNode ) - { - insertObjectAtIndex( - ((DisplayGroupNode)aChild).object(), anIndex ); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot insert nodes of type: " + aChild ); - } - } - - /** - * Removes the node at the index corresponding - * to the index of the object. - */ - public void remove(MutableTreeNode node) - { - if ( node instanceof DisplayGroupNode ) - { - remove((DisplayGroupNode)node); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot insert nodes of type: " + node ); - } - } - - /** - * Removes the value in the parent display group - * at the index that corresponds to the index of this node - * and add it to the end of the display group that corresponds - * to the user value of the specified node. - */ - public void setParent(MutableTreeNode newParent) - { - if ( newParent instanceof DisplayGroupNode ) - { - setParent((DisplayGroupNode)newParent); - } - else // not a display group node - { - throw new WotonomyException( - "Cannot set parent to nodes of type: " + newParent ); - } - } - - /** - * Overridden to remember expanded state for nodes - * after nodes have been rearranged. - */ - protected void fireEventsForChanges( - Object[] oldObjects, Object[] newObjects ) - { - if ( !( parentAssociation.object() instanceof JTree ) ) - { - super.fireEventsForChanges( oldObjects, newObjects ); - return; - } - - JTree tree = (JTree) parentAssociation.object(); - Map expansionMap = new HashMap(); - DisplayGroupNode node; - TreePath path; - for ( int i = 0; i < oldObjects.length; i++ ) - { - node = (DisplayGroupNode) - getChildNodeForObject( oldObjects[i] ); - if ( node != null && ! node.isLeaf() ) - { - expansionMap.put( node, new Boolean( - tree.isExpanded( node.treePath() ) ) ); - } - } - - super.fireEventsForChanges( oldObjects, newObjects ); - - Object value; - Iterator iterator = new LinkedList( childNodes.values() ).iterator(); - while ( iterator.hasNext() ) - { - node = (DisplayGroupNode) iterator.next(); - value = expansionMap.get( node ); - if ( value != null ) - { - if ( Boolean.TRUE.equals( value ) ) - { - tree.expandPath( node.treePath() ); - } - else - { - tree.collapsePath( node.treePath() ); - } - } - } - } - } + * A DisplayGroupNode that exposes the MutableTreeNode interface. This was + * required so that other subclasses of DisplayGroupNode could opt out of + * supporting MutableTreeNode (so that they can implement IlvActivity, for + * example). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class MutableDisplayGroupNode extends DisplayGroupNode implements MutableTreeNode { + /** + * Constructor for all nodes. Root node must have a null delegate. + */ + public MutableDisplayGroupNode(TreeModelAssociation aParentAssociation, EODisplayGroup aParentGroup, + Object anObject) { + super(aParentAssociation, aParentGroup, anObject); + } + + public int getIndex(TreeNode node) { + return getIndex((DisplayGroupNode) node); + } + + public TreeNode getChildAt(int childIndex) { + return (TreeNode) getChildNodeAt(childIndex); + } + + public TreeNode getParent() { + Object parent = getParentGroup(); + if (parent instanceof TreeNode) { + return (TreeNode) parent; + } + return null; + } + + public void insert(MutableTreeNode aChild, int anIndex) { + if (aChild instanceof DisplayGroupNode) { + insertObjectAtIndex(((DisplayGroupNode) aChild).object(), anIndex); + } else // not a display group node + { + throw new WotonomyException("Cannot insert nodes of type: " + aChild); + } + } + + /** + * Removes the node at the index corresponding to the index of the object. + */ + public void remove(MutableTreeNode node) { + if (node instanceof DisplayGroupNode) { + remove((DisplayGroupNode) node); + } else // not a display group node + { + throw new WotonomyException("Cannot insert nodes of type: " + node); + } + } + + /** + * Removes the value in the parent display group at the index that corresponds + * to the index of this node and add it to the end of the display group that + * corresponds to the user value of the specified node. + */ + public void setParent(MutableTreeNode newParent) { + if (newParent instanceof DisplayGroupNode) { + setParent((DisplayGroupNode) newParent); + } else // not a display group node + { + throw new WotonomyException("Cannot set parent to nodes of type: " + newParent); + } + } + + /** + * Overridden to remember expanded state for nodes after nodes have been + * rearranged. + */ + protected void fireEventsForChanges(Object[] oldObjects, Object[] newObjects) { + if (!(parentAssociation.object() instanceof JTree)) { + super.fireEventsForChanges(oldObjects, newObjects); + return; + } + + JTree tree = (JTree) parentAssociation.object(); + Map expansionMap = new HashMap(); + DisplayGroupNode node; + TreePath path; + for (int i = 0; i < oldObjects.length; i++) { + node = (DisplayGroupNode) getChildNodeForObject(oldObjects[i]); + if (node != null && !node.isLeaf()) { + expansionMap.put(node, new Boolean(tree.isExpanded(node.treePath()))); + } + } + + super.fireEventsForChanges(oldObjects, newObjects); + + Object value; + Iterator iterator = new LinkedList(childNodes.values()).iterator(); + while (iterator.hasNext()) { + node = (DisplayGroupNode) iterator.next(); + value = expansionMap.get(node); + if (value != null) { + if (Boolean.TRUE.equals(value)) { + tree.expandPath(node.treePath()); + } else { + tree.collapsePath(node.treePath()); + } + } + } + } +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.8 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.7 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.7 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.6 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.6 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.5 2002/04/03 20:01:47 mpowers - * Now remembers expanded state. + * Revision 1.5 2002/04/03 20:01:47 mpowers Now remembers expanded state. * - * Revision 1.4 2002/03/08 23:19:07 mpowers - * Added getParentGroup to DisplayGroupNode. + * Revision 1.4 2002/03/08 23:19:07 mpowers Added getParentGroup to + * DisplayGroupNode. * - * Revision 1.3 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.3 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.2 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.2 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.1 2001/04/21 23:05:56 mpowers - * Contributing the tree-specific concrete subclass of DisplayGroupNode. + * Revision 1.1 2001/04/21 23:05:56 mpowers Contributing the tree-specific + * concrete subclass of DisplayGroupNode. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java index d105d89..4f65672 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/NotificationInspector.java @@ -49,285 +49,252 @@ import net.wotonomy.ui.swing.util.WindowUtilities; import net.wotonomy.control.internal.Surrogate; /** -* The NotificationInspector displays a JFrame that -* displays notifications as they occur.

-* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ - -public class NotificationInspector - implements MouseListener, ActionListener -{ - protected JTable table; - protected JPopupMenu popupMenu; - protected EODisplayGroup displayGroup; - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; - - protected static final String CLEAR_ALL = "Clear All"; - protected static final String CLEAR_SELECTED = "Clear Selected"; - - -/** -* Displays all notifications on the default notification center. -*/ - public NotificationInspector() - { - this( NSNotificationCenter.defaultCenter() ); - } - -/** -* Displays all notifications from the specified notification center. -*/ - public NotificationInspector( NSNotificationCenter aCenter ) - { - this( aCenter, null, null ); - } - -/** -* Displays notifications from the default notification center -* using the specified name and object filters. -*/ - public NotificationInspector( String notificationName, Object anObject ) - { - this( NSNotificationCenter.defaultCenter(), - notificationName, anObject ); - } + * The NotificationInspector displays a JFrame that displays notifications as + * they occur.
+ *
+ * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -/** -* Displays notifications on the specified notification center -* using the specified name and object filters. -*/ - public NotificationInspector( NSNotificationCenter aCenter, - String notificationName, Object anObject ) - { - // show stack traces - NSNotification.showStack = true; - - // register for notifications - NSSelector handleNotification = - new NSSelector( "handleNotification", - new Class[] { NSNotification.class } ); - aCenter.addObserver( - this, handleNotification, notificationName, anObject ); - - table = new JTable(); - - popupMenu = new JPopupMenu(); - JMenuItem menuItem = popupMenu.add( CLEAR_SELECTED ); - menuItem.addActionListener( this ); - menuItem = popupMenu.add( CLEAR_ALL ); - menuItem.addActionListener( this ); - - displayGroup = new EODisplayGroup(); - - TableColumn column; - TableColumnAssociation assoc; - - column = new TableColumn(); - column.setHeaderValue( "Time" ); - column.setCellRenderer( new FormattedCellRenderer( new SimpleDateFormat( "hh:mm:ss:SS" ) ) ); - column.setPreferredWidth( 90 ); - column.setMaxWidth( 90 ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "time" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Type" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "name" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Object" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "objectString" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Info" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "userInfoString" ); - assoc.setTable( table ); - assoc.establishConnection(); - - initLayout(); - } - - protected void initLayout() - { - table.addMouseListener( this ); // listen for double-clicks - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - //scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Notification Inspector" ); - window.getContentPane().add( panel ); - - //window.pack(); - window.setSize( 800, 400 ); - WindowUtilities.cascade( window ); - - // size the columns. +public class NotificationInspector implements MouseListener, ActionListener { + protected JTable table; + protected JPopupMenu popupMenu; + protected EODisplayGroup displayGroup; + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + protected static final String CLEAR_ALL = "Clear All"; + protected static final String CLEAR_SELECTED = "Clear Selected"; + + /** + * Displays all notifications on the default notification center. + */ + public NotificationInspector() { + this(NSNotificationCenter.defaultCenter()); + } + + /** + * Displays all notifications from the specified notification center. + */ + public NotificationInspector(NSNotificationCenter aCenter) { + this(aCenter, null, null); + } + + /** + * Displays notifications from the default notification center using the + * specified name and object filters. + */ + public NotificationInspector(String notificationName, Object anObject) { + this(NSNotificationCenter.defaultCenter(), notificationName, anObject); + } + + /** + * Displays notifications on the specified notification center using the + * specified name and object filters. + */ + public NotificationInspector(NSNotificationCenter aCenter, String notificationName, Object anObject) { + // show stack traces + NSNotification.showStack = true; + + // register for notifications + NSSelector handleNotification = new NSSelector("handleNotification", new Class[] { NSNotification.class }); + aCenter.addObserver(this, handleNotification, notificationName, anObject); + + table = new JTable(); + + popupMenu = new JPopupMenu(); + JMenuItem menuItem = popupMenu.add(CLEAR_SELECTED); + menuItem.addActionListener(this); + menuItem = popupMenu.add(CLEAR_ALL); + menuItem.addActionListener(this); + + displayGroup = new EODisplayGroup(); + + TableColumn column; + TableColumnAssociation assoc; + + column = new TableColumn(); + column.setHeaderValue("Time"); + column.setCellRenderer(new FormattedCellRenderer(new SimpleDateFormat("hh:mm:ss:SS"))); + column.setPreferredWidth(90); + column.setMaxWidth(90); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "time"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Type"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "name"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Object"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "objectString"); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Info"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "userInfoString"); + assoc.setTable(table); + assoc.establishConnection(); + + initLayout(); + } + + protected void initLayout() { + table.addMouseListener(this); // listen for double-clicks + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + // scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle("Notification Inspector"); + window.getContentPane().add(panel); + + // window.pack(); + window.setSize(800, 400); + WindowUtilities.cascade(window); + + // size the columns. // table.getColumnModel().getColumn( 0 ).setPreferredWidth( 100 ); - window.show(); - } - -/** -* Handles the notification. -*/ - public void handleNotification( NSNotification aNotification ) - { - Surrogate s = new Surrogate( new Object[] { aNotification } ); - s.directPut( "time", new Date() ); - s.directPut( "objectString", ""+aNotification.object() ); // snapshot of state - s.directPut( "userInfoString", ""+aNotification.userInfo() ); // snapshot of info - displayGroup.insertObjectAtIndex( s, 0 ); - } - - // interface ActionListener - - /** - * Method used to listen for the action event from the popup menu items. - */ - public void actionPerformed( ActionEvent e ) - { - if ( CLEAR_SELECTED.equals( e.getActionCommand() ) ) - { - NSMutableArray objects = new NSMutableArray( displayGroup.allObjects() ); - objects.removeAll( displayGroup.selectedObjects() ); - displayGroup.setObjectArray( objects ); - displayGroup.updateDisplayedObjects(); - } - else if ( CLEAR_ALL.equals( e.getActionCommand() ) ) - { - displayGroup.setObjectArray( null ); - displayGroup.updateDisplayedObjects(); - } - } - - // interface MouseListener - - /** - * Double click to launch object inspector. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - col = table.convertColumnIndexToModel( col ); - - if ( row == -1 ) return; - - if ( col == 0 ) // time - { - new StackTraceInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).stackTrace() ); - } - else - if ( col == 2 ) // object - { - new ObjectInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).object() ); - } - else - if ( col == 3 ) // info - { - new ObjectInspector( ( (NSNotification) ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ).userInfo() ); - } - else - { - new ObjectInspector( ( (Surrogate) - displayGroup.displayedObjects().objectAtIndex( row ) ).getDelegate() ); - } - } - else - { - // Click count is 1 then, check for popup trigger. - if ( e.isPopupTrigger() ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - } - - public void mouseReleased(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.isPopupTrigger() && ( e.getClickCount() == 1 ) ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - - public void mousePressed(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.isPopupTrigger() && ( e.getClickCount() == 1 ) ) - { - popupMenu.show( table, e.getX(), e.getY() ); - } - } - } - - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} + window.show(); + } + + /** + * Handles the notification. + */ + public void handleNotification(NSNotification aNotification) { + Surrogate s = new Surrogate(new Object[] { aNotification }); + s.directPut("time", new Date()); + s.directPut("objectString", "" + aNotification.object()); // snapshot of state + s.directPut("userInfoString", "" + aNotification.userInfo()); // snapshot of info + displayGroup.insertObjectAtIndex(s, 0); + } + + // interface ActionListener + + /** + * Method used to listen for the action event from the popup menu items. + */ + public void actionPerformed(ActionEvent e) { + if (CLEAR_SELECTED.equals(e.getActionCommand())) { + NSMutableArray objects = new NSMutableArray(displayGroup.allObjects()); + objects.removeAll(displayGroup.selectedObjects()); + displayGroup.setObjectArray(objects); + displayGroup.updateDisplayedObjects(); + } else if (CLEAR_ALL.equals(e.getActionCommand())) { + displayGroup.setObjectArray(null); + displayGroup.updateDisplayedObjects(); + } + } + + // interface MouseListener + + /** + * Double click to launch object inspector. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + col = table.convertColumnIndexToModel(col); + + if (row == -1) + return; + + if (col == 0) // time + { + new StackTraceInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).stackTrace()); + } else if (col == 2) // object + { + new ObjectInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).object()); + } else if (col == 3) // info + { + new ObjectInspector( + ((NSNotification) ((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)) + .getDelegate()).userInfo()); + } else { + new ObjectInspector(((Surrogate) displayGroup.displayedObjects().objectAtIndex(row)).getDelegate()); + } + } else { + // Click count is 1 then, check for popup trigger. + if (e.isPopupTrigger()) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + } + + public void mouseReleased(MouseEvent e) { + if (e.getSource() == table) { + if (e.isPopupTrigger() && (e.getClickCount() == 1)) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + + public void mousePressed(MouseEvent e) { + if (e.getSource() == table) { + if (e.isPopupTrigger() && (e.getClickCount() == 1)) { + popupMenu.show(table, e.getX(), e.getY()); + } + } + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:23 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:23 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.5 2002/11/18 22:11:51 mpowers - * rglista's long-overdue enhancements: can now clear the display! + * Revision 1.5 2002/11/18 22:11:51 mpowers rglista's long-overdue enhancements: + * can now clear the display! * - * Revision 1.4 2002/10/24 18:19:03 mpowers - * Now telling NSNotification to generate stack traces. + * Revision 1.4 2002/10/24 18:19:03 mpowers Now telling NSNotification to + * generate stack traces. * - * Revision 1.3 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. + * Revision 1.3 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. * - * Revision 1.2 2001/04/09 21:40:25 mpowers - * Numerous usability enhancements. + * Revision 1.2 2001/04/09 21:40:25 mpowers Numerous usability enhancements. * - * Revision 1.1 2001/04/08 20:58:45 mpowers - * Contributing notification inspector. + * Revision 1.1 2001/04/08 20:58:45 mpowers Contributing notification inspector. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java index d2e51ed..b287691 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/RadioPanelAssociation.java @@ -28,430 +28,343 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.components.RadioButtonPanel; /** -* RadioPanelAssociation binds RadioButtonPanels to -* display groups. It works exactly like a -* ComboBoxAssociation. Bindings are: -*
    -* -*
  • value: a property of the selected object in the -* display group that will be bind to the item the user -* selects or the text that the user enters in the field.
  • -* -*
  • titles: a property of the objects in the bound -* display group that will appear in the list. If the -* objects aspect is not bound, this property is also -* used to populate the value binding.
  • -* -*
  • objects: optional - if specified, when the user -* selects an title in the list, the property of the -* object at the corresponding index of the bound display -* group will be used to populate the value binding.
  • -* -*
  • enabled: a boolean property of the selected object in the -* display group that determines whether -* the user can edit the field.
  • -* -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class RadioPanelAssociation extends EOAssociation - implements ActionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ValueAspect, - ObjectsAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public RadioPanelAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof RadioButtonPanel ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { + * RadioPanelAssociation binds RadioButtonPanels to display groups. It works + * exactly like a ComboBoxAssociation. Bindings are: + *
    + * + *
  • value: a property of the selected object in the display group that will + * be bind to the item the user selects or the text that the user enters in the + * field.
  • + * + *
  • titles: a property of the objects in the bound display group that will + * appear in the list. If the objects aspect is not bound, this property is also + * used to populate the value binding.
  • + * + *
  • objects: optional - if specified, when the user selects an title in the + * list, the property of the object at the corresponding index of the bound + * display group will be used to populate the value binding.
  • + * + *
  • enabled: a boolean property of the selected object in the display group + * that determines whether the user can edit the field.
  • + * + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class RadioPanelAssociation extends EOAssociation implements ActionListener { + static final NSArray aspects = new NSArray( + new Object[] { TitlesAspect, ValueAspect, ObjectsAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public RadioPanelAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof RadioButtonPanel); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { super.establishConnection(); // prepopulate titles - EODisplayGroup displayGroup = - displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + EODisplayGroup displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); } - populateValue(); - addAsListener(); - } - - protected void addAsListener() - { - component().addActionListener( this ); + populateValue(); + addAsListener(); + } + + protected void addAsListener() { + component().addActionListener(this); } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { removeAsListener(); - super.breakConnection(); - } + super.breakConnection(); + } - protected void removeAsListener() - { - component().removeActionListener( this ); + protected void removeAsListener() { + component().removeActionListener(this); } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { removeAsListener(); - + RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; - + // titles aspect - displayGroup = displayGroupForAspect( TitlesAspect ); - if ( displayGroup != null ) - { + displayGroup = displayGroupForAspect(TitlesAspect); + if (displayGroup != null) { // if backing group has changed - if ( displayGroup.contentsChanged() ) - { - key = displayGroupKeyForAspect( TitlesAspect ); - populateTitles( displayGroup, key ); + if (displayGroup.contentsChanged()) { + key = displayGroupKeyForAspect(TitlesAspect); + populateTitles(displayGroup, key); } } - // value aspect - populateValue(); - + // value aspect + populateValue(); + // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( EnabledAspect ); - Object value = - displayGroup.selectedObjectValueForKey( key ); - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } + displayGroup = displayGroupForAspect(EnabledAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(EnabledAspect); + Object value = displayGroup.selectedObjectValueForKey(key); + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); + } } - + addAsListener(); - } - + } + /** - * Called to repopulate the title list from the - * specified display group. - */ - protected void populateTitles( - EODisplayGroup displayGroup, String key ) - { + * Called to repopulate the title list from the specified display group. + */ + protected void populateTitles(EODisplayGroup displayGroup, String key) { Object value; int count = displayGroup.displayedObjects().count(); - String[] titles = new String[ count ]; - for ( int i = 0; i < count; i++ ) - { - value = displayGroup.valueForObjectAtIndex( i, key ); - if ( value != null ) - { + String[] titles = new String[count]; + for (int i = 0; i < count; i++) { + value = displayGroup.valueForObjectAtIndex(i, key); + if (value != null) { titles[i] = value.toString(); - } - else - { - titles[i] = ""; + } else { + titles[i] = ""; } } - component().setLabels( titles ); + component().setLabels(titles); } - + /** - * Called to populate the value from the display group. - */ - protected void populateValue() - { + * Called to populate the value from the display group. + */ + protected void populateValue() { RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - - Object value = displayGroup.selectedObjectValueForKey( key ); + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + + Object value = displayGroup.selectedObjectValueForKey(key); // objects aspect - EODisplayGroup objectsDisplayGroup = - displayGroupForAspect( ObjectsAspect ); - if ( ( objectsDisplayGroup != null ) && ( value != null ) ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); + EODisplayGroup objectsDisplayGroup = displayGroupForAspect(ObjectsAspect); + if ((objectsDisplayGroup != null) && (value != null)) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); Object match; int index = NSArray.NotFound; int count = objectsDisplayGroup.displayedObjects().count(); - for ( int i = 0; i < count; i++ ) - { - match = objectsDisplayGroup.valueForObjectAtIndex( i, objectKey ); - if ( value.equals( match ) ) - { + for (int i = 0; i < count; i++) { + match = objectsDisplayGroup.valueForObjectAtIndex(i, objectKey); + if (value.equals(match)) { index = i; } } - if ( index == NSArray.NotFound ) - { - if ( component.getValue() != null ) - { - component.setValue( null ); + if (index == NSArray.NotFound) { + if (component.getValue() != null) { + component.setValue(null); } - } - else - { + } else { String[] titles = component().getLabels(); - component.setValue( titles[ index ] ); + component.setValue(titles[index]); } - } - else - { - if ( value != null ) value = value.toString(); - component.setValue( (String) value ); + } else { + if (value != null) + value = value.toString(); + component.setValue((String) value); } } } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { RadioButtonPanel component = component(); EODisplayGroup displayGroup; String key; - + // selected title aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); Object value = null; // selected object aspect, if any - EODisplayGroup objectsGroup = - displayGroupForAspect( ObjectsAspect ); - if ( objectsGroup != null ) - { - String objectKey = displayGroupKeyForAspect( ObjectsAspect ); - String selectedValue = component.getValue(); - if ( selectedValue != null ) - { + EODisplayGroup objectsGroup = displayGroupForAspect(ObjectsAspect); + if (objectsGroup != null) { + String objectKey = displayGroupKeyForAspect(ObjectsAspect); + String selectedValue = component.getValue(); + if (selectedValue != null) { String[] titles = component.getLabels(); int index = -1; - for ( int i = 0; i < titles.length; i++ ) - { - if ( selectedValue.equals( titles[i] ) ) - { - index = i; + for (int i = 0; i < titles.length; i++) { + if (selectedValue.equals(titles[i])) { + index = i; } } - if ( index != -1 ) - { - value = objectsGroup - .valueForObjectAtIndex( index, objectKey ); + if (index != -1) { + value = objectsGroup.valueForObjectAtIndex(index, objectKey); } } - } - else // just use the selected item + } else // just use the selected item { - value = component.getValue(); + value = component.getValue(); } - return displayGroup.setSelectedObjectValue( value, key ); + return displayGroup.setSelectedObjectValue(value, key); } - + return false; } - - // interface ActionListener - + + // interface ActionListener + /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { writeValueToDisplayGroup(); } - + // convenience - private RadioButtonPanel component() - { + private RadioButtonPanel component() { return (RadioButtonPanel) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.4 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.3 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/02/16 17:48:07 mpowers - * Populating titles or data not longer marks target object as changed. + * Revision 1.2 2001/02/16 17:48:07 mpowers Populating titles or data not longer + * marks target object as changed. * - * Revision 1.1.1.1 2000/12/21 15:48:52 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:52 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java index 7194f23..afa5909 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/ReferenceInspector.java @@ -43,241 +43,220 @@ import net.wotonomy.ui.swing.components.ButtonPanel; import net.wotonomy.ui.swing.util.WindowUtilities; /** -* ReferenceInspector tracks objects until they are garbage collected. -* Use it to track objects that you suspect are not being GCed. -* ReferenceInspector retains only weak references to the objects that -* it tracks, so when those weak references cannot be resolved, the -* object has been garbage collected. Note that under some GC -* implementations, adding a weak reference to an object will delay -* garbage collection for that object. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * ReferenceInspector tracks objects until they are garbage collected. Use it to + * track objects that you suspect are not being GCed. ReferenceInspector retains + * only weak references to the objects that it tracks, so when those weak + * references cannot be resolved, the object has been garbage collected. Note + * that under some GC implementations, adding a weak reference to an object will + * delay garbage collection for that object. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class ReferenceInspector - implements MouseListener, ActionListener -{ - protected JTable table; - protected JLabel memoryLabel; - - static protected EODisplayGroup displayGroup; - static protected JFrame window; - - /* Reference queue for cleared WeakKeys */ - private static ReferenceQueue queue = new ReferenceQueue(); - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; +public class ReferenceInspector implements MouseListener, ActionListener { + protected JTable table; + protected JLabel memoryLabel; -/** -* Launches a new ReferenceInspector if one does not already exist. -*/ - public ReferenceInspector() - { - if ( window == null ) - { - table = new JTable(); - memoryLabel = new JLabel(); - - displayGroup = new EODisplayGroup(); - - TableColumn column; - TableColumnAssociation assoc; - - column = new TableColumn(); - column.setHeaderValue( "Object" ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "" ); - assoc.setTable( table ); - assoc.establishConnection(); - - column = new TableColumn(); - column.setHeaderValue( "Address" ); - column.setMaxWidth( 100 ); - - assoc = new TableColumnAssociation( column ); - assoc.bindAspect( EOAssociation.ValueAspect, displayGroup, "identityHashCode" ); - assoc.setTable( table ); - assoc.establishConnection(); - - initLayout(); - } - window.show(); - } + static protected EODisplayGroup displayGroup; + static protected JFrame window; -/** -* Adds the specified object to the ReferenceInspector, launching -* a new ReferenceInspector if one does not already exist. -*/ - public ReferenceInspector( Object anObject ) - { - this(); - displayGroup.insertObjectAtIndex( new ExtendedWeakReference( anObject, queue ), 0 ); - window.show(); - } - - protected void initLayout() - { - table.addMouseListener( this ); // listen for double-clicks - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 500, 250 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - ButtonPanel buttonPanel = new ButtonPanel( new String[] { "Update" } ); - buttonPanel.addActionListener( this ); - - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout( new BorderLayout() ); - bottomPanel.add( buttonPanel, BorderLayout.EAST ); - bottomPanel.add( memoryLabel, BorderLayout.CENTER ); - panel.add( bottomPanel, BorderLayout.SOUTH ); - - window = new JFrame(); - window.setTitle( "Reference Inspector" ); - window.getContentPane().add( panel ); + /* Reference queue for cleared WeakKeys */ + private static ReferenceQueue queue = new ReferenceQueue(); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + /** + * Launches a new ReferenceInspector if one does not already exist. + */ + public ReferenceInspector() { + if (window == null) { + table = new JTable(); + memoryLabel = new JLabel(); + + displayGroup = new EODisplayGroup(); + + TableColumn column; + TableColumnAssociation assoc; + + column = new TableColumn(); + column.setHeaderValue("Object"); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, ""); + assoc.setTable(table); + assoc.establishConnection(); + + column = new TableColumn(); + column.setHeaderValue("Address"); + column.setMaxWidth(100); + + assoc = new TableColumnAssociation(column); + assoc.bindAspect(EOAssociation.ValueAspect, displayGroup, "identityHashCode"); + assoc.setTable(table); + assoc.establishConnection(); + + initLayout(); + } + window.show(); + } + + /** + * Adds the specified object to the ReferenceInspector, launching a new + * ReferenceInspector if one does not already exist. + */ + public ReferenceInspector(Object anObject) { + this(); + displayGroup.insertObjectAtIndex(new ExtendedWeakReference(anObject, queue), 0); + window.show(); + } + + protected void initLayout() { + table.addMouseListener(this); // listen for double-clicks + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(500, 250)); + panel.add(scrollPane, BorderLayout.CENTER); + + ButtonPanel buttonPanel = new ButtonPanel(new String[] { "Update" }); + buttonPanel.addActionListener(this); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BorderLayout()); + bottomPanel.add(buttonPanel, BorderLayout.EAST); + bottomPanel.add(memoryLabel, BorderLayout.CENTER); + panel.add(bottomPanel, BorderLayout.SOUTH); + + window = new JFrame(); + window.setTitle("Reference Inspector"); + window.getContentPane().add(panel); // javax.swing.Timer timer = new javax.swing.Timer( 10000, this ); // timer.restart(); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - /* 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() - { + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } + + /* + * 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() { // System.out.println( "ReferenceInspector.processQueue:"); - synchronized ( displayGroup ) - { - int idx; - WeakReference rk; - while ((rk = (WeakReference)queue.poll()) != null) - { + synchronized (displayGroup) { + int idx; + WeakReference rk; + while ((rk = (WeakReference) queue.poll()) != null) { // System.out.println( "ReferenceInspector.processQueue: removing object: " + rk ); - if ( rk != null ) - { - idx = displayGroup.displayedObjects().indexOfIdenticalObject( rk ); - if ( idx != NSArray.NotFound ) - { - displayGroup.deleteObjectAtIndex( idx ); - } - } - } - displayGroup.updateDisplayedObjects(); - } - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { - Runtime runtime = Runtime.getRuntime(); - runtime.gc(); - processQueue(); - - long totalMemory = runtime.totalMemory() / 1024; - long freeMemory = runtime.freeMemory() / 1024; - memoryLabel.setText( - Long.toString( totalMemory - freeMemory ) + "K / " + - Long.toString( totalMemory ) + "K" ); - } - - // interface MouseListener - - /** - * Double click to launch object inspector. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - col = table.convertColumnIndexToModel( col ); - - if ( row == -1 ) return; - - if ( col == 0 ) // time - { - } - else - { - } - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - public class ExtendedWeakReference extends WeakReference - { - public ExtendedWeakReference(Object referent, ReferenceQueue q) - { - super( referent, q ); - } - - public String toString() - { - if ( get() != null ) - { - return get().toString(); - } - return null; - } - - public String identityHashCode() - { - if ( get() != null ) - { - return Integer.toHexString( System.identityHashCode( get() ) ); - } - return null; - } - - } + if (rk != null) { + idx = displayGroup.displayedObjects().indexOfIdenticalObject(rk); + if (idx != NSArray.NotFound) { + displayGroup.deleteObjectAtIndex(idx); + } + } + } + displayGroup.updateDisplayedObjects(); + } + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { + Runtime runtime = Runtime.getRuntime(); + runtime.gc(); + processQueue(); + + long totalMemory = runtime.totalMemory() / 1024; + long freeMemory = runtime.freeMemory() / 1024; + memoryLabel.setText(Long.toString(totalMemory - freeMemory) + "K / " + Long.toString(totalMemory) + "K"); + } + + // interface MouseListener + + /** + * Double click to launch object inspector. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + col = table.convertColumnIndexToModel(col); + + if (row == -1) + return; + + if (col == 0) // time + { + } else { + } + } + } + } + + public void mouseReleased(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public class ExtendedWeakReference extends WeakReference { + public ExtendedWeakReference(Object referent, ReferenceQueue q) { + super(referent, q); + } + + public String toString() { + if (get() != null) { + return get().toString(); + } + return null; + } + + public String identityHashCode() { + if (get() != null) { + return Integer.toHexString(System.identityHashCode(get())); + } + return null; + } + + } } - + /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/03/22 22:39:59 mpowers - * Now shows the window even if previously hidden. + * Revision 1.4 2002/03/22 22:39:59 mpowers Now shows the window even if + * previously hidden. * - * Revision 1.3 2001/10/02 14:22:39 mpowers - * Now shows used and heap memory usage. + * Revision 1.3 2001/10/02 14:22:39 mpowers Now shows used and heap memory + * usage. * - * Revision 1.2 2001/07/10 16:39:32 mpowers - * Removed printlns. + * Revision 1.2 2001/07/10 16:39:32 mpowers Removed printlns. * - * Revision 1.1 2001/07/10 16:32:50 mpowers - * Adding the reference inspector. + * Revision 1.1 2001/07/10 16:32:50 mpowers Adding the reference inspector. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java index b836dcc..5a83240 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/SliderAssociation.java @@ -30,390 +30,309 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* SliderAssociation binds a JSlider component to -* a display group. Bindings are: -*
    -*
  • value: a property convertable to/from a string
  • -*
  • enabled: a boolean property that determines whether -* the user can select the text in the field
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class SliderAssociation extends EOAssociation - implements ChangeListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, VisibleAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "value" - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public SliderAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JSlider ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - component().addChangeListener( this ); - super.establishConnection(); - + * SliderAssociation binds a JSlider component to a display group. Bindings are: + *
    + *
  • value: a property convertable to/from a string
  • + *
  • enabled: a boolean property that determines whether the user can select + * the text in the field
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class SliderAssociation extends EOAssociation implements ChangeListener { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EnabledAspect, VisibleAspect }); + static final NSArray aspectSignatures = new NSArray( + new Object[] { AttributeToOneAspectSignature, AttributeToOneAspectSignature, }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "value" }); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public SliderAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JSlider); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + component().addChangeListener(this); + super.establishConnection(); + // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - component().removeChangeListener( this ); - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + component().removeChangeListener(this); + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { JSlider component = component(); EODisplayGroup displayGroup; String key; Object value; - + // value aspect - displayGroup = displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - key = displayGroupKeyForAspect( ValueAspect ); - component.setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( key ) ); - - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup + displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + key = displayGroupKeyForAspect(ValueAspect); + component.setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(key)); + + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup // convert value to int - value = ValueConverter.convertObjectToClass( value, Integer.class ); + value = ValueConverter.convertObjectToClass(value, Integer.class); int intValue; - if ( value == null ) - { + if (value == null) { intValue = 0; + } else { + intValue = ((Integer) value).intValue(); } - else - { - intValue = ((Integer)value).intValue(); - } - - if ( component.getValue() != intValue ) - { - component().removeChangeListener( this ); - component.setValue( intValue ); - component().addChangeListener( this ); + + if (component.getValue() != intValue) { + component().removeChangeListener(this); + component.setValue(intValue); + component().addChangeListener(this); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( displayGroup != null ) || ( key != null ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((displayGroup != null) || (key != null)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (converted.booleanValue() != component.isEnabled()) { + component.setEnabled(converted.booleanValue()); } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( converted.booleanValue() != component.isEnabled() ) - { - component.setEnabled( converted.booleanValue() ); - } } // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - key = displayGroupKeyForAspect( VisibleAspect ); - if ( ( displayGroup != null ) || ( key != null ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(VisibleAspect); + key = displayGroupKeyForAspect(VisibleAspect); + if ((displayGroup != null) || (key != null)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value - value = key; + value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != component.isVisible() ) - { - component.setVisible( converted.booleanValue() ); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != component.isVisible()) { + component.setVisible(converted.booleanValue()); } - } + } } - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { return writeValueToDisplayGroup(); - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - EODisplayGroup displayGroup = - displayGroupForAspect( ValueAspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( ValueAspect ); - Object value = new Integer( component().getValue() ); - - boolean returnValue = true; - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + EODisplayGroup displayGroup = displayGroupForAspect(ValueAspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(ValueAspect); + Object value = new Integer(component().getValue()); + + boolean returnValue = true; + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; } return false; } - // interface ChangeListener - + // interface ChangeListener + /** - * Updates object on change. - */ - public void stateChanged(ChangeEvent e) - { + * Updates object on change. + */ + public void stateChanged(ChangeEvent e) { writeValueToDisplayGroup(); } - - private JSlider component() - { - return (JSlider) object(); + + private JSlider component() { + return (JSlider) object(); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.8 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.7 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2002/10/11 20:12:58 mpowers - * Updated aspect signature. + * Revision 1.6 2002/10/11 20:12:58 mpowers Updated aspect signature. * - * Revision 1.5 2002/10/11 20:08:14 mpowers - * Added visible aspect to slider association. + * Revision 1.5 2002/10/11 20:08:14 mpowers Added visible aspect to slider + * association. * - * Revision 1.4 2001/11/01 15:54:37 mpowers - * Minor update to aspect signature. + * Revision 1.4 2001/11/01 15:54:37 mpowers Minor update to aspect signature. * - * Revision 1.3 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.3 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.2 2001/02/16 15:03:34 mpowers - * Fixed: slider sets value in display group after selection changed. + * Revision 1.2 2001/02/16 15:03:34 mpowers Fixed: slider sets value in display + * group after selection changed. * - * Revision 1.1.1.1 2000/12/21 15:48:55 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:55 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java index bc02f7d..edba674 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableAssociation.java @@ -52,754 +52,619 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TableAssociation binds one or more TableColumnAssociations -* to a display group. You should not instantiate this class -* directly; use TableColumnAssociation.setTable() instead. -* Note that TableAssociation inserts itself as the controlled -* JTable's TableModel. -* -* Bindings are: -*
    -*
  • source: a property convertable to a string for -* display in the cells of the table column
  • -*
  • enabled: a property convertable to a string for -* display in the cells of the table column. -* Note that you can bind this aspect to a key equal to -* "true" or "false" and leave the display group null.
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TableAssociation extends EOAssociation - implements ActionListener, ListSelectionListener, FocusListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - SourceAspect, EnabledAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "tableModel", "tableHeader" - } ); - - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + * TableAssociation binds one or more TableColumnAssociations to a display + * group. You should not instantiate this class directly; use + * TableColumnAssociation.setTable() instead. Note that TableAssociation inserts + * itself as the controlled JTable's TableModel. + * + * Bindings are: + *
    + *
  • source: a property convertable to a string for display in the cells of + * the table column
  • + *
  • enabled: a property convertable to a string for display in the cells of + * the table column. Note that you can bind this aspect to a key equal to "true" + * or "false" and leave the display group null.
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TableAssociation extends EOAssociation implements ActionListener, ListSelectionListener, FocusListener { + static final NSArray aspects = new NSArray(new Object[] { SourceAspect, EnabledAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "tableModel", "tableHeader" }); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; EODisplayGroup source; - EODisplayGroup sortTarget; // used by TreeColumnAssociation + EODisplayGroup sortTarget; // used by TreeColumnAssociation NSMutableArray columns; - JTableHeader tableHeader; - - boolean pleaseIgnore; - boolean selectionPaintedImmediately; - boolean selectionTracking; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. setTable() must be called before - * establishing the connection. - */ - public TableAssociation ( Object anObject ) - { - super( anObject ); + JTableHeader tableHeader; + + boolean pleaseIgnore; + boolean selectionPaintedImmediately; + boolean selectionTracking; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. setTable() must be + * called before establishing the connection. + */ + public TableAssociation(Object anObject) { + super(anObject); source = null; columns = new NSMutableArray(); - JTable aTable = ((JTable)anObject); - aTable.addFocusListener( this ); - aTable.setModel( - new TableAssociationModel( this ) ); - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - - // why did sun make this harder to use? - //aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( - // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY); - //aTable.getActionMap().put(COPY, this); - - aTable.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - aTable.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - tableHeader = new SortedTableHeader(); - tableHeader.setColumnModel( aTable.getColumnModel() ); - aTable.setTableHeader( tableHeader ); - tableHeader.addMouseListener( new TableHeaderListener() ); - pleaseIgnore = false; - selectionPaintedImmediately = false; - selectionTracking = false; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JTable ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( SourceAspect.equals( anAspect ) ) - { + JTable aTable = ((JTable) anObject); + aTable.addFocusListener(this); + aTable.setModel(new TableAssociationModel(this)); + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + + // why did sun make this harder to use? + // aTable.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( + // KeyStroke.getKeyStroke( KeyEvent.VK_C, java.awt.Event.CTRL_MASK ), COPY); + // aTable.getActionMap().put(COPY, this); + + aTable.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + aTable.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + tableHeader = new SortedTableHeader(); + tableHeader.setColumnModel(aTable.getColumnModel()); + aTable.setTableHeader(tableHeader); + tableHeader.addMouseListener(new TableHeaderListener()); + pleaseIgnore = false; + selectionPaintedImmediately = false; + selectionTracking = false; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JTable); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (SourceAspect.equals(anAspect)) { source = aDisplayGroup; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - if ( source == null ) - { - throw new WotonomyException( "No display group: " + - "please ensure that the TableColumnAssociation " + - "has a display group bound to the ValueAspect" ); - } - super.establishConnection(); - selectFromDisplayGroup(); - addAsListener(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { - component().getSelectionModel() - .addListSelectionListener( this ); - } - - protected void removeAsListener() - { - component().getSelectionModel() - .removeListSelectionListener( this ); - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - // stop any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - return editor.stopCellEditing(); - } - return true; - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - if ( source != null ) - { - if ( source.contentsChanged() ) - { - removeAsListener(); - ((TableAssociationModel)component().getModel()). - fireTableDataChanged(); + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + if (source == null) { + throw new WotonomyException("No display group: " + "please ensure that the TableColumnAssociation " + + "has a display group bound to the ValueAspect"); + } + super.establishConnection(); + selectFromDisplayGroup(); + addAsListener(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { + component().getSelectionModel().addListSelectionListener(this); + } + + protected void removeAsListener() { + component().getSelectionModel().removeListSelectionListener(this); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + // stop any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + return editor.stopCellEditing(); + } + return true; + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + if (source != null) { + if (source.contentsChanged()) { + removeAsListener(); + ((TableAssociationModel) component().getModel()).fireTableDataChanged(); selectFromDisplayGroup(); - addAsListener(); - - // if we caused this change, do nothing - if ( pleaseIgnore ) - { - pleaseIgnore = false; - } - else // otherwise, update the sort indicator - { - tableHeader.repaint(); - - // cancel any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - editor.cancelCellEditing(); - } - } - } - else - if ( source.selectionChanged() ) - { - removeAsListener(); + addAsListener(); + + // if we caused this change, do nothing + if (pleaseIgnore) { + pleaseIgnore = false; + } else // otherwise, update the sort indicator + { + tableHeader.repaint(); + + // cancel any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + editor.cancelCellEditing(); + } + } + } else if (source.selectionChanged()) { + removeAsListener(); selectFromDisplayGroup(); - addAsListener(); + addAsListener(); } } - } + } - private void selectFromDisplayGroup() - { + private void selectFromDisplayGroup() { JTable component = component(); int index; component.getSelectionModel().clearSelection(); - Enumeration e = - source.selectionIndexes().objectEnumerator(); - - while ( e.hasMoreElements() ) - { // add selections one-by-one to support non-contiguous - index = ((Number)e.nextElement()).intValue(); - component.getSelectionModel().addSelectionInterval( - index, index ); // adds one row + Enumeration e = source.selectionIndexes().objectEnumerator(); + + while (e.hasMoreElements()) { // add selections one-by-one to support non-contiguous + index = ((Number) e.nextElement()).intValue(); + component.getSelectionModel().addSelectionInterval(index, index); // adds one row } } // interface ListSelectionListener - public void valueChanged(ListSelectionEvent e) - { - if ( source != null ) - { - if ( selectionTracking || !e.getValueIsAdjusting() ) - { - int[] selectedIndices = component().getSelectedRows(); - final NSMutableArray indexList = new NSMutableArray(); - for ( int i = 0; i < selectedIndices.length; i++ ) - { - indexList.addObject( new Integer( selectedIndices[i] ) ); - } - - // invoke later so the component is repainted before - // any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - Runnable select = new Runnable() - { - public void run() - { - pleaseIgnore = true; - source.setSelectionIndexes( indexList ); - } - }; - if ( selectionPaintedImmediately ) - { - EventQueue.invokeLater( select ); - } - else - { - select.run(); - } - } - } - } - - /** - * Determines whether the selection should be painted - * immediately after the user clicks and therefore - * before the children display group is updated. - * When the children group is bound to many associations - * or is bound to a master-detail association, updating - * the display group can take a perceptibly long time. - * This property defaults to false. - * @see #setSelectionPaintedImmediately - */ - public boolean isSelectionPaintedImmediately() - { - return selectionPaintedImmediately; - } - - /** - * Sets whether the selection should be painted immediately. - * Setting this property to true will let the table paint - * first before the display group is updated. - */ - public void setSelectionPaintedImmediately( boolean isImmediate ) - { - selectionPaintedImmediately = isImmediate; - } - - /** - * Determines whether the selection is actively tracking - * the selection as the user moves the mouse. - * If true, selection will not be updated while the - * list selection event returns true for isValueAdjusting(). - * This property defaults to false. - * @see #setSelectionTracking - */ - public boolean isSelectionTracking() - { - return selectionTracking; - } - - /** - * Sets whether the selection is actively tracking - * the selection as the user moves the mouse. - * Setting this property to true will update the display - * group with each change to the selection. - * This means that any tree selection listers will - * also be notified before the display group is updated - * and will have to invokeLater if they want to see the - * updated display group. - */ - public void setSelectionTracking( boolean isTracking ) - { - selectionTracking = isTracking; - } - - // convenience - private JTable component() - { - return (JTable) object(); - } + public void valueChanged(ListSelectionEvent e) { + if (source != null) { + if (selectionTracking || !e.getValueIsAdjusting()) { + int[] selectedIndices = component().getSelectedRows(); + final NSMutableArray indexList = new NSMutableArray(); + for (int i = 0; i < selectedIndices.length; i++) { + indexList.addObject(new Integer(selectedIndices[i])); + } + + // invoke later so the component is repainted before + // any potentially lengthy second-order effects happen: + // this improves user-perceived responsiveness of big apps + Runnable select = new Runnable() { + public void run() { + pleaseIgnore = true; + source.setSelectionIndexes(indexList); + } + }; + if (selectionPaintedImmediately) { + EventQueue.invokeLater(select); + } else { + select.run(); + } + } + } + } /** - * Copies the contents of the table to the clipboard as a tab-delimited string. - */ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getTabDelimitedString() ); - clipboard.setContents( selection, selection ); - } + * Determines whether the selection should be painted immediately after the user + * clicks and therefore before the children display group is updated. When the + * children group is bound to many associations or is bound to a master-detail + * association, updating the display group can take a perceptibly long time. + * This property defaults to false. + * + * @see #setSelectionPaintedImmediately + */ + public boolean isSelectionPaintedImmediately() { + return selectionPaintedImmediately; + } /** - * Converts the contents of the table to a tab-delimited string. - * @return A String containing the text contents of the table. - */ - public String getTabDelimitedString() - { - StringBuffer result = new StringBuffer(64); - - TableModel model = component().getModel(); - int cols = model.getColumnCount(); - int rows = model.getRowCount(); - - Object o = null; - for ( int y = 0; y < rows; y++ ) - { - for ( int x = 0; x < cols; x++ ) - { - o = model.getValueAt( y, x ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\t' ); - } - result.append( '\n' ); - } - - return result.toString(); - } - - // interface ActionEventListener - - public void actionPerformed(ActionEvent evt) - { - String cmd = evt.getActionCommand(); - - if ( COPY.equals( cmd ) ) - { - copyToClipboard(); - return; - } - } - - /** - * Used to render the little triangle over the sorted column(s). - */ - class SortedTableHeader extends JTableHeader - { - public void paint(Graphics g) - { - super.paint( g ); - Rectangle r; - TableColumnAssociation association; - int size = columns.size(); - NSArray orderings; - if ( sortTarget != null ) - { - orderings = sortTarget.sortOrderings(); - } - else - { - orderings = source.sortOrderings(); - } - for ( int i = 0; i < size; i++ ) - { - r = getHeaderRect( component().convertColumnIndexToView( i ) ); - association = (TableColumnAssociation) columns.objectAtIndex( i ); - association.drawSortIndicator( r, g, orderings ); - } - } - } - - /** - * Used to listen for clicks on the table header. - */ - class TableHeaderListener extends MouseAdapter - { - public void mouseClicked( MouseEvent evt ) - { - EODisplayGroup displayGroup = sortTarget; - if ( displayGroup == null ) displayGroup = source; - - if ( evt.getClickCount() > 0 ) - { - int columnClicked = tableHeader.columnAtPoint( evt.getPoint() ); - if ( columnClicked != -1 ) - { - columnClicked = component().convertColumnIndexToModel( columnClicked ); - TableColumnAssociation association = (TableColumnAssociation) - columns.objectAtIndex( columnClicked ); - if ( association.isSortable() ) - { - NSMutableArray newOrder = - new NSMutableArray( displayGroup.sortOrderings() ); - - // click once = asc, twice = desc, thrice = clear - EOSortOrdering sortOrdering; - int index = association.getIndexOfMatchingOrdering( newOrder ); - if ( index == -1 ) sortOrdering = null; - else if ( index == 1 ) sortOrdering = association.getSortOrdering( false ); - else sortOrdering = association.getSortOrdering( true ); - - pleaseIgnore = true; - tableHeader.repaint(); - - // stop any cell editing - CellEditor editor = component().getCellEditor(); - if ( editor != null ) - { - editor.stopCellEditing(); - } - - // remove existing key - if ( index != 0 ) - { - newOrder.removeObjectAtIndex( Math.abs( index ) - 1 ); - } - - // put new key at front of list - if ( sortOrdering != null ) - { - newOrder.insertObjectAtIndex( sortOrdering, 0 ); - } - - displayGroup.setSortOrderings( newOrder ); - displayGroup.updateDisplayedObjects(); - } - } - } - } - } + * Sets whether the selection should be painted immediately. Setting this + * property to true will let the table paint first before the display group is + * updated. + */ + public void setSelectionPaintedImmediately(boolean isImmediate) { + selectionPaintedImmediately = isImmediate; + } /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Determines whether the selection is actively tracking the selection as the + * user moves the mouse. If true, selection will not be updated while the list + * selection event returns true for isValueAdjusting(). This property defaults + * to false. + * + * @see #setSelectionTracking + */ + public boolean isSelectionTracking() { + return selectionTracking; + } + + /** + * Sets whether the selection is actively tracking the selection as the user + * moves the mouse. Setting this property to true will update the display group + * with each change to the selection. This means that any tree selection listers + * will also be notified before the display group is updated and will have to + * invokeLater if they want to see the updated display group. + */ + public void setSelectionTracking(boolean isTracking) { + selectionTracking = isTracking; + } + + // convenience + private JTable component() { + return (JTable) object(); + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getTabDelimitedString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the contents of the table to a tab-delimited string. + * + * @return A String containing the text contents of the table. + */ + public String getTabDelimitedString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = component().getModel(); + int cols = model.getColumnCount(); + int rows = model.getRowCount(); + + Object o = null; + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + o = model.getValueAt(y, x); + if (o == null) + o = ""; + result.append(o); + result.append('\t'); + } + result.append('\n'); + } + + return result.toString(); + } + + // interface ActionEventListener + + public void actionPerformed(ActionEvent evt) { + String cmd = evt.getActionCommand(); + + if (COPY.equals(cmd)) { + copyToClipboard(); + return; + } + } + + /** + * Used to render the little triangle over the sorted column(s). + */ + class SortedTableHeader extends JTableHeader { + public void paint(Graphics g) { + super.paint(g); + Rectangle r; + TableColumnAssociation association; + int size = columns.size(); + NSArray orderings; + if (sortTarget != null) { + orderings = sortTarget.sortOrderings(); + } else { + orderings = source.sortOrderings(); + } + for (int i = 0; i < size; i++) { + r = getHeaderRect(component().convertColumnIndexToView(i)); + association = (TableColumnAssociation) columns.objectAtIndex(i); + association.drawSortIndicator(r, g, orderings); + } + } + } + + /** + * Used to listen for clicks on the table header. + */ + class TableHeaderListener extends MouseAdapter { + public void mouseClicked(MouseEvent evt) { + EODisplayGroup displayGroup = sortTarget; + if (displayGroup == null) + displayGroup = source; + + if (evt.getClickCount() > 0) { + int columnClicked = tableHeader.columnAtPoint(evt.getPoint()); + if (columnClicked != -1) { + columnClicked = component().convertColumnIndexToModel(columnClicked); + TableColumnAssociation association = (TableColumnAssociation) columns.objectAtIndex(columnClicked); + if (association.isSortable()) { + NSMutableArray newOrder = new NSMutableArray(displayGroup.sortOrderings()); + + // click once = asc, twice = desc, thrice = clear + EOSortOrdering sortOrdering; + int index = association.getIndexOfMatchingOrdering(newOrder); + if (index == -1) + sortOrdering = null; + else if (index == 1) + sortOrdering = association.getSortOrdering(false); + else + sortOrdering = association.getSortOrdering(true); + + pleaseIgnore = true; + tableHeader.repaint(); + + // stop any cell editing + CellEditor editor = component().getCellEditor(); + if (editor != null) { + editor.stopCellEditing(); + } + + // remove existing key + if (index != 0) { + newOrder.removeObjectAtIndex(Math.abs(index) - 1); + } + + // put new key at front of list + if (sortOrdering != null) { + newOrder.insertObjectAtIndex(sortOrdering, 0); + } + + displayGroup.setSortOrderings(newOrder); + displayGroup.updateDisplayedObjects(); + } + } + } + } + } + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! component().isEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!component().isEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } /** - * Used as the model for the controlled table. - * Package access so TableColumnAssociation can recognize - * it and use the addColumnAssociation() method. - * Extends AbstractTableModel just so we get event - * broadcasting for free. - */ - static class TableAssociationModel extends AbstractTableModel - { + * Used as the model for the controlled table. Package access so + * TableColumnAssociation can recognize it and use the addColumnAssociation() + * method. Extends AbstractTableModel just so we get event broadcasting for + * free. + */ + static class TableAssociationModel extends AbstractTableModel { private TableAssociation parent; - private TableAssociationModel( TableAssociation aParent ) - { + private TableAssociationModel(TableAssociation aParent) { parent = aParent; } - - public TableAssociation getAssociation() - { - return parent; - } + + public TableAssociation getAssociation() { + return parent; + } /** - * Adds the column to the list of ColumnAssociations, - * and adds the corresponding column to the table - * at the next available index, setting the value of - * the column's model index accordingly. - * Establishes connection if no columns are currently - * associated. - */ - public void addColumnAssociation( - TableColumnAssociation aColumnAssociation ) - { - // establish connection if necessary - if ( parent.columns.size() == 0 ) - { - parent.establishConnection(); - } - + * Adds the column to the list of ColumnAssociations, and adds the corresponding + * column to the table at the next available index, setting the value of the + * column's model index accordingly. Establishes connection if no columns are + * currently associated. + */ + public void addColumnAssociation(TableColumnAssociation aColumnAssociation) { + // establish connection if necessary + if (parent.columns.size() == 0) { + parent.establishConnection(); + } + int newIndex = parent.columns.count(); - parent.columns.add( aColumnAssociation ); + parent.columns.add(aColumnAssociation); // add column to table TableColumn column = (TableColumn) aColumnAssociation.object(); - column.setModelIndex( newIndex ); - parent.component().addColumn( column ); - + column.setModelIndex(newIndex); + parent.component().addColumn(column); + } /** - * Removes the column from the list of ColumnAssociations, - * and removes the corresponding column from the table. - * Breaks connection if no more columns are associated. - */ - public void removeColumnAssociation( - TableColumnAssociation aColumnAssociation ) - { - int index = parent.columns.indexOfIdenticalObject( aColumnAssociation ); - if ( index == NSArray.NotFound ) return; - - parent.columns.removeObjectAtIndex( index ); + * Removes the column from the list of ColumnAssociations, and removes the + * corresponding column from the table. Breaks connection if no more columns are + * associated. + */ + public void removeColumnAssociation(TableColumnAssociation aColumnAssociation) { + int index = parent.columns.indexOfIdenticalObject(aColumnAssociation); + if (index == NSArray.NotFound) + return; + + parent.columns.removeObjectAtIndex(index); // remove column from table TableColumn column = (TableColumn) aColumnAssociation.object(); - parent.component().removeColumn( column ); - - // break connection if necessary - if ( parent.columns.size() == 0 ) - { - parent.breakConnection(); - } + parent.component().removeColumn(column); + + // break connection if necessary + if (parent.columns.size() == 0) { + parent.breakConnection(); + } } - public int getRowCount() - { - if ( parent.source == null ) return 0; + public int getRowCount() { + if (parent.source == null) + return 0; return parent.source.displayedObjects().count(); } - public int getColumnCount() - { + public int getColumnCount() { return parent.columns.count(); } /** - * Attempts to retrieve the header value from the specified column, - * or returns " " if the value is null. - */ - public String getColumnName(int columnIndex) - { - TableColumnAssociation association = (TableColumnAssociation) - parent.columns.objectAtIndex( columnIndex ); - Object value = ((TableColumn)association.object()).getHeaderValue(); - if ( value != null ) return value.toString(); - return " "; + * Attempts to retrieve the header value from the specified column, or returns " + * " if the value is null. + */ + public String getColumnName(int columnIndex) { + TableColumnAssociation association = (TableColumnAssociation) parent.columns.objectAtIndex(columnIndex); + Object value = ((TableColumn) association.object()).getHeaderValue(); + if (value != null) + return value.toString(); + return " "; } /** - * Returns the class of the first item in the - * display group bound to the column. - */ - public Class getColumnClass(int columnIndex) - { - Object value; - int count = getRowCount(); - for( int i = 0; i < count; i++ ) - { //First row in column is null find one that is not. - value = ((TableColumnAssociation)parent.columns. - objectAtIndex( columnIndex ) ).valueAtIndex( i ); - if ( value != null ) return value.getClass(); - } - return Object.class; + * Returns the class of the first item in the display group bound to the column. + */ + public Class getColumnClass(int columnIndex) { + Object value; + int count = getRowCount(); + for (int i = 0; i < count; i++) { // First row in column is null find one that is not. + value = ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(i); + if (value != null) + return value.getClass(); + } + return Object.class; } - /** - * Calls the column association's isEditableAtRow method. - */ - public boolean isCellEditable(int rowIndex, - int columnIndex) - { - return - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).isEditableAtRow( rowIndex ); + /** + * Calls the column association's isEditableAtRow method. + */ + public boolean isCellEditable(int rowIndex, int columnIndex) { + return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).isEditableAtRow(rowIndex); } - /** - * Calls the column association's valueAtIndex method. - */ - public Object getValueAt(int rowIndex, - int columnIndex) - { - return - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).valueAtIndex( rowIndex ); + /** + * Calls the column association's valueAtIndex method. + */ + public Object getValueAt(int rowIndex, int columnIndex) { + return ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).valueAtIndex(rowIndex); } - /** - * Calls the column association's setValueAtIndex method. - */ - public void setValueAt(Object aValue, - int rowIndex, - int columnIndex) - { - Object existingValue = getValueAt( rowIndex, columnIndex ); - - // don't update display group if value is the same as before - if ( aValue == existingValue ) return; // handles null case - if ( existingValue != null ) // both are not null - { - Object newValue = aValue; - - // if different types - if ( newValue.getClass() != existingValue.getClass() ) - { - // convert to string since most editors do anyway - newValue = newValue.toString(); - existingValue = existingValue.toString(); - } - if ( newValue.equals( existingValue ) ) - { - // same value - do nothing - return; - } - } - - ((TableColumnAssociation)parent.columns.objectAtIndex( - columnIndex ) ).setValueAtIndex( aValue, rowIndex ); + /** + * Calls the column association's setValueAtIndex method. + */ + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + Object existingValue = getValueAt(rowIndex, columnIndex); + + // don't update display group if value is the same as before + if (aValue == existingValue) + return; // handles null case + if (existingValue != null) // both are not null + { + Object newValue = aValue; + + // if different types + if (newValue.getClass() != existingValue.getClass()) { + // convert to string since most editors do anyway + newValue = newValue.toString(); + existingValue = existingValue.toString(); + } + if (newValue.equals(existingValue)) { + // same value - do nothing + return; + } + } + + ((TableColumnAssociation) parent.columns.objectAtIndex(columnIndex)).setValueAtIndex(aValue, rowIndex); } } @@ -807,121 +672,106 @@ public class TableAssociation extends EOAssociation } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.31 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.31 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.30 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.30 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.29 2002/05/24 14:41:29 mpowers - * Throwing an exception for clarity. + * Revision 1.29 2002/05/24 14:41:29 mpowers Throwing an exception for clarity. * - * Revision 1.28 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.28 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.27 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.27 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.25 2002/03/04 22:49:53 mpowers - * Now working with TreeColumnAssociation to delegate sorting behavior. + * Revision 1.25 2002/03/04 22:49:53 mpowers Now working with + * TreeColumnAssociation to delegate sorting behavior. * - * Revision 1.23 2002/03/04 03:58:17 mpowers - * Refined table header click behavior. + * Revision 1.23 2002/03/04 03:58:17 mpowers Refined table header click + * behavior. * - * Revision 1.22 2002/03/01 23:41:39 mpowers - * Added facility to get table association from model. + * Revision 1.22 2002/03/01 23:41:39 mpowers Added facility to get table + * association from model. * - * Revision 1.21 2002/03/01 20:41:22 mpowers - * Cleaned up unused code. + * Revision 1.21 2002/03/01 20:41:22 mpowers Cleaned up unused code. * - * Revision 1.20 2002/03/01 15:42:00 mpowers - * Table column headers now always show their sort indicator. - * A third table-column click clears the sort for that column. + * Revision 1.20 2002/03/01 15:42:00 mpowers Table column headers now always + * show their sort indicator. A third table-column click clears the sort for + * that column. * - * Revision 1.19 2002/02/28 23:01:39 mpowers - * TableColumnAssociations add and remove themselves from the TableAssociation - * when their connection is established and broken respectively. - * TableAssociations now break connection if they have no column associations. + * Revision 1.19 2002/02/28 23:01:39 mpowers TableColumnAssociations add and + * remove themselves from the TableAssociation when their connection is + * established and broken respectively. TableAssociations now break connection + * if they have no column associations. * - * Revision 1.18 2002/02/28 22:45:27 mpowers - * Now registers as editing association when table gets focus, so that - * endEditing can appropriate commit any table cell editors. + * Revision 1.18 2002/02/28 22:45:27 mpowers Now registers as editing + * association when table gets focus, so that endEditing can appropriate commit + * any table cell editors. * - * Revision 1.17 2001/10/26 20:01:50 mpowers - * We're again cancelling instead of stopping editing on subjectChanged. - * I believe that the introduction of NSRunLoop has allowed us to return - * to the correct behavior. + * Revision 1.17 2001/10/26 20:01:50 mpowers We're again cancelling instead of + * stopping editing on subjectChanged. I believe that the introduction of + * NSRunLoop has allowed us to return to the correct behavior. * - * Revision 1.16 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.16 2001/09/14 13:40:26 mpowers User-initiated selection changes + * are now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.15 2001/07/25 15:03:01 mpowers - * getColumnName now checks for a column header value before defaulting " ". + * Revision 1.15 2001/07/25 15:03:01 mpowers getColumnName now checks for a + * column header value before defaulting " ". * - * Revision 1.14 2001/06/05 19:09:25 mpowers - * Fixed broken build. + * Revision 1.14 2001/06/05 19:09:25 mpowers Fixed broken build. * - * Revision 1.13 2001/06/05 19:08:12 mpowers - * Better determination of column class. Previously, if the first object - * was null, Object.class was used. Now we get the first non-null object. + * Revision 1.13 2001/06/05 19:08:12 mpowers Better determination of column + * class. Previously, if the first object was null, Object.class was used. Now + * we get the first non-null object. * - * Revision 1.12 2001/05/02 17:39:01 mpowers - * Now selects from display group after initial population. + * Revision 1.12 2001/05/02 17:39:01 mpowers Now selects from display group + * after initial population. * - * Revision 1.11 2001/03/29 23:05:22 mpowers - * Fixes for editing table cells. + * Revision 1.11 2001/03/29 23:05:22 mpowers Fixes for editing table cells. * - * Revision 1.10 2001/03/09 22:09:22 mpowers - * Now better handling jdk1.1 for rendering the column header. + * Revision 1.10 2001/03/09 22:09:22 mpowers Now better handling jdk1.1 for + * rendering the column header. * - * Revision 1.9 2001/02/27 02:10:38 mpowers - * No longer updating values to the display group if the value - * has not changed. + * Revision 1.9 2001/02/27 02:10:38 mpowers No longer updating values to the + * display group if the value has not changed. * - * Revision 1.8 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.8 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.7 2001/02/16 17:47:17 mpowers - * Now cancelling any cell editing on subjectChanged(). + * Revision 1.7 2001/02/16 17:47:17 mpowers Now cancelling any cell editing on + * subjectChanged(). * - * Revision 1.6 2001/01/12 19:11:56 mpowers - * Fixed table column click sorting. + * Revision 1.6 2001/01/12 19:11:56 mpowers Fixed table column click sorting. * - * Revision 1.5 2001/01/12 17:20:30 mpowers - * Moved EOSortOrdering creation to ColumnAssociation. + * Revision 1.5 2001/01/12 17:20:30 mpowers Moved EOSortOrdering creation to + * ColumnAssociation. * - * Revision 1.4 2001/01/11 21:55:57 mpowers - * Implemented sort indicator for table column headers. + * Revision 1.4 2001/01/11 21:55:57 mpowers Implemented sort indicator for table + * column headers. * - * Revision 1.3 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.3 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.2 2001/01/08 20:40:51 mpowers - * JTables in 1.3 clear their selection when the data model changes, - * which sends a list selection event. We now reset the selection again - * after the data changes so we're compatible across 1.2 and 1.3. + * Revision 1.2 2001/01/08 20:40:51 mpowers JTables in 1.3 clear their selection + * when the data model changes, which sends a list selection event. We now reset + * the selection again after the data changes so we're compatible across 1.2 and + * 1.3. * - * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:00 mpowers Contributing wotonomy. * - * Revision 1.7 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.7 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java index e1c32f3..f279926 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TableColumnAssociation.java @@ -38,671 +38,544 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.TableAssociation.TableAssociationModel; - /** -* TableColumnAssociation binds a column of a JTable -* to a property of the elements of a display group. -* Bindings are: -*
    -*
  • value: a property convertable to a string for -* display in the cells of the table column
  • -*
  • editable: a property convertable to a boolean -* that determines the editability of the corresponding -* cells in the column.
  • -*
- -* Because TableColumns do not have a handle to their -* containing JTable, setTable() must be called before -* calling establishConnection(). This will add the -* controlled TableColumn to the specified JTable. -* -* Columns appear in the table in the order in which -* setTable is called on the corresponding association. -* The original table model index is ignored. -* -* Column names appear in the table based on the value -* of TableColumn.getHeaderValue(). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TableColumnAssociation extends EOAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EditableAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "table" - } ); - - static Color[] sortIndicatorColorList; - + * TableColumnAssociation binds a column of a JTable to a property of the + * elements of a display group. Bindings are: + *
    + *
  • value: a property convertable to a string for display in the cells of the + * table column
  • + *
  • editable: a property convertable to a boolean that determines the + * editability of the corresponding cells in the column.
  • + *
+ * + * Because TableColumns do not have a handle to their containing JTable, + * setTable() must be called before calling establishConnection(). This will add + * the controlled TableColumn to the specified JTable. + * + * Columns appear in the table in the order in which setTable is called on the + * corresponding association. The original table model index is ignored. + * + * Column names appear in the table based on the value of + * TableColumn.getHeaderValue(). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TableColumnAssociation extends EOAssociation { + static final NSArray aspects = new NSArray(new Object[] { ValueAspect, EditableAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "table" }); + + static Color[] sortIndicatorColorList; + EODisplayGroup valueDisplayGroup, editableDisplayGroup; String valueKey, editableKey; - boolean sortable; - boolean sortCaseSensitive; - JTable table; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. setTable() must be called before - * establishing the connection. - */ - public TableColumnAssociation ( Object anObject ) - { - super( anObject ); + boolean sortable; + boolean sortCaseSensitive; + JTable table; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. setTable() must be + * called before establishing the connection. + */ + public TableColumnAssociation(Object anObject) { + super(anObject); valueDisplayGroup = null; valueKey = null; editableDisplayGroup = null; editableKey = null; - sortable = true; - sortCaseSensitive = true; - table = null; - } - + sortable = true; + sortCaseSensitive = true; + table = null; + } + /** - * Sets the table to be used for this column association. - * If no TableAssociation exists for the specified table, - * one will be created automatically. The controlled - * table column will be added to the table. Note that - * the table column's model index is ignored: table columns - * appear in the table in the order in which setTable is - * called on their corresponding associations. - */ - public void setTable( JTable aTable ) - { - table = aTable; - if ( table == null ) return; - - // creates table association if not existing - getTableAssociation(); + * Sets the table to be used for this column association. If no TableAssociation + * exists for the specified table, one will be created automatically. The + * controlled table column will be added to the table. Note that the table + * column's model index is ignored: table columns appear in the table in the + * order in which setTable is called on their corresponding associations. + */ + public void setTable(JTable aTable) { + table = aTable; + if (table == null) + return; + + // creates table association if not existing + getTableAssociation(); } - - /** - * Returns the table association for this table column, - * or null if no table has been set. This method will - * create the association if none exists for the table. - */ - public TableAssociation getTableAssociation() - { - if ( table == null ) return null; - - TableAssociation result; - if ( ! ( table.getModel() instanceof TableAssociationModel ) ) - { - result = new TableAssociation( table ); - result.bindAspect( - SourceAspect, displayGroupForAspect( ValueAspect ), "" ); + + /** + * Returns the table association for this table column, or null if no table has + * been set. This method will create the association if none exists for the + * table. + */ + public TableAssociation getTableAssociation() { + if (table == null) + return null; + + TableAssociation result; + if (!(table.getModel() instanceof TableAssociationModel)) { + result = new TableAssociation(table); + result.bindAspect(SourceAspect, displayGroupForAspect(ValueAspect), ""); + } else { + result = ((TableAssociationModel) table.getModel()).getAssociation(); } - else - { - result = ((TableAssociationModel)table.getModel()).getAssociation(); - } - return result; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof TableColumn ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { - valueDisplayGroup = aDisplayGroup; + return result; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof TableColumn); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { + valueDisplayGroup = aDisplayGroup; valueKey = aKey; } - if ( EditableAspect.equals( anAspect ) ) - { + if (EditableAspect.equals(anAspect)) { editableDisplayGroup = aDisplayGroup; editableKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - addAsListener(); - - if ( table == null ) throw new WotonomyException( - "A table must be specified by calling setTable()" ); + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + addAsListener(); + + if (table == null) + throw new WotonomyException("A table must be specified by calling setTable()"); // add association to model - TableAssociationModel model = - (TableAssociationModel) table.getModel(); - model.addColumnAssociation( this ); - - super.establishConnection(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - removeAsListener(); - - if ( table == null ) throw new WotonomyException( - "TableColumnAssociation's table may not be null" ); + TableAssociationModel model = (TableAssociationModel) table.getModel(); + model.addColumnAssociation(this); + + super.establishConnection(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + removeAsListener(); + + if (table == null) + throw new WotonomyException("TableColumnAssociation's table may not be null"); // remove association from model - TableAssociationModel model = - (TableAssociationModel) table.getModel(); - model.removeColumnAssociation( this ); - - super.breakConnection(); - } - - protected void addAsListener() - { - } - - protected void removeAsListener() - { - } - + TableAssociationModel model = (TableAssociationModel) table.getModel(); + model.removeColumnAssociation(this); + + super.breakConnection(); + } + + protected void addAsListener() { + } + + protected void removeAsListener() { + } + /** - * Returns the value to be displayed at the specified index. - * This method is called by the TableAssocation to populate - * the table model. - * This implementation simply retrieves the value from the - * display group bound to the value aspect. - */ - public Object valueAtIndex( int aRowIndex ) - { - if ( valueDisplayGroup != null ) - { - return valueDisplayGroup.valueForObjectAtIndex( - aRowIndex, valueKey ); + * Returns the value to be displayed at the specified index. This method is + * called by the TableAssocation to populate the table model. This + * implementation simply retrieves the value from the display group bound to the + * value aspect. + */ + public Object valueAtIndex(int aRowIndex) { + if (valueDisplayGroup != null) { + return valueDisplayGroup.valueForObjectAtIndex(aRowIndex, valueKey); } return null; } - + /** - * Sets a value for the specified index. This method is - * called by the TableAssocation after a cell has been - * edited. - * This implementation simply sets the value in the - * display group bound to the value aspect. - */ - public void setValueAtIndex( Object aValue, int aRowIndex ) - { - if ( valueDisplayGroup != null ) - { - valueDisplayGroup.setValueForObjectAtIndex( - aValue, aRowIndex, valueKey ); + * Sets a value for the specified index. This method is called by the + * TableAssocation after a cell has been edited. This implementation simply sets + * the value in the display group bound to the value aspect. + */ + public void setValueAtIndex(Object aValue, int aRowIndex) { + if (valueDisplayGroup != null) { + valueDisplayGroup.setValueForObjectAtIndex(aValue, aRowIndex, valueKey); } } - - /** - * Returns whether this column should be sorted when the - * user clicks on the column header. Defaults to true. - */ - public boolean isSortable() - { - return sortable; - } - - /** - * Sets whether this column should be sorted when the - * user clicks on the column header. - */ - public void setSortable( boolean isSortable ) - { - sortable = isSortable; - } - - /** - * Returns whether this column should be sorted - * in a case sensitive manner. Defaults to true. - */ - public boolean isSortCaseSensitive() - { - return sortCaseSensitive; - } - - /** - * Sets whether this column should be sorted when - * in a case sensitive manner. - * If false, the column contents should be string values. - */ - public void setSortCaseSensitive( boolean isCaseSensitive ) - { - sortCaseSensitive = isCaseSensitive; - } /** - * Called by the TableAssociation to determine whether - * the value at the specified row is editable. - * This is determined by the binding of the Editable aspect, - * looking at the value of the corresponding index in that - * display group. Note: because the display group may - * not have the same number if items, the selected index is - * used if the editable display group is not the same as the - * the value display group. - */ - public boolean isEditableAtRow( int aRowIndex ) - { - if ( editableKey == null ) return false; + * Returns whether this column should be sorted when the user clicks on the + * column header. Defaults to true. + */ + public boolean isSortable() { + return sortable; + } + + /** + * Sets whether this column should be sorted when the user clicks on the column + * header. + */ + public void setSortable(boolean isSortable) { + sortable = isSortable; + } + + /** + * Returns whether this column should be sorted in a case sensitive manner. + * Defaults to true. + */ + public boolean isSortCaseSensitive() { + return sortCaseSensitive; + } + + /** + * Sets whether this column should be sorted when in a case sensitive manner. If + * false, the column contents should be string values. + */ + public void setSortCaseSensitive(boolean isCaseSensitive) { + sortCaseSensitive = isCaseSensitive; + } + + /** + * Called by the TableAssociation to determine whether the value at the + * specified row is editable. This is determined by the binding of the Editable + * aspect, looking at the value of the corresponding index in that display + * group. Note: because the display group may not have the same number if items, + * the selected index is used if the editable display group is not the same as + * the the value display group. + */ + public boolean isEditableAtRow(int aRowIndex) { + if (editableKey == null) + return false; Object value = null; - if ( editableDisplayGroup != null ) - { + if (editableDisplayGroup != null) { // if using the same group for both, return the value for the index - if ( editableDisplayGroup.equals( valueDisplayGroup ) ) - { - value = - editableDisplayGroup.valueForObjectAtIndex( aRowIndex, editableKey ); - } - else // using an external display group to determine editability + if (editableDisplayGroup.equals(valueDisplayGroup)) { + value = editableDisplayGroup.valueForObjectAtIndex(aRowIndex, editableKey); + } else // using an external display group to determine editability { // ignore index and use the selected object value from display group - value = - editableDisplayGroup.selectedObjectValueForKey( editableKey ); + value = editableDisplayGroup.selectedObjectValueForKey(editableKey); } - } - else - { + } else { // treat bound key without display group as a value - value = editableKey; + value = editableKey; } - if ( value == null ) return false; // null defaults to false - Boolean result = (Boolean) - ValueConverter.convertObjectToClass( value, Boolean.class ); - if ( result == null ) return true; // non-null defaults to true + if (value == null) + return false; // null defaults to false + Boolean result = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + if (result == null) + return true; // non-null defaults to true return result.booleanValue(); } - - // convenience - - private TableColumn component() - { - return (TableColumn) object(); - } - - /** - * Called by TableAssociation to get a EOSortOrdering suitable - * for the information in this column. - * This implementation returns a EOSortOrdering with the key - * equal to the value aspect's key and the appropriate selector - * for the specified ascending value and the case sensitivity - * of this column. - * Override to customize the sort for your column. - */ - public EOSortOrdering getSortOrdering( boolean isAscending ) - { - if ( isAscending ) - { - if ( isSortCaseSensitive() ) - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareAscending ) ; - } - else - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareCaseInsensitiveAscending ) ; - } - } - else - { - if ( isSortCaseSensitive() ) - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareDescending ) ; - } - else - { - return new EOSortOrdering( - valueKey, - EOSortOrdering.CompareCaseInsensitiveDescending ) ; - } - } - } - - /** - * Returns the one-based index of this assocation's sort ordering - * in the specified list of orderings. If the sign of the returned - * value is negative, the ordering is descending. If the return - * value is zero, no matching ordering was found. - */ - protected int getIndexOfMatchingOrdering( List orderings ) - { - // find index of matching ordering - int index = 0; - EOSortOrdering ordering = null; - Iterator i = orderings.iterator(); - while ( i.hasNext() ) - { - index++; - ordering = (EOSortOrdering) i.next(); - if ( ordering.key().equals( valueKey ) ) - { - // determine ascending or descending - if ( getSortOrdering( true ).equals( ordering ) ) - { - return index; - } - else - if ( getSortOrdering( false ).equals( ordering ) ) - { - return -index; - } - } - } - return 0; - - } - - /** - * Called by TableAssociation to draw some indicator in the - * specified rectangle using the specified graphics to indicate - * the specified sort state. The rectangle corresponds to the - * bounds of the column header. - * This implementation draws a small transparent gray triangle at - * the right edge of the bounding rectangle. - * Override to do something different or to do nothing at all. - */ - protected void drawSortIndicator( Rectangle aBoundingRectangle, - Graphics aGraphicsContext, List orderings ) - { - int index = getIndexOfMatchingOrdering( orderings ); - if ( index == 0 ) return; - - boolean isAscending = ( index > 0 ); - index = Math.abs( index ); - - // turn on anti-aliasing - if ( aGraphicsContext instanceof Graphics2D ) - { - ((Graphics2D)aGraphicsContext).setRenderingHint( - RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); - } - - Rectangle r = new Rectangle( aBoundingRectangle ); - - // resize to a right-justified square, sides equal to height - r.setBounds( r.x + r.width - r.height, r.y, r.height, r.height ); - - // resize to about a third smaller - int portion = r.height / 3; - r.grow( -portion, -portion ); - - // transparencies cause java2d printing to rasterize, - // resulting in excessive memory usage and print time. - // aGraphicsContext.setColor( new Color( 0, 0, 0, 255 / (index*2) ) ); - aGraphicsContext.setColor( getSortIndicatorColor( index ) ); - - Polygon triangle; - if ( !isAscending ) - { - triangle = new Polygon( - new int[] { r.x, r.x+r.width/2, r.x+r.width }, - new int[] { r.y, r.y+r.height, r.y }, 3 ); - } - else - { - triangle = new Polygon( - new int[] { r.x, r.x+r.width/2, r.x+r.width }, - new int[] { r.y+r.height, r.y, r.y+r.height }, 3 ); - } - aGraphicsContext.fillPolygon( triangle ); - } - - /** - * Returns a color to be used by the sort indicator based on the index - * of the sorting column. The goal of this method is to make the color - * appear lighter and lighter, the "less" primary the sort order for this - * column is. This can be acheives simply though a "transparent" color, - * however, during printing of the corresponding table, java print - * kicks into "raster" based printing when printing a component with - * a transparent color instead of "vector" based printing. Raster - * based printing can take up to 20-30 times longer to print than - * vector printing and consume several times the amount of memory. - * Raster-based printing should be avoided at all costs if the a component - * is to be printed (as of Java 1.3.1). - * @param index The "sort" index of the associated table column. The higher - * the index, the lighter the color will be. An index of 0 will - * return null. - * @return The color to use when rendering the sort indicator. - */ - protected static Color getSortIndicatorColor( int index ) - { - if ( index == 0 ) return null; - - // Create the color list if not already created. - if ( sortIndicatorColorList == null ) - { - // Default size to 13 elements, it would be extremely rare that a - // user sorts more than 12 columns at a time (although possible). - // (Index 0 is not used.) - sortIndicatorColorList = new Color[ 13 ]; - } - - // Get the color out of the color list. Use the index directly as - // an index into an ordered list. If the color has already been - // created for that index, then return it, otherwise create the color. - if ( ( index < sortIndicatorColorList.length ) && - ( sortIndicatorColorList[ index ] != null ) ) - { - return sortIndicatorColorList[ index ]; - } - - // The following logic performs the same affect as the above - // transparent color, without actually using a transparent color. - // Start with the table header's background color and derive a color - // that is "darker" than that color. Any color this logic creates will - // be between those two colors. - Color lightColor = java.awt.SystemColor.control; - Color darkColor = lightColor.darker().darker(); - - // Make the light color (the upper bound) a little darker, so that even - // the lightest triangle will still be slightly visible. - lightColor = new Color( - Math.max( ( int )( lightColor.getRed() * 0.9), 0 ), - Math.max( ( int )( lightColor.getGreen() * 0.9), 0 ), - Math.max( ( int )( lightColor.getBlue() * 0.9), 0) ); - - // Subtract the light color from the dark color. This is the range - // between the two colors. - Color difference = new Color( lightColor.getRed() - darkColor.getRed(), - lightColor.getGreen() - darkColor.getGreen(), - lightColor.getBlue() - darkColor.getBlue() ); - - // If the index is 1, user the dark color as is. Otherwise scale the - // color closer and closer to the lighter color as the index gets - // biggger and bigger. - if ( index > 1 ) - { - float factor = ( float )Math.pow( 0.5, ( index - 1 ) ); - darkColor = new Color( - Math.max( lightColor.getRed() - ( int )( difference.getRed() * factor ), 0 ), - Math.max( lightColor.getGreen() - ( int )( difference.getGreen() * factor ), 0 ), - Math.max( lightColor.getBlue() - ( int )( difference.getBlue() * factor ), 0 ) ); - } - - // Cache the created color in the color list for this index. - if ( index >= sortIndicatorColorList.length ) - { - // The color list is too small, create a new larger list with - // some padding for even larger indicies. - Color[] oldList = sortIndicatorColorList; - sortIndicatorColorList = new Color[ index + 5 ]; - System.arraycopy( oldList, 0, sortIndicatorColorList, 0, oldList.length ); - } - sortIndicatorColorList[ index ] = darkColor; - - return darkColor; - } + + // convenience + + private TableColumn component() { + return (TableColumn) object(); + } + + /** + * Called by TableAssociation to get a EOSortOrdering suitable for the + * information in this column. This implementation returns a EOSortOrdering with + * the key equal to the value aspect's key and the appropriate selector for the + * specified ascending value and the case sensitivity of this column. Override + * to customize the sort for your column. + */ + public EOSortOrdering getSortOrdering(boolean isAscending) { + if (isAscending) { + if (isSortCaseSensitive()) { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareAscending); + } else { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareCaseInsensitiveAscending); + } + } else { + if (isSortCaseSensitive()) { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareDescending); + } else { + return new EOSortOrdering(valueKey, EOSortOrdering.CompareCaseInsensitiveDescending); + } + } + } + + /** + * Returns the one-based index of this assocation's sort ordering in the + * specified list of orderings. If the sign of the returned value is negative, + * the ordering is descending. If the return value is zero, no matching ordering + * was found. + */ + protected int getIndexOfMatchingOrdering(List orderings) { + // find index of matching ordering + int index = 0; + EOSortOrdering ordering = null; + Iterator i = orderings.iterator(); + while (i.hasNext()) { + index++; + ordering = (EOSortOrdering) i.next(); + if (ordering.key().equals(valueKey)) { + // determine ascending or descending + if (getSortOrdering(true).equals(ordering)) { + return index; + } else if (getSortOrdering(false).equals(ordering)) { + return -index; + } + } + } + return 0; + + } + + /** + * Called by TableAssociation to draw some indicator in the specified rectangle + * using the specified graphics to indicate the specified sort state. The + * rectangle corresponds to the bounds of the column header. This implementation + * draws a small transparent gray triangle at the right edge of the bounding + * rectangle. Override to do something different or to do nothing at all. + */ + protected void drawSortIndicator(Rectangle aBoundingRectangle, Graphics aGraphicsContext, List orderings) { + int index = getIndexOfMatchingOrdering(orderings); + if (index == 0) + return; + + boolean isAscending = (index > 0); + index = Math.abs(index); + + // turn on anti-aliasing + if (aGraphicsContext instanceof Graphics2D) { + ((Graphics2D) aGraphicsContext).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + Rectangle r = new Rectangle(aBoundingRectangle); + + // resize to a right-justified square, sides equal to height + r.setBounds(r.x + r.width - r.height, r.y, r.height, r.height); + + // resize to about a third smaller + int portion = r.height / 3; + r.grow(-portion, -portion); + + // transparencies cause java2d printing to rasterize, + // resulting in excessive memory usage and print time. + // aGraphicsContext.setColor( new Color( 0, 0, 0, 255 / (index*2) ) ); + aGraphicsContext.setColor(getSortIndicatorColor(index)); + + Polygon triangle; + if (!isAscending) { + triangle = new Polygon(new int[] { r.x, r.x + r.width / 2, r.x + r.width }, + new int[] { r.y, r.y + r.height, r.y }, 3); + } else { + triangle = new Polygon(new int[] { r.x, r.x + r.width / 2, r.x + r.width }, + new int[] { r.y + r.height, r.y, r.y + r.height }, 3); + } + aGraphicsContext.fillPolygon(triangle); + } + + /** + * Returns a color to be used by the sort indicator based on the index of the + * sorting column. The goal of this method is to make the color appear lighter + * and lighter, the "less" primary the sort order for this column is. This can + * be acheives simply though a "transparent" color, however, during printing of + * the corresponding table, java print kicks into "raster" based printing when + * printing a component with a transparent color instead of "vector" based + * printing. Raster based printing can take up to 20-30 times longer to print + * than vector printing and consume several times the amount of memory. + * Raster-based printing should be avoided at all costs if the a component is to + * be printed (as of Java 1.3.1). + * + * @param index The "sort" index of the associated table column. The higher the + * index, the lighter the color will be. An index of 0 will return + * null. + * @return The color to use when rendering the sort indicator. + */ + protected static Color getSortIndicatorColor(int index) { + if (index == 0) + return null; + + // Create the color list if not already created. + if (sortIndicatorColorList == null) { + // Default size to 13 elements, it would be extremely rare that a + // user sorts more than 12 columns at a time (although possible). + // (Index 0 is not used.) + sortIndicatorColorList = new Color[13]; + } + + // Get the color out of the color list. Use the index directly as + // an index into an ordered list. If the color has already been + // created for that index, then return it, otherwise create the color. + if ((index < sortIndicatorColorList.length) && (sortIndicatorColorList[index] != null)) { + return sortIndicatorColorList[index]; + } + + // The following logic performs the same affect as the above + // transparent color, without actually using a transparent color. + // Start with the table header's background color and derive a color + // that is "darker" than that color. Any color this logic creates will + // be between those two colors. + Color lightColor = java.awt.SystemColor.control; + Color darkColor = lightColor.darker().darker(); + + // Make the light color (the upper bound) a little darker, so that even + // the lightest triangle will still be slightly visible. + lightColor = new Color(Math.max((int) (lightColor.getRed() * 0.9), 0), + Math.max((int) (lightColor.getGreen() * 0.9), 0), Math.max((int) (lightColor.getBlue() * 0.9), 0)); + + // Subtract the light color from the dark color. This is the range + // between the two colors. + Color difference = new Color(lightColor.getRed() - darkColor.getRed(), + lightColor.getGreen() - darkColor.getGreen(), lightColor.getBlue() - darkColor.getBlue()); + + // If the index is 1, user the dark color as is. Otherwise scale the + // color closer and closer to the lighter color as the index gets + // biggger and bigger. + if (index > 1) { + float factor = (float) Math.pow(0.5, (index - 1)); + darkColor = new Color(Math.max(lightColor.getRed() - (int) (difference.getRed() * factor), 0), + Math.max(lightColor.getGreen() - (int) (difference.getGreen() * factor), 0), + Math.max(lightColor.getBlue() - (int) (difference.getBlue() * factor), 0)); + } + + // Cache the created color in the color list for this index. + if (index >= sortIndicatorColorList.length) { + // The color list is too small, create a new larger list with + // some padding for even larger indicies. + Color[] oldList = sortIndicatorColorList; + sortIndicatorColorList = new Color[index + 5]; + System.arraycopy(oldList, 0, sortIndicatorColorList, 0, oldList.length); + } + sortIndicatorColorList[index] = darkColor; + + return darkColor; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.16 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.16 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.15 2002/08/22 15:42:49 mpowers - * No longer using transparency to render sort indicator (see comments). + * Revision 1.15 2002/08/22 15:42:49 mpowers No longer using transparency to + * render sort indicator (see comments). * - * Revision 1.14 2002/04/12 21:05:57 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.14 2002/04/12 21:05:57 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.13 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.13 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.12 2002/03/04 22:11:43 mpowers - * Darkened the sort indicator to better differentiate the first sort. + * Revision 1.12 2002/03/04 22:11:43 mpowers Darkened the sort indicator to + * better differentiate the first sort. * - * Revision 1.11 2002/03/04 03:58:17 mpowers - * Refined table header click behavior. + * Revision 1.11 2002/03/04 03:58:17 mpowers Refined table header click + * behavior. * - * Revision 1.10 2002/03/01 15:42:00 mpowers - * Table column headers now always show their sort indicator. - * A third table-column click clears the sort for that column. + * Revision 1.10 2002/03/01 15:42:00 mpowers Table column headers now always + * show their sort indicator. A third table-column click clears the sort for + * that column. * - * Revision 1.9 2002/02/28 23:01:39 mpowers - * TableColumnAssociations add and remove themselves from the TableAssociation - * when their connection is established and broken respectively. - * TableAssociations now break connection if they have no column associations. + * Revision 1.9 2002/02/28 23:01:39 mpowers TableColumnAssociations add and + * remove themselves from the TableAssociation when their connection is + * established and broken respectively. TableAssociations now break connection + * if they have no column associations. * - * Revision 1.8 2001/06/05 16:03:56 mpowers - * Flipped the triangle to be consistent with Aqua. + * Revision 1.8 2001/06/05 16:03:56 mpowers Flipped the triangle to be + * consistent with Aqua. * - * Revision 1.7 2001/03/09 22:09:22 mpowers - * Now better handling jdk1.1 for rendering the column header. + * Revision 1.7 2001/03/09 22:09:22 mpowers Now better handling jdk1.1 for + * rendering the column header. * - * Revision 1.6 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.6 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.5 2001/01/12 19:11:56 mpowers - * Fixed table column click sorting. + * Revision 1.5 2001/01/12 19:11:56 mpowers Fixed table column click sorting. * - * Revision 1.4 2001/01/12 17:20:30 mpowers - * Moved EOSortOrdering creation to ColumnAssociation. + * Revision 1.4 2001/01/12 17:20:30 mpowers Moved EOSortOrdering creation to + * ColumnAssociation. * - * Revision 1.3 2001/01/11 21:55:57 mpowers - * Implemented sort indicator for table column headers. + * Revision 1.3 2001/01/11 21:55:57 mpowers Implemented sort indicator for table + * column headers. * - * Revision 1.2 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.2 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.1.1.1 2000/12/21 15:49:03 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:03 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java index 6aa27c3..ea2b47a 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TextAssociation.java @@ -54,1159 +54,923 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TextAssociation binds JTextComponents and other objects -* with getText() and setText() methods to a display group. -* Note that JLabels are supported with both the Text and -* Icon aspects. -* Bindings are: -*
    -*
  • value: a property convertable to/from a string
  • -*
  • editable: a boolean property that determines whether -* the user can edit the text in the field
  • -*
  • enabled: a boolean property that determines whether -* the user can select the text in the field
  • -*
  • visible: a boolean property that determines whether -* the field is visible
  • -*
  • label: a boolean property that determines whether -* field should appear as a read-only, selectable label
  • -*
  • icon: a property that returns a Swing icon, for use -* with JLabels and other components with setIcon() methods. -* If bound to a static string, the string will be used to -* load an image resource from the selected object's class.
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TextAssociation extends EOAssociation - implements FocusListener, ActionListener, DocumentListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect, VisibleAspect, LabelAspect, IconAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text", "enabled", "editable", "visible" - } ); - - private final static NSSelector getText = - new NSSelector( "getText" ); - private final static NSSelector setText = - new NSSelector( "setText", - new Class[] { String.class } ); - private final static NSSelector getDocument = - new NSSelector( "getDocument" ); - private final static NSSelector setIcon = - new NSSelector( "setIcon", - new Class[] { Icon.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); - - // null handling - protected boolean wasNull; - protected static final String EMPTY_STRING = ""; - - // dirty handling - protected boolean needsUpdate; - protected boolean hasDocument; - protected boolean isListening; - - // formatting - protected Format format; - - // on-the-fly validation - protected boolean activeUpdate; - - // type conversion - protected Class lastKnownType; - - // cache the value aspect - private EODisplayGroup valueDisplayGroup; - private String valueKey; - - // hacky flags needed for no activeUpdate - private boolean pleaseIgnoreNextChange = false; - private boolean pleaseAcceptNextChange = false; - private boolean externallyChanged = true; - - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public TextAssociation ( Object anObject ) - { - super( anObject ); - wasNull = false; - needsUpdate = false; - activeUpdate = true; - hasDocument = false; - isListening = true; - valueDisplayGroup = null; - valueKey = null; - format = null; - lastKnownType = null; - - // register for idle notifications - NSSelector handleNotification = - new NSSelector( "handleNotification", - new Class[] { NSNotification.class } ); - NSNotificationCenter.defaultCenter().addObserver( - this, handleNotification, null, this ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setText.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { - valueDisplayGroup = aDisplayGroup; - valueKey = aKey; - } - super.bindAspect( anAspect, aDisplayGroup, aKey ); - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { - Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); - } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); - } - hasDocument = false; - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).addDocumentListener( this ); - hasDocument = true; - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); - } - - super.establishConnection(); - - // forces update from bindings - subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); - } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); - } - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).removeDocumentListener( this ); - } - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - public void objectWillChange( Object anObject ) - { - super.objectWillChange( anObject ); - externallyChanged = true; - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged() - { - if ( pleaseIgnoreNextChange ) - { - pleaseIgnoreNextChange = false; - externallyChanged = false; - return; - } - - externallyChanged = true; - - Object component = object(); - EODisplayGroup displayGroup; - String key; - Object value; - - // value aspect - displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) ); - } - - // if activeUpdate or we are not the editing association - if ( activeUpdate || displayGroup.editingAssociation() != this || pleaseAcceptNextChange ) - { - pleaseAcceptNextChange = false; - key = valueKey; - - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = previousValue; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && previousValue != null - && !currentValue.toString().equals( previousValue.toString() ) ) { - value = null; - break; - } - - } // end while - - } else { - - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - // null handling - if ( value == null ) - { - wasNull = true; - value = EMPTY_STRING; - lastKnownType = null; - } - else - { - wasNull = false; - lastKnownType = value.getClass(); - if ( format() != null ) - { - try - { - value = format().format( value ); - } - catch ( IllegalArgumentException exc ) - { - value = value.toString(); - } - } - } - - - try - { - if ( needToReadValueFromDisplayGroup( value.toString(), getText ) ) - { - // No need to listen for any events that might get fired - // while setting the text since we are the one setting it. - boolean wasListening = isListening; - isListening = false; - - // setText is an expensive operation - setText.invoke( component, value.toString() ); - - isListening = wasListening; - needsUpdate = false; - } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } - } - } - - // icon aspect - displayGroup = displayGroupForAspect( IconAspect ); - key = displayGroupKeyForAspect( IconAspect ); - if ( key != null ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group - // as a resource to be loaded from the selected class. - value = null; - Object o = displayGroup.selectedObject(); - if ( o != null ) - { - URL url = o.getClass().getResource( key ); - if ( url != null ) - { - value = new ImageIcon( url ); - } - } - } - - try - { - setIcon.invoke( component, value ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); - } - } - - // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( ((Component)component).isEnabled() != converted.booleanValue() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } - } - - // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((JTextComponent)component).isEditable() ) - { - ((JTextComponent)component).setEditable( converted.booleanValue() ); - } - } - } - - // visible aspect - displayGroup = displayGroupForAspect( VisibleAspect ); - key = displayGroupKeyForAspect( VisibleAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((Component)component).isVisible() ) - { - ((Component)component).setVisible( converted.booleanValue() ); - } - } - } - - // label aspect - displayGroup = displayGroupForAspect( LabelAspect ); - key = displayGroupKeyForAspect( LabelAspect ); - - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group as a value - value = key; - } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() ) - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - areaToLabel( (JTextArea) component ); - } - else - { - fieldToLabel( (JTextComponent) component ); - } - } - } - else - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - labelToArea( (JTextArea) component ); - } - else - { - labelToField( (JTextComponent ) component ); - } - } - } - } - } - } - - private void fieldToLabel( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting - - aTextField.setEditable(false); - aTextField.setOpaque(false); - - // Set the border, colors and font to that of a label - - //LookAndFeel.installBorder(aTextField, "Label.border"); - aTextField.setBorder( null ); - - LookAndFeel.installColorsAndFont(aTextField, - "Label.background", - "Label.foreground", - "Label.font"); - } - - private void labelToField( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting - - aTextField.setEditable(true); - aTextField.setOpaque(true); - - // Set the border, colors and font to that of a label - - LookAndFeel.installBorder(aTextField, "TextField.border"); - - LookAndFeel.installColorsAndFont(aTextField, - "TextField.background", - "TextField.foreground", - "TextField.font"); - } - - private void areaToLabel( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting - - aTextArea.setLineWrap(true); - aTextArea.setWrapStyleWord(true); - aTextArea.setEditable(false); - - // Set the text area's border, colors and font to - // that of a label - - //LookAndFeel.installBorder(aTextArea, "Label.border"); - aTextArea.setBorder( null ); - - LookAndFeel.installColorsAndFont(aTextArea, - "Label.background", - "Label.foreground", - "Label.font"); - - } - - private void labelToArea( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting - - aTextArea.setEditable(true); - - // Set the border, colors and font to that of a label - - LookAndFeel.installBorder(aTextArea, "TextArea.border"); - - LookAndFeel.installColorsAndFont(aTextArea, - "TextArea.background", - "TextArea.foreground", - "TextArea.font"); - } - - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing() - { - pleaseAcceptNextChange = true; - pleaseIgnoreNextChange = false; - return writeValueToDisplayGroup(); - } - - /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - boolean returnValue = true; - if ( hasDocument && !needsUpdate ) return true; - - EODisplayGroup displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - String key = valueKey; - Object component = object(); - Object value = null; - try - { - //if ( getText.implementedByObject( component ) ) - //{ - value = getText.invoke( component ); - //} - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) ) - { - value = null; - } - else - if ( format() != null ) - { - try - { - value = format().parseObject( value.toString() ); - } - catch ( ParseException exc ) - { - String message = exc.getMessage(); - //"That format was not recognized."; - if ( displayGroup.associationFailedToValidateValue( - this, value.toString(), key, exc, message ) ) - { - boolean wasListening = isListening; - isListening = false; - JOptionPane.showMessageDialog( - (Component)component, message ); - isListening = wasListening; - } - needsUpdate = false; - return false; - } - } - - if ( ( lastKnownType != null ) && ( value != null ) ) - { - // convert back to last known type, if necessary/possible - Class type = value.getClass(); - if ( ( type != null ) && ( type != lastKnownType ) ) - { - Object converted = - ValueConverter.convertObjectToClass( - value, lastKnownType ); - if ( converted != null ) - { - value = converted; - } - // else: not possible, ignore - } - } - - needsUpdate = false; - - // only update if the value is different from the one in the display group - if ( ! needToWriteValueToDisplayGroup( value, displayGroup ) ) return true; - - // we might lose focus if display group displays a validation message - boolean wasListening = isListening; - isListening = false; - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - needsUpdate = false; - } - else - { - needsUpdate = false; - returnValue = false; - } - } - isListening = wasListening; - - } - return returnValue; - } - - /** - * Called to determine whether the display group needs to be - * updated. This implementation reads the value from the display - * group and only returns true if the specified value is different. - * This is done as an optimization since writes are more expensive - * than reads. Override to customize this behavior. - */ - protected boolean needToWriteValueToDisplayGroup( - Object aValue, EODisplayGroup aDisplayGroup ) - { - Object existingValue = aDisplayGroup.selectedObjectValueForKey( valueKey ); - if ( aDisplayGroup.selectedObjects().size() == 1 ) - { - if ( existingValue == aValue ) return false; - if ( ( existingValue != null ) && ( existingValue.equals( aValue ) ) ) return false; - if ( ( aValue != null ) && ( aValue.equals( existingValue ) ) ) return false; - } - return true; - } - - /** - * Called to determine whether the controlled component needs to be - * updated. This implementation reads the value from the selector - * and only returns true if the specified value is different. - * This is done as an optimization since updating the component - * can be an expensive operation. Override to customize this behavior. - */ - protected boolean needToReadValueFromDisplayGroup( - Object aValue, NSSelector aSelector ) - throws IllegalAccessException, InvocationTargetException, NoSuchMethodException - { - return !aValue.toString().equals( aSelector.invoke( object() ) ); - } - - /** - * Sets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public void setFormat( Format aFormat ) - { - format = aFormat; - } - - /** - * Gets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public Format format() - { - return format; - } - - /** - * Returns whether the text association is configured to actively - * update the model in response to changes in the component. - */ - public boolean isActiveUpdate() - { - return activeUpdate; - } - - /** - * Sets whether the text association should actively - * update the model in response to changes in the component. - * Default is true. False indicates that the model will be updated - * only when the component loses focus or fires an action event. - */ - public void setActiveUpdate( boolean isActiveUpdate ) - { - activeUpdate = isActiveUpdate; - } - - // interface ActionListener - - /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - if ( ! isListening ) return; - if ( needsUpdate ) - { - pleaseAcceptNextChange = true; // needed if activeUpdate = false - writeValueToDisplayGroup(); - } - } - - // interface FocusListener - - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - if ( ! isListening ) return; - - pleaseAcceptNextChange = true; - externallyChanged = true; - - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); - } - } - } - - /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! isListening ) return; - if ( endEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - else - { - // probably should notify of a validation error here, - } - } - - /** - * Queues a notification to PostWhenIdle. - */ - protected void queueUpdate(DocumentEvent e) - { - if ( e.getDocument() instanceof DefaultStyledDocument ) - { - if ( e instanceof AbstractDocument.DefaultDocumentEvent ) - { - int docLength = e.getDocument().getLength(); - - if ( ( e.getType().equals( DocumentEvent.EventType.CHANGE ) ) ) - { - if ( e.getOffset() == 0 && e.getLength() == docLength ) - { - // ignore document events for the whole document - // since default styled document broadcasts these - // using invokeLater, and we've already received - // notification about the actual style change. - // see: DefaultStyledDocument.ChangeUpdateRunnable - return; - } - } - } - } - - NSNotificationQueue.defaultQueue().enqueueNotification( - new NSNotification( "TextAssociation.DocumentChanged", this, - new NSDictionary( new Object[] { "event" }, new Object[] { e } ) ), - NSNotificationQueue.PostWhenIdle ); - } - - /** - * Handles idle notification. - */ - public void handleNotification( NSNotification aNotification ) - { - if ( activeUpdate ) - { - writeValueToDisplayGroup(); - } - } - - // interface DocumentListener - - public void insertUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } - - public void removeUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } - - public void changedUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate( e ); - } + * TextAssociation binds JTextComponents and other objects with getText() and + * setText() methods to a display group. Note that JLabels are supported with + * both the Text and Icon aspects. Bindings are: + *
    + *
  • value: a property convertable to/from a string
  • + *
  • editable: a boolean property that determines whether the user can edit + * the text in the field
  • + *
  • enabled: a boolean property that determines whether the user can select + * the text in the field
  • + *
  • visible: a boolean property that determines whether the field is + * visible
  • + *
  • label: a boolean property that determines whether field should appear as + * a read-only, selectable label
  • + *
  • icon: a property that returns a Swing icon, for use with JLabels and + * other components with setIcon() methods. If bound to a static string, the + * string will be used to load an image resource from the selected object's + * class.
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TextAssociation extends EOAssociation implements FocusListener, ActionListener, DocumentListener { + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EnabledAspect, EditableAspect, VisibleAspect, LabelAspect, IconAspect }); + static final NSArray aspectSignatures = new NSArray( + new Object[] { AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text", "enabled", "editable", "visible" }); + + private final static NSSelector getText = new NSSelector("getText"); + private final static NSSelector setText = new NSSelector("setText", new Class[] { String.class }); + private final static NSSelector getDocument = new NSSelector("getDocument"); + private final static NSSelector setIcon = new NSSelector("setIcon", new Class[] { Icon.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); + + // null handling + protected boolean wasNull; + protected static final String EMPTY_STRING = ""; + + // dirty handling + protected boolean needsUpdate; + protected boolean hasDocument; + protected boolean isListening; + + // formatting + protected Format format; + + // on-the-fly validation + protected boolean activeUpdate; + + // type conversion + protected Class lastKnownType; + + // cache the value aspect + private EODisplayGroup valueDisplayGroup; + private String valueKey; + + // hacky flags needed for no activeUpdate + private boolean pleaseIgnoreNextChange = false; + private boolean pleaseAcceptNextChange = false; + private boolean externallyChanged = true; + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public TextAssociation(Object anObject) { + super(anObject); + wasNull = false; + needsUpdate = false; + activeUpdate = true; + hasDocument = false; + isListening = true; + valueDisplayGroup = null; + valueKey = null; + format = null; + lastKnownType = null; + + // register for idle notifications + NSSelector handleNotification = new NSSelector("handleNotification", new Class[] { NSNotification.class }); + NSNotificationCenter.defaultCenter().addObserver(this, handleNotification, null, this); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setText.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { + valueDisplayGroup = aDisplayGroup; + valueKey = aKey; + } + super.bindAspect(anAspect, aDisplayGroup, aKey); + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { + Object component = object(); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); + } + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); + } + hasDocument = false; + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).addDocumentListener(this); + hasDocument = true; + } + } + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); + } + + super.establishConnection(); + + // forces update from bindings + subjectChanged(); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + Object component = object(); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); + } + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); + } + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).removeDocumentListener(this); + } + } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); + } + super.breakConnection(); + } + + public void objectWillChange(Object anObject) { + super.objectWillChange(anObject); + externallyChanged = true; + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + if (pleaseIgnoreNextChange) { + pleaseIgnoreNextChange = false; + externallyChanged = false; + return; + } + + externallyChanged = true; + + Object component = object(); + EODisplayGroup displayGroup; + String key; + Object value; + + // value aspect + displayGroup = valueDisplayGroup; + if (displayGroup != null) { + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(valueKey)); + } + + // if activeUpdate or we are not the editing association + if (activeUpdate || displayGroup.editingAssociation() != this || pleaseAcceptNextChange) { + pleaseAcceptNextChange = false; + key = valueKey; + + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = previousValue; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && previousValue != null + && !currentValue.toString().equals(previousValue.toString())) { + value = null; + break; + } + + } // end while + + } else { + + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // null handling + if (value == null) { + wasNull = true; + value = EMPTY_STRING; + lastKnownType = null; + } else { + wasNull = false; + lastKnownType = value.getClass(); + if (format() != null) { + try { + value = format().format(value); + } catch (IllegalArgumentException exc) { + value = value.toString(); + } + } + } + + try { + if (needToReadValueFromDisplayGroup(value.toString(), getText)) { + // No need to listen for any events that might get fired + // while setting the text since we are the one setting it. + boolean wasListening = isListening; + isListening = false; + + // setText is an expensive operation + setText.invoke(component, value.toString()); + + isListening = wasListening; + needsUpdate = false; + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } + } + } + + // icon aspect + displayGroup = displayGroupForAspect(IconAspect); + key = displayGroupKeyForAspect(IconAspect); + if (key != null) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group + // as a resource to be loaded from the selected class. + value = null; + Object o = displayGroup.selectedObject(); + if (o != null) { + URL url = o.getClass().getResource(key); + if (url != null) { + value = new ImageIcon(url); + } + } + } + + try { + setIcon.invoke(component, value); + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); + } + } + + // enabled aspect + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (((Component) component).isEnabled() != converted.booleanValue()) { + ((Component) component).setEnabled(converted.booleanValue()); + } + } + + // editable aspect + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((JTextComponent) component).isEditable()) { + ((JTextComponent) component).setEditable(converted.booleanValue()); + } + } + } + + // visible aspect + displayGroup = displayGroupForAspect(VisibleAspect); + key = displayGroupKeyForAspect(VisibleAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((Component) component).isVisible()) { + ((Component) component).setVisible(converted.booleanValue()); + } + } + } + + // label aspect + displayGroup = displayGroupForAspect(LabelAspect); + key = displayGroupKeyForAspect(LabelAspect); + + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group as a value + value = key; + } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue()) { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + areaToLabel((JTextArea) component); + } else { + fieldToLabel((JTextComponent) component); + } + } + } else { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + labelToArea((JTextArea) component); + } else { + labelToField((JTextComponent) component); + } + } + } + } + } + } + + private void fieldToLabel(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting + + aTextField.setEditable(false); + aTextField.setOpaque(false); + + // Set the border, colors and font to that of a label + + // LookAndFeel.installBorder(aTextField, "Label.border"); + aTextField.setBorder(null); + + LookAndFeel.installColorsAndFont(aTextField, "Label.background", "Label.foreground", "Label.font"); + } + + private void labelToField(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting + + aTextField.setEditable(true); + aTextField.setOpaque(true); + + // Set the border, colors and font to that of a label + + LookAndFeel.installBorder(aTextField, "TextField.border"); + + LookAndFeel.installColorsAndFont(aTextField, "TextField.background", "TextField.foreground", "TextField.font"); + } + + private void areaToLabel(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting + + aTextArea.setLineWrap(true); + aTextArea.setWrapStyleWord(true); + aTextArea.setEditable(false); + + // Set the text area's border, colors and font to + // that of a label + + // LookAndFeel.installBorder(aTextArea, "Label.border"); + aTextArea.setBorder(null); + + LookAndFeel.installColorsAndFont(aTextArea, "Label.background", "Label.foreground", "Label.font"); + + } + + private void labelToArea(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting + + aTextArea.setEditable(true); + + // Set the border, colors and font to that of a label + + LookAndFeel.installBorder(aTextArea, "TextArea.border"); + + LookAndFeel.installColorsAndFont(aTextArea, "TextArea.background", "TextArea.foreground", "TextArea.font"); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + pleaseAcceptNextChange = true; + pleaseIgnoreNextChange = false; + return writeValueToDisplayGroup(); + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + boolean returnValue = true; + if (hasDocument && !needsUpdate) + return true; + + EODisplayGroup displayGroup = valueDisplayGroup; + if (displayGroup != null) { + String key = valueKey; + Object component = object(); + Object value = null; + try { + // if ( getText.implementedByObject( component ) ) + // { + value = getText.invoke(component); + // } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + if ((wasNull) && (EMPTY_STRING.equals(value))) { + value = null; + } else if (format() != null) { + try { + value = format().parseObject(value.toString()); + } catch (ParseException exc) { + String message = exc.getMessage(); + // "That format was not recognized."; + if (displayGroup.associationFailedToValidateValue(this, value.toString(), key, exc, message)) { + boolean wasListening = isListening; + isListening = false; + JOptionPane.showMessageDialog((Component) component, message); + isListening = wasListening; + } + needsUpdate = false; + return false; + } + } + + if ((lastKnownType != null) && (value != null)) { + // convert back to last known type, if necessary/possible + Class type = value.getClass(); + if ((type != null) && (type != lastKnownType)) { + Object converted = ValueConverter.convertObjectToClass(value, lastKnownType); + if (converted != null) { + value = converted; + } + // else: not possible, ignore + } + } + + needsUpdate = false; + + // only update if the value is different from the one in the display group + if (!needToWriteValueToDisplayGroup(value, displayGroup)) + return true; + + // we might lose focus if display group displays a validation message + boolean wasListening = isListening; + isListening = false; + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (displayGroup.setValueForObjectAtIndex(value, index, key)) { + needsUpdate = false; + } else { + needsUpdate = false; + returnValue = false; + } + } + isListening = wasListening; + + } + return returnValue; + } + + /** + * Called to determine whether the display group needs to be updated. This + * implementation reads the value from the display group and only returns true + * if the specified value is different. This is done as an optimization since + * writes are more expensive than reads. Override to customize this behavior. + */ + protected boolean needToWriteValueToDisplayGroup(Object aValue, EODisplayGroup aDisplayGroup) { + Object existingValue = aDisplayGroup.selectedObjectValueForKey(valueKey); + if (aDisplayGroup.selectedObjects().size() == 1) { + if (existingValue == aValue) + return false; + if ((existingValue != null) && (existingValue.equals(aValue))) + return false; + if ((aValue != null) && (aValue.equals(existingValue))) + return false; + } + return true; + } + + /** + * Called to determine whether the controlled component needs to be updated. + * This implementation reads the value from the selector and only returns true + * if the specified value is different. This is done as an optimization since + * updating the component can be an expensive operation. Override to customize + * this behavior. + */ + protected boolean needToReadValueFromDisplayGroup(Object aValue, NSSelector aSelector) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return !aValue.toString().equals(aSelector.invoke(object())); + } + + /** + * Sets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public void setFormat(Format aFormat) { + format = aFormat; + } + + /** + * Gets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public Format format() { + return format; + } + + /** + * Returns whether the text association is configured to actively update the + * model in response to changes in the component. + */ + public boolean isActiveUpdate() { + return activeUpdate; + } + + /** + * Sets whether the text association should actively update the model in + * response to changes in the component. Default is true. False indicates that + * the model will be updated only when the component loses focus or fires an + * action event. + */ + public void setActiveUpdate(boolean isActiveUpdate) { + activeUpdate = isActiveUpdate; + } + + // interface ActionListener + + /** + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + if (!isListening) + return; + if (needsUpdate) { + pleaseAcceptNextChange = true; // needed if activeUpdate = false + writeValueToDisplayGroup(); + } + } + + // interface FocusListener + + /** + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + if (!isListening) + return; + + pleaseAcceptNextChange = true; + externallyChanged = true; + + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); + } + } + } + + /** + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!isListening) + return; + if (endEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } else { + // probably should notify of a validation error here, + } + } + + /** + * Queues a notification to PostWhenIdle. + */ + protected void queueUpdate(DocumentEvent e) { + if (e.getDocument() instanceof DefaultStyledDocument) { + if (e instanceof AbstractDocument.DefaultDocumentEvent) { + int docLength = e.getDocument().getLength(); + + if ((e.getType().equals(DocumentEvent.EventType.CHANGE))) { + if (e.getOffset() == 0 && e.getLength() == docLength) { + // ignore document events for the whole document + // since default styled document broadcasts these + // using invokeLater, and we've already received + // notification about the actual style change. + // see: DefaultStyledDocument.ChangeUpdateRunnable + return; + } + } + } + } + + NSNotificationQueue.defaultQueue().enqueueNotification( + new NSNotification("TextAssociation.DocumentChanged", this, + new NSDictionary(new Object[] { "event" }, new Object[] { e })), + NSNotificationQueue.PostWhenIdle); + } + + /** + * Handles idle notification. + */ + public void handleNotification(NSNotification aNotification) { + if (activeUpdate) { + writeValueToDisplayGroup(); + } + } + + // interface DocumentListener + + public void insertUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } + + public void changedUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(e); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.41 2004/02/05 02:18:18 mpowers - * Now setting border to null to new Aqua LAF behaves. + * Revision 1.41 2004/02/05 02:18:18 mpowers Now setting border to null to new + * Aqua LAF behaves. * - * Revision 1.40 2004/01/28 22:47:56 mpowers - * un-activeUpdate was brokne. + * Revision 1.40 2004/01/28 22:47:56 mpowers un-activeUpdate was brokne. * - * Revision 1.39 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.39 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.38 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.38 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.37 2003/02/06 16:21:34 mpowers - * Fix for activeUpdate: no longer bothering with editing context's changes. + * Revision 1.37 2003/02/06 16:21:34 mpowers Fix for activeUpdate: no longer + * bothering with editing context's changes. * - * Revision 1.36 2002/10/24 18:19:24 mpowers - * Bug fix - thanks to dwang. + * Revision 1.36 2002/10/24 18:19:24 mpowers Bug fix - thanks to dwang. * - * Revision 1.35 2002/08/02 19:19:30 mpowers - * Added control points for when to read or write from the display group. - * Added flags needed to fix problems with non-activeUpdate and commit key. + * Revision 1.35 2002/08/02 19:19:30 mpowers Added control points for when to + * read or write from the display group. Added flags needed to fix problems with + * non-activeUpdate and commit key. * - * Revision 1.33 2002/03/08 23:18:01 mpowers - * Added visible aspect. + * Revision 1.33 2002/03/08 23:18:01 mpowers Added visible aspect. * - * Revision 1.32 2002/03/06 16:13:53 mpowers - * Yet another fix for style document changes: swing's DefaultStyledDocument - * using an invoke later to launch a final StyleChanged event, which occurs - * after the TextAssociation has reestablished itself as a document listener, - * causing the item to be marked dirty. We're now handling this case. + * Revision 1.32 2002/03/06 16:13:53 mpowers Yet another fix for style document + * changes: swing's DefaultStyledDocument using an invoke later to launch a + * final StyleChanged event, which occurs after the TextAssociation has + * reestablished itself as a document listener, causing the item to be marked + * dirty. We're now handling this case. * - * Revision 1.31 2002/03/04 22:10:37 mpowers - * Supressing active update only marks dirty when contents have changed. + * Revision 1.31 2002/03/04 22:10:37 mpowers Supressing active update only marks + * dirty when contents have changed. * - * Revision 1.30 2002/02/23 16:19:12 mpowers - * Now only marking an editing context as dirty if it's not already dirty. + * Revision 1.30 2002/02/23 16:19:12 mpowers Now only marking an editing context + * as dirty if it's not already dirty. * - * Revision 1.29 2002/02/19 18:38:29 mpowers - * Minor optimization: activeUpdate now checked in handleNotification. + * Revision 1.29 2002/02/19 18:38:29 mpowers Minor optimization: activeUpdate + * now checked in handleNotification. * - * Revision 1.28 2002/02/19 16:36:47 mpowers - * Better support for active update: objects are now marked as changed - * even though the model itself is not updated -- this allows editing - * context itself to be marked as having changes to be saved. + * Revision 1.28 2002/02/19 16:36:47 mpowers Better support for active update: + * objects are now marked as changed even though the model itself is not updated + * -- this allows editing context itself to be marked as having changes to be + * saved. * - * Revision 1.27 2002/01/23 19:50:11 mpowers - * Fix for a null pointer when value is null and last known type is not. - * (from dwang) + * Revision 1.27 2002/01/23 19:50:11 mpowers Fix for a null pointer when value + * is null and last known type is not. (from dwang) * - * Revision 1.26 2002/01/14 19:37:22 mpowers - * Fix for NPE when value is null and auto update is false. + * Revision 1.26 2002/01/14 19:37:22 mpowers Fix for NPE when value is null and + * auto update is false. * - * Revision 1.25 2001/12/10 03:16:11 mpowers - * Fixed bug with isListening when no items are in display group. + * Revision 1.25 2001/12/10 03:16:11 mpowers Fixed bug with isListening when no + * items are in display group. * - * Revision 1.24 2001/11/16 19:14:51 mpowers - * Brought back the idea of configuring whether updates occur on each change. + * Revision 1.24 2001/11/16 19:14:51 mpowers Brought back the idea of + * configuring whether updates occur on each change. * - * Revision 1.23 2001/11/08 20:06:06 mpowers - * Now performing type-conversion as a convenience. + * Revision 1.23 2001/11/08 20:06:06 mpowers Now performing type-conversion as a + * convenience. * - * Revision 1.22 2001/11/04 18:24:20 mpowers - * Better handling for non-string values when bulk-editing. + * Revision 1.22 2001/11/04 18:24:20 mpowers Better handling for non-string + * values when bulk-editing. * - * Revision 1.21 2001/11/01 15:53:34 mpowers - * Now that NSNotificationQueue correctly implements PostWhenIdle, we can - * finally discard our use of Swing's Timer in favor of using the queue - * to coalesce document changed events. + * Revision 1.21 2001/11/01 15:53:34 mpowers Now that NSNotificationQueue + * correctly implements PostWhenIdle, we can finally discard our use of Swing's + * Timer in favor of using the queue to coalesce document changed events. * - * Revision 1.20 2001/10/26 19:58:06 mpowers - * Better handling for non-string types. We were testing with equals with the - * new value against the existing value in the component. Now we convert - * the new value to a string before comparing. Fixes case for properties - * of non-String types, like StringBuffer. + * Revision 1.20 2001/10/26 19:58:06 mpowers Better handling for non-string + * types. We were testing with equals with the new value against the existing + * value in the component. Now we convert the new value to a string before + * comparing. Fixes case for properties of non-String types, like StringBuffer. * - * Revision 1.19 2001/09/30 21:57:14 mpowers - * Timers were not getting cleaned up if breakConnection was called - * before the timer got a chance to fire. + * Revision 1.19 2001/09/30 21:57:14 mpowers Timers were not getting cleaned up + * if breakConnection was called before the timer got a chance to fire. * - * Revision 1.18 2001/08/22 15:42:26 mpowers - * Added support for JTextComponent label-izing. + * Revision 1.18 2001/08/22 15:42:26 mpowers Added support for JTextComponent + * label-izing. * - * Revision 1.17 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.17 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.16 2001/07/17 19:53:37 mpowers - * Made some private fields protected for benefit of subclassers. + * Revision 1.16 2001/07/17 19:53:37 mpowers Made some private fields protected + * for benefit of subclassers. * - * Revision 1.15 2001/06/30 14:59:36 mpowers - * LabelAspect now sets the text field's opaque setting. + * Revision 1.15 2001/06/30 14:59:36 mpowers LabelAspect now sets the text + * field's opaque setting. * - * Revision 1.14 2001/06/29 14:54:08 mpowers - * Another fix for timers - timers were definitely causing a memory leak. + * Revision 1.14 2001/06/29 14:54:08 mpowers Another fix for timers - timers + * were definitely causing a memory leak. * - * Revision 1.13 2001/06/26 21:37:19 mpowers - * Fixed a null pointer in the new key timer scheme. + * Revision 1.13 2001/06/26 21:37:19 mpowers Fixed a null pointer in the new key + * timer scheme. * - * Revision 1.12 2001/06/25 14:46:03 mpowers - * Fixed a memory leak involving the use of timers. + * Revision 1.12 2001/06/25 14:46:03 mpowers Fixed a memory leak involving the + * use of timers. * - * Revision 1.11 2001/06/01 19:14:59 mpowers - * Text association's enabled aspect is now more discriminating. + * Revision 1.11 2001/06/01 19:14:59 mpowers Text association's enabled aspect + * is now more discriminating. * - * Revision 1.10 2001/05/18 21:07:24 mpowers - * Changed the way we handle failure to update object value. + * Revision 1.10 2001/05/18 21:07:24 mpowers Changed the way we handle failure + * to update object value. * - * Revision 1.9 2001/03/13 21:39:58 mpowers - * Improved validation handling. + * Revision 1.9 2001/03/13 21:39:58 mpowers Improved validation handling. * - * Revision 1.8 2001/03/12 12:49:10 mpowers - * Improved validation handling. - * Having a formatter disables auto-updating. + * Revision 1.8 2001/03/12 12:49:10 mpowers Improved validation handling. Having + * a formatter disables auto-updating. * - * Revision 1.7 2001/03/09 22:08:13 mpowers - * Now handling any objects that have a valid Document. - * No longer checking enabled before updating the enabled state. + * Revision 1.7 2001/03/09 22:08:13 mpowers Now handling any objects that have a + * valid Document. No longer checking enabled before updating the enabled state. * - * Revision 1.6 2001/03/07 19:57:32 mpowers - * Fixed paste error in IconAspect. + * Revision 1.6 2001/03/07 19:57:32 mpowers Fixed paste error in IconAspect. * - * Revision 1.4 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.4 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.3 2001/01/31 19:12:33 mpowers - * Implemented auto-updating in TextComponent. + * Revision 1.3 2001/01/31 19:12:33 mpowers Implemented auto-updating in + * TextComponent. * - * Revision 1.2 2001/01/10 15:53:58 mpowers - * Preventing a null pointer exception if getText were to return null, - * which doesn't happen for JTextFields but might happen for other objects. + * Revision 1.2 2001/01/10 15:53:58 mpowers Preventing a null pointer exception + * if getText were to return null, which doesn't happen for JTextFields but + * might happen for other objects. * - * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers Contributing wotonomy. * - * Revision 1.13 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.13 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java index 49879e9..a6e993c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TimedTextAssociation.java @@ -48,75 +48,51 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TimedTextAssociation works like TextAssociation, -* but instead of using a delayed event to update the -* model, it uses a timer so that the model is only -* updated if the user pauses typing for some short interval. -* This is useful when the update and/or re-read of the model -* is a costly operation. -* Bindings are: -*
    -*
  • value: a property convertable to/from a string
  • -*
  • editable: a boolean property that determines whether -* the user can edit the text in the field
  • -*
  • enabled: a boolean property that determines whether -* the user can select the text in the field
  • -*
  • label: a boolean property that determines whether -* field should appear as a read-only, selectable label
  • -*
  • icon: a property that returns a Swing icon, for use -* with JLabels and other components with setIcon() methods. -* If bound to a static string, the string will be used to -* load an image resource from the selected object's class.
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TimedTextAssociation extends EOAssociation - implements FocusListener, ActionListener, DocumentListener -{ - //TODO: need to refactor this so that it can subclass text association. - //This implementation is basically a branch from the v1.20 TextAssociation. - - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature, - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "text", "enabled", "editable" - } ); - - private final static NSSelector getText = - new NSSelector( "getText" ); - private final static NSSelector setText = - new NSSelector( "setText", - new Class[] { String.class } ); - private final static NSSelector getDocument = - new NSSelector( "getDocument" ); - private final static NSSelector setIcon = - new NSSelector( "setIcon", - new Class[] { Icon.class } ); - private final static NSSelector addActionListener = - new NSSelector( "addActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector removeActionListener = - new NSSelector( "removeActionListener", - new Class[] { ActionListener.class } ); - private final static NSSelector addFocusListener = - new NSSelector( "addFocusListener", - new Class[] { FocusListener.class } ); - private final static NSSelector removeFocusListener = - new NSSelector( "removeFocusListener", - new Class[] { FocusListener.class } ); + * TimedTextAssociation works like TextAssociation, but instead of using a + * delayed event to update the model, it uses a timer so that the model is only + * updated if the user pauses typing for some short interval. This is useful + * when the update and/or re-read of the model is a costly operation. Bindings + * are: + *
    + *
  • value: a property convertable to/from a string
  • + *
  • editable: a boolean property that determines whether the user can edit + * the text in the field
  • + *
  • enabled: a boolean property that determines whether the user can select + * the text in the field
  • + *
  • label: a boolean property that determines whether field should appear as + * a read-only, selectable label
  • + *
  • icon: a property that returns a Swing icon, for use with JLabels and + * other components with setIcon() methods. If bound to a static string, the + * string will be used to load an image resource from the selected object's + * class.
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TimedTextAssociation extends EOAssociation implements FocusListener, ActionListener, DocumentListener { + // TODO: need to refactor this so that it can subclass text association. + // This implementation is basically a branch from the v1.20 TextAssociation. + + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EnabledAspect, EditableAspect, LabelAspect, IconAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature, + AttributeToOneAspectSignature, AttributeToOneAspectSignature, AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "text", "enabled", "editable" }); + + private final static NSSelector getText = new NSSelector("getText"); + private final static NSSelector setText = new NSSelector("setText", new Class[] { String.class }); + private final static NSSelector getDocument = new NSSelector("getDocument"); + private final static NSSelector setIcon = new NSSelector("setIcon", new Class[] { Icon.class }); + private final static NSSelector addActionListener = new NSSelector("addActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector removeActionListener = new NSSelector("removeActionListener", + new Class[] { ActionListener.class }); + private final static NSSelector addFocusListener = new NSSelector("addFocusListener", + new Class[] { FocusListener.class }); + private final static NSSelector removeFocusListener = new NSSelector("removeFocusListener", + new Class[] { FocusListener.class }); // null handling protected boolean wasNull; @@ -125,227 +101,186 @@ public class TimedTextAssociation extends EOAssociation // dirty handling protected boolean needsUpdate; protected boolean hasDocument; - protected boolean isListening; + protected boolean isListening; // formatting protected Format format; - // cache the value aspect - private EODisplayGroup valueDisplayGroup; - private String valueKey; - - // coalescing document events - protected boolean autoUpdating; - protected int interval = 400; // adjust as needed - protected Timer keyTimer; - - // NOTE: a new key timer is created for each use and - // is disposed when the timer is stopped. - // Swing's Timer class is kept in a static list of timers - // and each retains a strong reference to their listeners. - // This caused a memory leak as associations typically - // refer to their controlled component which is referred - // to by its parents and so on until no application window - // will ever get garbage collected. yikes. - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public TimedTextAssociation ( Object anObject ) - { - super( anObject ); + // cache the value aspect + private EODisplayGroup valueDisplayGroup; + private String valueKey; + + // coalescing document events + protected boolean autoUpdating; + protected int interval = 400; // adjust as needed + protected Timer keyTimer; + + // NOTE: a new key timer is created for each use and + // is disposed when the timer is stopped. + // Swing's Timer class is kept in a static list of timers + // and each retains a strong reference to their listeners. + // This caused a memory leak as associations typically + // refer to their controlled component which is referred + // to by its parents and so on until no application window + // will ever get garbage collected. yikes. + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public TimedTextAssociation(Object anObject) { + super(anObject); wasNull = false; needsUpdate = false; hasDocument = false; - isListening = true; - valueDisplayGroup = null; - valueKey = null; - - autoUpdating = true; - keyTimer = null; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setText.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ValueAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ValueAspect.equals( anAspect ) ) - { + isListening = true; + valueDisplayGroup = null; + valueKey = null; + + autoUpdating = true; + keyTimer = null; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setText.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ValueAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ValueAspect.equals(anAspect)) { valueDisplayGroup = aDisplayGroup; valueKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * attempts to add this class as an ActionListener - * and as a FocusListener to the specified object. - */ - public void establishConnection () - { + /** + * Establishes a connection between this association and the controlled object. + * This implementation attempts to add this class as an ActionListener and as a + * FocusListener to the specified object. + */ + public void establishConnection() { Object component = object(); - try - { - if ( addActionListener.implementedByObject( component ) ) - { - addActionListener.invoke( component, this ); + try { + if (addActionListener.implementedByObject(component)) { + addActionListener.invoke(component, this); } - if ( addFocusListener.implementedByObject( component ) ) - { - addFocusListener.invoke( component, this ); + if (addFocusListener.implementedByObject(component)) { + addFocusListener.invoke(component, this); } - hasDocument = false; - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).addDocumentListener( this ); - hasDocument = true; - } + hasDocument = false; + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).addDocumentListener(this); + hasDocument = true; + } } - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while establishing connection", exc ); + } catch (Exception exc) { + throw new WotonomyException("Error while establishing connection", exc); } - super.establishConnection(); + super.establishConnection(); // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { Object component = object(); - try - { - if ( removeActionListener.implementedByObject( component ) ) - { - removeActionListener.invoke( component, this ); + try { + if (removeActionListener.implementedByObject(component)) { + removeActionListener.invoke(component, this); } - if ( removeFocusListener.implementedByObject( component ) ) - { - removeFocusListener.invoke( component, this ); + if (removeFocusListener.implementedByObject(component)) { + removeFocusListener.invoke(component, this); } - if ( getDocument.implementedByObject( component ) ) - { - Object document = getDocument.invoke( component ); - if ( document instanceof Document ) - { - ((Document)document).removeDocumentListener( this ); - } + if (getDocument.implementedByObject(component)) { + Object document = getDocument.invoke(component); + if (document instanceof Document) { + ((Document) document).removeDocumentListener(this); + } } + } catch (Exception exc) { + throw new WotonomyException("Error while breaking connection", exc); } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while breaking connection", exc ); - } - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { Object component = object(); EODisplayGroup displayGroup; String key; @@ -353,677 +288,538 @@ public class TimedTextAssociation extends EOAssociation // value aspect displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - if ( component instanceof Component ) - { - ((Component)component).setEnabled( - displayGroup.enabledToSetSelectedObjectValueForKey( valueKey ) ); - } - + if (displayGroup != null) { + if (component instanceof Component) { + ((Component) component).setEnabled(displayGroup.enabledToSetSelectedObjectValueForKey(valueKey)); + } + key = valueKey; - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - // if there's only one object selected. - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking the size of selected objects in displayGroup - - // convert value to string - if ( value == null ) - { - wasNull = true; - value = EMPTY_STRING; - } - else - { - wasNull = false; - if ( format() != null ) - { - try - { - value = format().format( value ); - } - catch ( IllegalArgumentException exc ) - { - value = value.toString(); - } - } - } - - - try - { - if ( ! value.toString().equals( getText.invoke( component ) ) ) - { - // No need to listen for any events that might get fired - // while setting the text since we are the one setting it. - boolean wasListening = isListening; - isListening = false; - - // setText is an expensive operation - setText.invoke( component, value.toString() ); - - isListening = wasListening; - needsUpdate = false; - } + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + // if there's only one object selected. + value = displayGroup.selectedObjectValueForKey(key); + } // end checking the size of selected objects in displayGroup + + // convert value to string + if (value == null) { + wasNull = true; + value = EMPTY_STRING; + } else { + wasNull = false; + if (format() != null) { + try { + value = format().format(value); + } catch (IllegalArgumentException exc) { + value = value.toString(); + } + } } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); + + try { + if (!value.toString().equals(getText.invoke(component))) { + // No need to listen for any events that might get fired + // while setting the text since we are the one setting it. + boolean wasListening = isListening; + isListening = false; + + // setText is an expensive operation + setText.invoke(component, value.toString()); + + isListening = wasListening; + needsUpdate = false; + } + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); } } // icon aspect - displayGroup = displayGroupForAspect( IconAspect ); - key = displayGroupKeyForAspect( IconAspect ); - if ( key != null ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { - // treat bound key without display group - // as a resource to be loaded from the selected class. - value = null; - Object o = displayGroup.selectedObject(); - if ( o != null ) - { - URL url = o.getClass().getResource( key ); - if ( url != null ) - { - value = new ImageIcon( url ); - } - } + displayGroup = displayGroupForAspect(IconAspect); + key = displayGroupKeyForAspect(IconAspect); + if (key != null) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { + // treat bound key without display group + // as a resource to be loaded from the selected class. + value = null; + Object o = displayGroup.selectedObject(); + if (o != null) { + URL url = o.getClass().getResource(key); + if (url != null) { + value = new ImageIcon(url); + } + } } - try - { - setIcon.invoke( component, value ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error while updating component connection", exc ); + try { + setIcon.invoke(component, value); + } catch (Exception exc) { + throw new WotonomyException("Error while updating component connection", exc); } } // enabled aspect - displayGroup = displayGroupForAspect( EnabledAspect ); - key = displayGroupKeyForAspect( EnabledAspect ); - if ( ( key != null ) - && ( component instanceof Component ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EnabledAspect); + key = displayGroupKeyForAspect(EnabledAspect); + if ((key != null) && (component instanceof Component)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = null; - if ( value != null ) - { - converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - } - if ( converted == null ) converted = Boolean.FALSE; - if ( ((Component)component).isEnabled() != converted.booleanValue() ) - { - ((Component)component).setEnabled( converted.booleanValue() ); - } + Boolean converted = null; + if (value != null) { + converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + } + if (converted == null) + converted = Boolean.FALSE; + if (((Component) component).isEnabled() != converted.booleanValue()) { + ((Component) component).setEnabled(converted.booleanValue()); + } } // editable aspect - displayGroup = displayGroupForAspect( EditableAspect ); - key = displayGroupKeyForAspect( EditableAspect ); - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(EditableAspect); + key = displayGroupKeyForAspect(EditableAspect); + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() != ((JTextComponent)component).isEditable() ) - { - ((JTextComponent)component).setEditable( converted.booleanValue() ); + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue() != ((JTextComponent) component).isEditable()) { + ((JTextComponent) component).setEditable(converted.booleanValue()); } } } // label aspect - displayGroup = displayGroupForAspect( LabelAspect ); - key = displayGroupKeyForAspect( LabelAspect ); - - if ( ( key != null ) - && ( component instanceof JTextComponent ) ) - { - if ( displayGroup != null ) - { - value = - displayGroup.selectedObjectValueForKey( key ); - } - else - { + displayGroup = displayGroupForAspect(LabelAspect); + key = displayGroupKeyForAspect(LabelAspect); + + if ((key != null) && (component instanceof JTextComponent)) { + if (displayGroup != null) { + value = displayGroup.selectedObjectValueForKey(key); + } else { // treat bound key without display group as a value value = key; } - Boolean converted = (Boolean) - ValueConverter.convertObjectToClass( - value, Boolean.class ); - - if ( converted != null ) - { - if ( converted.booleanValue() ) - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - areaToLabel( (JTextArea) component ); - } - else - { - fieldToLabel( (JTextComponent) component ); - } - } + Boolean converted = (Boolean) ValueConverter.convertObjectToClass(value, Boolean.class); + + if (converted != null) { + if (converted.booleanValue()) { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + areaToLabel((JTextArea) component); + } else { + fieldToLabel((JTextComponent) component); + } + } + } else { + if (component instanceof JTextComponent) { + if (component instanceof JTextArea) { + labelToArea((JTextArea) component); + } else { + labelToField((JTextComponent) component); + } + } } - else - { - if ( component instanceof JTextComponent ) - { - if ( component instanceof JTextArea ) - { - labelToArea( (JTextArea) component ); - } - else - { - labelToField( (JTextComponent ) component ); - } - } - } } } - } + } - private void fieldToLabel( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting + private void fieldToLabel(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting - aTextField.setEditable(false); - aTextField.setOpaque(false); + aTextField.setEditable(false); + aTextField.setOpaque(false); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextField, "Label.border"); + LookAndFeel.installBorder(aTextField, "Label.border"); - LookAndFeel.installColorsAndFont(aTextField, - "Label.background", - "Label.foreground", - "Label.font"); - } + LookAndFeel.installColorsAndFont(aTextField, "Label.background", "Label.foreground", "Label.font"); + } - private void labelToField( JTextComponent aTextField ) - { - // turn on wrapping and disable editing and highlighting + private void labelToField(JTextComponent aTextField) { + // turn on wrapping and disable editing and highlighting - aTextField.setEditable(true); - aTextField.setOpaque(true); + aTextField.setEditable(true); + aTextField.setOpaque(true); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextField, "TextField.border"); + LookAndFeel.installBorder(aTextField, "TextField.border"); - LookAndFeel.installColorsAndFont(aTextField, - "TextField.background", - "TextField.foreground", - "TextField.font"); - } + LookAndFeel.installColorsAndFont(aTextField, "TextField.background", "TextField.foreground", "TextField.font"); + } - private void areaToLabel( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting + private void areaToLabel(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting - aTextArea.setLineWrap(true); - aTextArea.setWrapStyleWord(true); - aTextArea.setEditable(false); + aTextArea.setLineWrap(true); + aTextArea.setWrapStyleWord(true); + aTextArea.setEditable(false); - // Set the text area's border, colors and font to - // that of a label + // Set the text area's border, colors and font to + // that of a label - LookAndFeel.installBorder(aTextArea, "Label.border"); + LookAndFeel.installBorder(aTextArea, "Label.border"); - LookAndFeel.installColorsAndFont(aTextArea, - "Label.background", - "Label.foreground", - "Label.font"); + LookAndFeel.installColorsAndFont(aTextArea, "Label.background", "Label.foreground", "Label.font"); } - private void labelToArea( JTextArea aTextArea ) - { - // turn on wrapping and disable editing and highlighting + private void labelToArea(JTextArea aTextArea) { + // turn on wrapping and disable editing and highlighting - aTextArea.setEditable(true); + aTextArea.setEditable(true); - // Set the border, colors and font to that of a label + // Set the border, colors and font to that of a label - LookAndFeel.installBorder(aTextArea, "TextArea.border"); + LookAndFeel.installBorder(aTextArea, "TextArea.border"); - LookAndFeel.installColorsAndFont(aTextArea, - "TextArea.background", - "TextArea.foreground", - "TextArea.font"); + LookAndFeel.installColorsAndFont(aTextArea, "TextArea.background", "TextArea.foreground", "TextArea.font"); } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - if ( keyTimer != null ) - { - keyTimer.stop(); - keyTimer.removeActionListener( this ); - keyTimer = null; - } + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + if (keyTimer != null) { + keyTimer.stop(); + keyTimer.removeActionListener(this); + keyTimer = null; + } return writeValueToDisplayGroup(); - } + } /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueToDisplayGroup() - { - boolean returnValue = true; - if ( hasDocument && !needsUpdate ) return true; - - EODisplayGroup displayGroup = valueDisplayGroup; - if ( displayGroup != null ) - { - String key = valueKey; - Object component = object(); - Object value = null; - try - { - //if ( getText.implementedByObject( component ) ) - //{ - value = getText.invoke( component ); - //} - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Error updating display group", exc ); - } - - if ( ( wasNull ) && ( EMPTY_STRING.equals( value ) ) ) - { - value = null; - } - else - if ( format() != null ) - { - try - { - value = format().parseObject( value.toString() ); - } - catch ( ParseException exc ) - { - String message = exc.getMessage(); - //"That format was not recognized."; - if ( displayGroup.associationFailedToValidateValue( - this, value.toString(), key, exc, message ) ) - { - boolean wasListening = isListening; - isListening = false; - JOptionPane.showMessageDialog( - (Component)component, message ); - isListening = wasListening; - } - needsUpdate = false; - return false; - } - } - - needsUpdate = false; - - // only update if the value is different from the one in the display group - Object existingValue = displayGroup.selectedObjectValueForKey( key ); - if ( displayGroup.selectedObjects().size() == 1 ) - { - if ( existingValue == value ) return true; - if ( ( existingValue != null ) && ( existingValue.equals( value ) ) ) return true; - if ( ( value != null ) && ( value.equals( existingValue ) ) ) return true; - } - - // we might lose focus if display group displays a validation message - boolean wasListening = isListening; - isListening = false; - - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - isListening = wasListening; - needsUpdate = false; - } - else - { - isListening = wasListening; - needsUpdate = false; - returnValue = false; - } - } - - } - return returnValue; + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueToDisplayGroup() { + boolean returnValue = true; + if (hasDocument && !needsUpdate) + return true; + + EODisplayGroup displayGroup = valueDisplayGroup; + if (displayGroup != null) { + String key = valueKey; + Object component = object(); + Object value = null; + try { + // if ( getText.implementedByObject( component ) ) + // { + value = getText.invoke(component); + // } + } catch (Exception exc) { + throw new WotonomyException("Error updating display group", exc); + } + + if ((wasNull) && (EMPTY_STRING.equals(value))) { + value = null; + } else if (format() != null) { + try { + value = format().parseObject(value.toString()); + } catch (ParseException exc) { + String message = exc.getMessage(); + // "That format was not recognized."; + if (displayGroup.associationFailedToValidateValue(this, value.toString(), key, exc, message)) { + boolean wasListening = isListening; + isListening = false; + JOptionPane.showMessageDialog((Component) component, message); + isListening = wasListening; + } + needsUpdate = false; + return false; + } + } + + needsUpdate = false; + + // only update if the value is different from the one in the display group + Object existingValue = displayGroup.selectedObjectValueForKey(key); + if (displayGroup.selectedObjects().size() == 1) { + if (existingValue == value) + return true; + if ((existingValue != null) && (existingValue.equals(value))) + return true; + if ((value != null) && (value.equals(existingValue))) + return true; + } + + // we might lose focus if display group displays a validation message + boolean wasListening = isListening; + isListening = false; + + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (displayGroup.setValueForObjectAtIndex(value, index, key)) { + isListening = wasListening; + needsUpdate = false; + } else { + isListening = wasListening; + needsUpdate = false; + returnValue = false; + } + } + + } + return returnValue; } /** - * Sets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - * Having a formatter disables auto-updating. - */ - public void setFormat( Format aFormat ) - { + * Sets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. Having a formatter disables + * auto-updating. + */ + public void setFormat(Format aFormat) { format = aFormat; } /** - * Gets the Format that is used to convert values from the display - * group to and from text that is displayed in the component. - */ - public Format format() - { + * Gets the Format that is used to convert values from the display group to and + * from text that is displayed in the component. + */ + public Format format() { return format; } - // interface ActionListener + // interface ActionListener /** - * Updates object on action performed. - */ - public void actionPerformed( ActionEvent evt ) - { - if ( keyTimer != null ) - { - keyTimer.stop(); - keyTimer.removeActionListener( this ); - keyTimer = null; - } - if ( ! isListening ) return; - if ( needsUpdate ) - { - writeValueToDisplayGroup(); - } + * Updates object on action performed. + */ + public void actionPerformed(ActionEvent evt) { + if (keyTimer != null) { + keyTimer.stop(); + keyTimer.removeActionListener(this); + keyTimer = null; + } + if (!isListening) + return; + if (needsUpdate) { + writeValueToDisplayGroup(); + } } - // interface FocusListener + // interface FocusListener /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { - if ( ! isListening ) return; + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { + if (!isListening) + return; Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! isListening ) return; - if ( endEditing() ) - { + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!isListening) + return; + if (endEditing()) { Object o; EODisplayGroup displayGroup; Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); } } - } - else - { + } else { // probably should notify of a validation error here, } - } - - /** - * Returns whether the data model is updated for every change - * in the controlled component. If false, the data is only - * updated on focus lost or the enter key. Default is true. - */ - public boolean isAutoUpdating() - { - if ( format() != null ) return false; - return autoUpdating; - } - - /** - * Sets whether the data model is updated for every change - * in the controlled component. - */ - public void setAutoUpdating( boolean isAutoUpdating ) - { - autoUpdating = isAutoUpdating; - } - - /** - * Triggers the key timer to start. - */ - protected void queueUpdate() - { - if ( isAutoUpdating() ) - { - if ( keyTimer == null ) - { - keyTimer = new Timer( interval, this ); - } - keyTimer.restart(); - } - } - - // interface DocumentListener - - public void insertUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } - - public void removeUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } - - public void changedUpdate(DocumentEvent e) - { - if ( ! isListening ) return; - needsUpdate = true; - queueUpdate(); - } + } + + /** + * Returns whether the data model is updated for every change in the controlled + * component. If false, the data is only updated on focus lost or the enter key. + * Default is true. + */ + public boolean isAutoUpdating() { + if (format() != null) + return false; + return autoUpdating; + } + + /** + * Sets whether the data model is updated for every change in the controlled + * component. + */ + public void setAutoUpdating(boolean isAutoUpdating) { + autoUpdating = isAutoUpdating; + } + + /** + * Triggers the key timer to start. + */ + protected void queueUpdate() { + if (isAutoUpdating()) { + if (keyTimer == null) { + keyTimer = new Timer(interval, this); + } + keyTimer.restart(); + } + } + + // interface DocumentListener + + public void insertUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } + + public void removeUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } + + public void changedUpdate(DocumentEvent e) { + if (!isListening) + return; + needsUpdate = true; + queueUpdate(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2004/01/28 18:34:57 mpowers - * Better handling for enabling. - * Now respecting enabledToSetSelectedObjectValueForKey from display group. + * Revision 1.3 2004/01/28 18:34:57 mpowers Better handling for enabling. Now + * respecting enabledToSetSelectedObjectValueForKey from display group. * - * Revision 1.2 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2001/12/20 18:57:24 mpowers - * (Re-)Contributing TimedTextAssociation. Just like TA, except uses timers. + * Revision 1.1 2001/12/20 18:57:24 mpowers (Re-)Contributing + * TimedTextAssociation. Just like TA, except uses timers. * - * Revision 1.20 2001/10/26 19:58:06 mpowers - * Better handling for non-string types. We were testing with equals with the - * new value against the existing value in the component. Now we convert - * the new value to a string before comparing. Fixes case for properties - * of non-String types, like StringBuffer. + * Revision 1.20 2001/10/26 19:58:06 mpowers Better handling for non-string + * types. We were testing with equals with the new value against the existing + * value in the component. Now we convert the new value to a string before + * comparing. Fixes case for properties of non-String types, like StringBuffer. * - * Revision 1.19 2001/09/30 21:57:14 mpowers - * Timers were not getting cleaned up if breakConnection was called - * before the timer got a chance to fire. + * Revision 1.19 2001/09/30 21:57:14 mpowers Timers were not getting cleaned up + * if breakConnection was called before the timer got a chance to fire. * - * Revision 1.18 2001/08/22 15:42:26 mpowers - * Added support for JTextComponent label-izing. + * Revision 1.18 2001/08/22 15:42:26 mpowers Added support for JTextComponent + * label-izing. * - * Revision 1.17 2001/07/30 16:32:55 mpowers - * Implemented support for bulk-editing. Detail associations will now - * apply changes to all selected objects. + * Revision 1.17 2001/07/30 16:32:55 mpowers Implemented support for + * bulk-editing. Detail associations will now apply changes to all selected + * objects. * - * Revision 1.16 2001/07/17 19:53:37 mpowers - * Made some private fields protected for benefit of subclassers. + * Revision 1.16 2001/07/17 19:53:37 mpowers Made some private fields protected + * for benefit of subclassers. * - * Revision 1.15 2001/06/30 14:59:36 mpowers - * LabelAspect now sets the text field's opaque setting. + * Revision 1.15 2001/06/30 14:59:36 mpowers LabelAspect now sets the text + * field's opaque setting. * - * Revision 1.14 2001/06/29 14:54:08 mpowers - * Another fix for timers - timers were definitely causing a memory leak. + * Revision 1.14 2001/06/29 14:54:08 mpowers Another fix for timers - timers + * were definitely causing a memory leak. * - * Revision 1.13 2001/06/26 21:37:19 mpowers - * Fixed a null pointer in the new key timer scheme. + * Revision 1.13 2001/06/26 21:37:19 mpowers Fixed a null pointer in the new key + * timer scheme. * - * Revision 1.12 2001/06/25 14:46:03 mpowers - * Fixed a memory leak involving the use of timers. + * Revision 1.12 2001/06/25 14:46:03 mpowers Fixed a memory leak involving the + * use of timers. * - * Revision 1.11 2001/06/01 19:14:59 mpowers - * Text association's enabled aspect is now more discriminating. + * Revision 1.11 2001/06/01 19:14:59 mpowers Text association's enabled aspect + * is now more discriminating. * - * Revision 1.10 2001/05/18 21:07:24 mpowers - * Changed the way we handle failure to update object value. + * Revision 1.10 2001/05/18 21:07:24 mpowers Changed the way we handle failure + * to update object value. * - * Revision 1.9 2001/03/13 21:39:58 mpowers - * Improved validation handling. + * Revision 1.9 2001/03/13 21:39:58 mpowers Improved validation handling. * - * Revision 1.8 2001/03/12 12:49:10 mpowers - * Improved validation handling. - * Having a formatter disables auto-updating. + * Revision 1.8 2001/03/12 12:49:10 mpowers Improved validation handling. Having + * a formatter disables auto-updating. * - * Revision 1.7 2001/03/09 22:08:13 mpowers - * Now handling any objects that have a valid Document. - * No longer checking enabled before updating the enabled state. + * Revision 1.7 2001/03/09 22:08:13 mpowers Now handling any objects that have a + * valid Document. No longer checking enabled before updating the enabled state. * - * Revision 1.6 2001/03/07 19:57:32 mpowers - * Fixed paste error in IconAspect. + * Revision 1.6 2001/03/07 19:57:32 mpowers Fixed paste error in IconAspect. * - * Revision 1.4 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.4 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.3 2001/01/31 19:12:33 mpowers - * Implemented auto-updating in TextComponent. + * Revision 1.3 2001/01/31 19:12:33 mpowers Implemented auto-updating in + * TextComponent. * - * Revision 1.2 2001/01/10 15:53:58 mpowers - * Preventing a null pointer exception if getText were to return null, - * which doesn't happen for JTextFields but might happen for other objects. + * Revision 1.2 2001/01/10 15:53:58 mpowers Preventing a null pointer exception + * if getText were to return null, which doesn't happen for JTextFields but + * might happen for other objects. * - * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:08 mpowers Contributing wotonomy. * - * Revision 1.13 2000/12/20 16:25:41 michael - * Added log to all files. + * Revision 1.13 2000/12/20 16:25:41 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java index 728643b..b0e3b09 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeAssociation.java @@ -39,172 +39,144 @@ import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.ui.EODisplayGroup; /** -* TreeAssociation is a TreeModelAssociation further -* customized for JTrees. It binds a JTree to a display group's -* list of displayable objects, each of which may have -* a list of child objects managed by another display -* group, and so on. TreeAssociation works exactly -* like a ListAssociation, with the additional capability -* to specify a "Children" aspect, that will allow child -* objects to be retrieved from a parent display group. -* Note that the children aspect requires the bound -* display group to have a DataSource that can vend a -* DataSource appropriate for the bound key That data -* source is then used to create data sources for -* child nodes, and so on. -* -*
    -* -*
  • titles: a property convertable to a string for -* display in the nodes of the tree. The objects in -* the bound display group will be used to populate the -* initial root nodes of the tree (more accurately, -* children of the offscreen root node in the tree).
  • -* -*
  • children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* The data source of the bound display group is replaced -* a data source that populates the display group with -* the visible nodes in the tree component as determined by -* calling fetchObjectsIntoChildrenGroup. -* If this aspect is not bound, the tree behaves like a list. -*

    -* Binding this aspect with a null display group is the same -* as binding it with the titles display group. -* In this configuration the contents of the titles -* display group will be replaced with the visible nodes in the -* tree component, as specified above, replacing the existing -* data source. -*

    -* In that case, the display groups for the nodes in -* the tree will still use the original data source -* for resolving their children key, and programmatically -* setting the contents of the display group will still -* repopulate the root nodes of the tree. -*
  • -* -*
  • isLeaf: a property of a node value that returns -* a value convertable to a boolean value (aside from -* an actual boolean value, zeroes evaluate to true, -* as does any String containing "yes" or "true" or that -* is convertable to a number equal to zero; other values -* evaluate to false). -*

    -* If the isLeaf aspect is not bound, -* the tree must force nodes to load their children to -* determine whether they are leaf nodes (in effect -* loading the grandchildren for any expanded node). -* If bound, child loading is deferred until the node -* is actually expanded. -*

    -* For example, binding this value to a null -* display group and the key "false" will result in a -* deferred-loading tree that works much like Windows -* Explorer's network volume browser - all nodes appear -* with "pluses" until they are expanded. -*

    -* Note that the display group is ignored: the property -* will be applied directly to the object corresponding -* to the node.
  • -* -*
-* -* All other usage is as TreeModelAssociation. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * TreeAssociation is a TreeModelAssociation further customized for JTrees. It + * binds a JTree to a display group's list of displayable objects, each of which + * may have a list of child objects managed by another display group, and so on. + * TreeAssociation works exactly like a ListAssociation, with the additional + * capability to specify a "Children" aspect, that will allow child objects to + * be retrieved from a parent display group. Note that the children aspect + * requires the bound display group to have a DataSource that can vend a + * DataSource appropriate for the bound key That data source is then used to + * create data sources for child nodes, and so on. + * + *
    + * + *
  • titles: a property convertable to a string for display in the nodes of + * the tree. The objects in the bound display group will be used to populate the + * initial root nodes of the tree (more accurately, children of the offscreen + * root node in the tree).
  • + * + *
  • children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. The data source of the bound display group is replaced a + * data source that populates the display group with the visible nodes in the + * tree component as determined by calling fetchObjectsIntoChildrenGroup. If + * this aspect is not bound, the tree behaves like a list.
    + *
    + * Binding this aspect with a null display group is the same as binding it with + * the titles display group. In this configuration the contents of the titles + * display group will be replaced with the visible nodes in the tree component, + * as specified above, replacing the existing data source.
    + *
    + * In that case, the display groups for the nodes in the tree will still use the + * original data source for resolving their children key, and programmatically + * setting the contents of the display group will still repopulate the root + * nodes of the tree.
  • + * + *
  • isLeaf: a property of a node value that returns a value convertable to a + * boolean value (aside from an actual boolean value, zeroes evaluate to true, + * as does any String containing "yes" or "true" or that is convertable to a + * number equal to zero; other values evaluate to false).
    + *
    + * If the isLeaf aspect is not bound, the tree must force nodes to load their + * children to determine whether they are leaf nodes (in effect loading the + * grandchildren for any expanded node). If bound, child loading is deferred + * until the node is actually expanded.
    + *
    + * For example, binding this value to a null display group and the key "false" + * will result in a deferred-loading tree that works much like Windows + * Explorer's network volume browser - all nodes appear with "pluses" until they + * are expanded.
    + *
    + * Note that the display group is ignored: the property will be applied directly + * to the object corresponding to the node.
  • + * + *
+ * + * All other usage is as TreeModelAssociation. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class TreeAssociation extends TreeModelAssociation - implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable -{ - private boolean isExpanding; - private Vector nodeQueue; - private Vector structureQueue; - private boolean isRunning; - - /** - * Constructor expecting a JTree. - */ - public TreeAssociation ( Object anObject ) - { - super( anObject ); - init(); - } - - /** - * Constructor expecting a JTree or similar component - * and specifying a label for the root node. - */ - public TreeAssociation( Object anObject, Object aRootLabel ) - { - super( anObject ); - init(); - rootLabel = aRootLabel; - rootNode.setUserObject( aRootLabel ); - } - - // convenience - private JTree component() - { - return (JTree) object(); - } - - /** - * Called by both constructors. - */ - protected void init() - { - isExpanding = false; - isRunning = false; - nodeQueue = new Vector(); - structureQueue = new Vector(); - component().addFocusListener( this ); - component().addTreeExpansionListener( this ); - component().addTreeWillExpandListener( this ); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof JTree ); - } - - /** - * Overridden to not fire events during initial population. - */ - public void establishConnection () - { - isExpanding = true; - super.establishConnection(); - isExpanding = false; - } - - // interface TreeSelectionListener - - public void valueChanged(TreeSelectionEvent e) - { - if ( ! isListening ) return; + implements FocusListener, TreeExpansionListener, TreeWillExpandListener, Runnable { + private boolean isExpanding; + private Vector nodeQueue; + private Vector structureQueue; + private boolean isRunning; + + /** + * Constructor expecting a JTree. + */ + public TreeAssociation(Object anObject) { + super(anObject); + init(); + } + + /** + * Constructor expecting a JTree or similar component and specifying a label for + * the root node. + */ + public TreeAssociation(Object anObject, Object aRootLabel) { + super(anObject); + init(); + rootLabel = aRootLabel; + rootNode.setUserObject(aRootLabel); + } + + // convenience + private JTree component() { + return (JTree) object(); + } + + /** + * Called by both constructors. + */ + protected void init() { + isExpanding = false; + isRunning = false; + nodeQueue = new Vector(); + structureQueue = new Vector(); + component().addFocusListener(this); + component().addTreeExpansionListener(this); + component().addTreeWillExpandListener(this); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof JTree); + } + + /** + * Overridden to not fire events during initial population. + */ + public void establishConnection() { + isExpanding = true; + super.establishConnection(); + isExpanding = false; + } + + // interface TreeSelectionListener + + public void valueChanged(TreeSelectionEvent e) { + if (!isListening) + return; // NOTE: This approach causes focus rectangle to perceptibly trail // the selection rectangle, presumably because we're called after the // new selection has been processed but before the focus is processed. // Users don't like it, so we're going with a preference to use // invokeLater. -/* - // paint immediately before updating the display group and - // before any potentially lengthy second-order effects happen: - // this improves user-perceived responsiveness of big apps - if ( object() instanceof javax.swing.JComponent ) - { - javax.swing.JComponent component = (javax.swing.JComponent)object(); - component.paintImmediately( component.getBounds() ); - } - selectFromSelectionModel(); -*/ + /* + * // paint immediately before updating the display group and // before any + * potentially lengthy second-order effects happen: // this improves + * user-perceived responsiveness of big apps if ( object() instanceof + * javax.swing.JComponent ) { javax.swing.JComponent component = + * (javax.swing.JComponent)object(); component.paintImmediately( + * component.getBounds() ); } selectFromSelectionModel(); + */ // NOTE: This approach uses invoke later to cause the update of // the display group (which could be lengthly if that in turn @@ -214,369 +186,291 @@ public class TreeAssociation extends TreeModelAssociation // will have to do a similar invoke later if they check the display // group. - Runnable select = new Runnable() - { - public void run() - { - selectFromSelectionModel(); - } - }; - - if ( selectionPaintedImmediately ) - { - if ( object() instanceof java.awt.Component ) - { - ((java.awt.Component)object()).repaint(); - } - EventQueue.invokeLater( select ); - } - else - { - select.run(); - } - } - - /** - * Overridden to check whether the node is visible - * in the tree on screen. Offscreen in a scrollpane - * does not count. - */ - public boolean isVisible(Object node) - { - JTree tree = (JTree) object(); - TreePath path = ((DisplayGroupNode)node).treePath(); - if ( tree.isVisible( path ) ) - { - Rectangle rowRect = tree.getPathBounds( path ); - if ( rowRect != null ) - { - Rectangle visible = tree.getVisibleRect(); - if ( visible != null ) - { + Runnable select = new Runnable() { + public void run() { + selectFromSelectionModel(); + } + }; + + if (selectionPaintedImmediately) { + if (object() instanceof java.awt.Component) { + ((java.awt.Component) object()).repaint(); + } + EventQueue.invokeLater(select); + } else { + select.run(); + } + } + + /** + * Overridden to check whether the node is visible in the tree on screen. + * Offscreen in a scrollpane does not count. + */ + public boolean isVisible(Object node) { + JTree tree = (JTree) object(); + TreePath path = ((DisplayGroupNode) node).treePath(); + if (tree.isVisible(path)) { + Rectangle rowRect = tree.getPathBounds(path); + if (rowRect != null) { + Rectangle visible = tree.getVisibleRect(); + if (visible != null) { //System.out.println( "isVisible: intersects: " + visible.intersects( rowRect ) ); - return visible.intersects( rowRect ); - } - } - } + return visible.intersects(rowRect); + } + } + } //System.out.println( "isVisible: false" ); - return false; - } - - /** - * Fires a tree nodes changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesChanged(final Object source, - final Object[] path, - final int[] childIndices, - final Object[] children) - { - if ( !isExpanding ) - { - for ( int i = 0; i < children.length; i++ ) - { - nodeQueue.add( children[i] ); - } - if ( !isRunning ) - { - isRunning = true; - EventQueue.invokeLater( this ); - } - } - } - - /** - * Fires a tree nodes inserted event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesInserted(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - super.fireTreeNodesInserted( source, path, childIndices, children ); - EventQueue.invokeLater( this ); - } - } - - /** - * Fires a tree nodes removed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesRemoved(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - super.fireTreeNodesRemoved( source, path, childIndices, children ); - EventQueue.invokeLater( this ); - } - } - - /** - * Fires a tree structure changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeStructureChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - if ( !isExpanding ) - { - structureQueue.add( path[path.length-1] ); - if ( !isRunning ) - { - isRunning = true; - EventQueue.invokeLater( this ); - } - } - } - - /** - * Overridden to return all visible rows in the tree. - */ - public NSArray objectsFetchedIntoChildrenGroup() - { - JTree tree = (JTree) object(); - NSMutableArray objectList = new NSMutableArray(); - - int count = tree.getRowCount(); - for ( int i = 0; i < count; i++ ) - { - objectList.add( - ((DisplayGroupNode) tree.getPathForRow( i ).getLastPathComponent()).object() ); - } + return false; + } + + /** + * Fires a tree nodes changed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesChanged(final Object source, final Object[] path, final int[] childIndices, + final Object[] children) { + if (!isExpanding) { + for (int i = 0; i < children.length; i++) { + nodeQueue.add(children[i]); + } + if (!isRunning) { + isRunning = true; + EventQueue.invokeLater(this); + } + } + } + + /** + * Fires a tree nodes inserted event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + super.fireTreeNodesInserted(source, path, childIndices, children); + EventQueue.invokeLater(this); + } + } + + /** + * Fires a tree nodes removed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + super.fireTreeNodesRemoved(source, path, childIndices, children); + EventQueue.invokeLater(this); + } + } + + /** + * Fires a tree structure changed event to all listeners. Provided as a + * convenience if you need to make manual changes to the tree model. + */ + public void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + if (!isExpanding) { + structureQueue.add(path[path.length - 1]); + if (!isRunning) { + isRunning = true; + EventQueue.invokeLater(this); + } + } + } + + /** + * Overridden to return all visible rows in the tree. + */ + public NSArray objectsFetchedIntoChildrenGroup() { + JTree tree = (JTree) object(); + NSMutableArray objectList = new NSMutableArray(); + + int count = tree.getRowCount(); + for (int i = 0; i < count; i++) { + objectList.add(((DisplayGroupNode) tree.getPathForRow(i).getLastPathComponent()).object()); + } //new net.wotonomy.ui.swing.util.StackTraceInspector( Integer.toString( objectList.size() ) ); - return objectList; - } + return objectList; + } + + // interface FocusListener - // interface FocusListener - /** - * Notifies of beginning of edit. - */ - public void focusGained(FocusEvent evt) - { + * Notifies of beginning of edit. + */ + public void focusGained(FocusEvent evt) { Object o; EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidBeginEditing( this ); + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidBeginEditing(this); } } - } + } /** - * Updates object on focus lost and notifies of end of edit. - */ - public void focusLost(FocusEvent evt) - { - if ( ! component().isEditing() ) - { - Object o; - EODisplayGroup displayGroup; - Enumeration e = aspects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - displayGroup = - displayGroupForAspect( e.nextElement().toString() ); - if ( displayGroup != null ) - { - displayGroup.associationDidEndEditing( this ); - } - } - } - } - - // interface TreeWillExpandListener - - public void treeWillExpand(TreeExpansionEvent event) - throws ExpandVetoException - { - isExpanding = true; - } - - public void treeWillCollapse(TreeExpansionEvent event) - throws ExpandVetoException - { - // do nothing - } - - // interface TreeExpansionListener - - /** - * Updates the children display group, if any. - */ - public void treeExpanded(TreeExpansionEvent event) - { //System.out.println( "treeExpanded: " + event.getPath().getLastPathComponent() ); - isExpanding = false; - if ( childrenDisplayGroup != null ) - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - } - } - - /** - * Updates the children display group, if any. - */ - public void treeCollapsed(TreeExpansionEvent event) - { - if ( childrenDisplayGroup != null ) - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - } - } - - // interface Runnable - - /** - * Fires any queued node changed and structure changed events. - * Typically invoked on a delayed event loop. - */ - public void run() - { - DisplayGroupNode node; - int index; - Iterator i; - - i = nodeQueue.iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - index = ((DisplayGroupNode)node.parentGroup).getIndex( node ); - if ( ( index != -1 ) - && ( node.treePath().getParentPath() != null ) ) - { - super.fireTreeNodesChanged( - node, - node.treePath().getParentPath().getPath(), - new int[] { index }, - new Object[] { node } ); - } - } - nodeQueue.clear(); - - i = structureQueue.iterator(); - while ( i.hasNext() ) - { - node = (DisplayGroupNode) i.next(); - super.fireTreeStructureChanged( - node, - node.treePath().getPath(), - null, - null ); - } - structureQueue.clear(); - - isRunning = false; -/* - EventQueue.invokeLater( new Runnable() { public void run() { - ((JTree)object()).treeDidChange(); - ((JTree)object()).getParent().invalidate(); - ((JTree)object()).getParent().validate(); - ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() ); - -// ((JTree)object()).getParent().doLayout(); -// ((JTree)object()).getParent().repaint(); -// ((JTree)object()).repaint(); -// ((JTree)object()).updateUI(); - } } ); -*/ - } + * Updates object on focus lost and notifies of end of edit. + */ + public void focusLost(FocusEvent evt) { + if (!component().isEditing()) { + Object o; + EODisplayGroup displayGroup; + Enumeration e = aspects().objectEnumerator(); + while (e.hasMoreElements()) { + displayGroup = displayGroupForAspect(e.nextElement().toString()); + if (displayGroup != null) { + displayGroup.associationDidEndEditing(this); + } + } + } + } + + // interface TreeWillExpandListener + + public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { + isExpanding = true; + } + + public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { + // do nothing + } + + // interface TreeExpansionListener + + /** + * Updates the children display group, if any. + */ + public void treeExpanded(TreeExpansionEvent event) { // System.out.println( "treeExpanded: " + + // event.getPath().getLastPathComponent() ); + isExpanding = false; + if (childrenDisplayGroup != null) { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + /** + * Updates the children display group, if any. + */ + public void treeCollapsed(TreeExpansionEvent event) { + if (childrenDisplayGroup != null) { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + } + } + + // interface Runnable + + /** + * Fires any queued node changed and structure changed events. Typically invoked + * on a delayed event loop. + */ + public void run() { + DisplayGroupNode node; + int index; + Iterator i; + + i = nodeQueue.iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + index = ((DisplayGroupNode) node.parentGroup).getIndex(node); + if ((index != -1) && (node.treePath().getParentPath() != null)) { + super.fireTreeNodesChanged(node, node.treePath().getParentPath().getPath(), new int[] { index }, + new Object[] { node }); + } + } + nodeQueue.clear(); + + i = structureQueue.iterator(); + while (i.hasNext()) { + node = (DisplayGroupNode) i.next(); + super.fireTreeStructureChanged(node, node.treePath().getPath(), null, null); + } + structureQueue.clear(); + + isRunning = false; + /* + * EventQueue.invokeLater( new Runnable() { public void run() { + * ((JTree)object()).treeDidChange(); + * ((JTree)object()).getParent().invalidate(); + * ((JTree)object()).getParent().validate(); + * ((JTree)object()).getParent().update( ((JTree)object()).getGraphics() ); + * + * // ((JTree)object()).getParent().doLayout(); // + * ((JTree)object()).getParent().repaint(); // ((JTree)object()).repaint(); // + * ((JTree)object()).updateUI(); } } ); + */ + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.55 2004/02/05 02:18:50 mpowers - * Super was calling back into this class before init() was called. + * Revision 1.55 2004/02/05 02:18:50 mpowers Super was calling back into this + * class before init() was called. * - * Revision 1.54 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.54 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.53 2003/06/03 14:49:48 mpowers - * Now correctly calculating isVisible based on the component visible rect. - * Now deferring node changed events to a later event queue to allow repaints - * to happen after all changes have taken effect. + * Revision 1.53 2003/06/03 14:49:48 mpowers Now correctly calculating isVisible + * based on the component visible rect. Now deferring node changed events to a + * later event queue to allow repaints to happen after all changes have taken + * effect. * - * Revision 1.52 2002/05/03 21:41:18 mpowers - * No longer clearing the selection model when updating from display group: - * we now only modify if a change needs to be made. - * No longer listening for selection change during firing of delete events: - * delete events cause JTree's to update their selection model. - * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges() - * must happen after the screen is painted, or the selection is not displayed. + * Revision 1.52 2002/05/03 21:41:18 mpowers No longer clearing the selection + * model when updating from display group: we now only modify if a change needs + * to be made. No longer listening for selection change during firing of delete + * events: delete events cause JTree's to update their selection model. Fix for + * paintsSelectionImmediately: TreeAssociation.processRecentChanges() must + * happen after the screen is painted, or the selection is not displayed. * - * Revision 1.51 2002/04/19 21:18:45 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.51 2002/04/19 21:18:45 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.49 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.49 2002/04/12 21:05:58 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.48 2002/04/12 20:36:31 mpowers - * Now distinguishing between changes made on titles group by tree expansion - * versus external changes which should cause us to repopulate root nodes. + * Revision 1.48 2002/04/12 20:36:31 mpowers Now distinguishing between changes + * made on titles group by tree expansion versus external changes which should + * cause us to repopulate root nodes. * - * Revision 1.47 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.47 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.46 2002/03/27 20:44:53 mpowers - * Added isVisible test for node. + * Revision 1.46 2002/03/27 20:44:53 mpowers Added isVisible test for node. * - * Revision 1.45 2002/03/08 23:19:57 mpowers - * Refactoring of DelegatingTreeDataSource to facilitate binding of titles - * and children aspects to the same display group. + * Revision 1.45 2002/03/08 23:19:57 mpowers Refactoring of + * DelegatingTreeDataSource to facilitate binding of titles and children aspects + * to the same display group. * - * Revision 1.44 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.44 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.43 2002/03/06 13:04:15 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.43 2002/03/06 13:04:15 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.42 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.42 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.41 2002/03/01 23:42:08 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.41 2002/03/01 23:42:08 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * - * Revision 1.40 2002/03/01 20:41:39 mpowers - * Now a focus listener and an expansion listener. + * Revision 1.40 2002/03/01 20:41:39 mpowers Now a focus listener and an + * expansion listener. * - * Revision 1.39 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.39 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java index f6c90d0..26f2eda 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeColumnAssociation.java @@ -26,306 +26,254 @@ import net.wotonomy.ui.EODisplayGroup; import net.wotonomy.ui.swing.components.TreeTableCellRenderer; /** -* TreeColumnAssociation is a TableColumnAssocation -* that works like a TreeAssociation, allowing any -* table to display hierarchical data in a tabular format. -* This class is mainly a convenience for connecting a -* TreeAssociation to a JTree to a TreeTableCellRenderer -* to a TableColumn.

-* -* Like TableColumnAssociation, you must call setTable() -* to specify the JTable to be used. (The corresponding -* table association will direct all column header sorting -* to the root node of the tree association.) -* -* You may also optionally call setTree() to specify a -* customized JTree to be used. If not specified, a -* slightly customized JTree will be used (see createTree() -* and configureColumn()). -* -* TreeColumnAssociation supports the following bindings, -* just as TableColumnAssociation does: -*
    -*
  • value: a property convertable to a string for -* display in the cells of the table column. This -* binding is equivalent to the titles binding of -* TreeAssociation.
  • -* -*
  • editable: a property convertable to a boolean -* that determines the editability of the corresponding -* cells in the column.
  • -*
-* -* TreeColumnAssociation additionally supports the following -* bindings, just as TreeAssociation does, except that the -* value binding is used instead of the titles binding. -*
    -*
  • children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* If this aspect is not bound, the tree behaves like a list.
  • -* -*
  • isLeaf: a property of a node value that returns -* a value convertable to a boolean value. -* If the isLeaf aspect is not bound, the tree will force -* nodes to load their children to determine whether they -* are leaf nodes (in effect loading the grandchildren for -* any expanded node). -*
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeColumnAssociation extends TableColumnAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ValueAspect, EditableAspect, ChildrenAspect, IsLeafAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "table" - } ); - + * TreeColumnAssociation is a TableColumnAssocation that works like a + * TreeAssociation, allowing any table to display hierarchical data in a tabular + * format. This class is mainly a convenience for connecting a TreeAssociation + * to a JTree to a TreeTableCellRenderer to a TableColumn.
+ *
+ * + * Like TableColumnAssociation, you must call setTable() to specify the JTable + * to be used. (The corresponding table association will direct all column + * header sorting to the root node of the tree association.) + * + * You may also optionally call setTree() to specify a customized JTree to be + * used. If not specified, a slightly customized JTree will be used (see + * createTree() and configureColumn()). + * + * TreeColumnAssociation supports the following bindings, just as + * TableColumnAssociation does: + *
    + *
  • value: a property convertable to a string for display in the cells of the + * table column. This binding is equivalent to the titles binding of + * TreeAssociation.
  • + * + *
  • editable: a property convertable to a boolean that determines the + * editability of the corresponding cells in the column.
  • + *
+ * + * TreeColumnAssociation additionally supports the following bindings, just as + * TreeAssociation does, except that the value binding is used instead of the + * titles binding. + *
    + *
  • children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. If this aspect is not bound, the tree behaves like a + * list.
  • + * + *
  • isLeaf: a property of a node value that returns a value convertable to a + * boolean value. If the isLeaf aspect is not bound, the tree will force nodes + * to load their children to determine whether they are leaf nodes (in effect + * loading the grandchildren for any expanded node).
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeColumnAssociation extends TableColumnAssociation { + static final NSArray aspects = new NSArray( + new Object[] { ValueAspect, EditableAspect, ChildrenAspect, IsLeafAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "table" }); + EODisplayGroup childrenDisplayGroup, leafDisplayGroup; String childrenKey, leafKey; - - TreeModelAssociation treeAssociation; - JTree tree; - - /** - * Constructor specifying the object to be controlled by this - * association. Throws an exception if the object is not - * a TableColumn. - */ - public TreeColumnAssociation ( Object anObject ) - { - super( anObject ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( ChildrenAspect.equals( anAspect ) ) - { - childrenDisplayGroup = aDisplayGroup; + + TreeModelAssociation treeAssociation; + JTree tree; + + /** + * Constructor specifying the object to be controlled by this association. + * Throws an exception if the object is not a TableColumn. + */ + public TreeColumnAssociation(Object anObject) { + super(anObject); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (ChildrenAspect.equals(anAspect)) { + childrenDisplayGroup = aDisplayGroup; childrenKey = aKey; } - if ( IsLeafAspect.equals( anAspect ) ) - { + if (IsLeafAspect.equals(anAspect)) { leafDisplayGroup = aDisplayGroup; leafKey = aKey; } - super.bindAspect( anAspect, aDisplayGroup, aKey ); + super.bindAspect(anAspect, aDisplayGroup, aKey); } - - /** - * Overridden to call createTree if necessary, - * call configureColumn, call createTreeAssociation - * if necessary, and then call to super. - */ - public void establishConnection () - { - if ( tree == null ) - { - tree = createTree(); - } - - configureColumn( (TableColumn) object() ); - - if ( treeAssociation == null ) - { - treeAssociation = createTreeAssociation( tree ); - } - - treeAssociation.bindAspect( TitlesAspect, valueDisplayGroup, valueKey ); - if ( childrenKey != null ) - { - treeAssociation.bindAspect( ChildrenAspect, childrenDisplayGroup, childrenKey ); - } - if ( leafKey != null ) - { - treeAssociation.bindAspect( IsLeafAspect, leafDisplayGroup, leafKey ); - } - - // ensure table association's source is tree asssociation's child group - getTableAssociation().bindAspect( - SourceAspect, treeAssociation.childrenDisplayGroup, "" ); - - treeAssociation.establishConnection(); - - table.setRowHeight( tree.getRowHeight() ); - - super.establishConnection(); - - // cause sort ordering to apply to root node of tree - if ( childrenKey != null ) - { - getTableAssociation().sortTarget = - (EODisplayGroup) treeAssociation.getRoot(); - } - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - super.breakConnection(); - treeAssociation.breakConnection(); - - // restore original source display group - getTableAssociation().sortTarget = null; - } - - /** - * Called by establishConnection if setTree was not called. - * This implementation returns a stock JTree. Override - * to provide your own customized JTree. Note that - * TreeTableCellRenderer will further customize this tree - * when configureColumn is called. - */ - protected JTree createTree() - { - return new JTree(); - } - - /** - * Called by establishConnection to create a tree association - * only if no tree association has already been created. - * This implementation returns a stock TreeAssociation. - * Override to return your own customized TreeAssociation. - */ - protected TreeModelAssociation createTreeAssociation( JTree aTree ) - { - return new TreeAssociation( aTree ); - } - - /** - * Called by establishConnection to configure the column - * with a TreeTableCellRenderer using the current JTree. - * Override to further customize the column, or customize - * your column yourself after the call to establishConnection. - */ - protected void configureColumn( TableColumn aColumn ) - { - aColumn.setCellRenderer( new TreeTableCellRenderer( tree ) ); - } - + /** - * Gets the JTree currently used for the column renderer. - * If not specified, returns null. - */ - public JTree getTree() - { - return tree; + * Overridden to call createTree if necessary, call configureColumn, call + * createTreeAssociation if necessary, and then call to super. + */ + public void establishConnection() { + if (tree == null) { + tree = createTree(); + } + + configureColumn((TableColumn) object()); + + if (treeAssociation == null) { + treeAssociation = createTreeAssociation(tree); + } + + treeAssociation.bindAspect(TitlesAspect, valueDisplayGroup, valueKey); + if (childrenKey != null) { + treeAssociation.bindAspect(ChildrenAspect, childrenDisplayGroup, childrenKey); + } + if (leafKey != null) { + treeAssociation.bindAspect(IsLeafAspect, leafDisplayGroup, leafKey); + } + + // ensure table association's source is tree asssociation's child group + getTableAssociation().bindAspect(SourceAspect, treeAssociation.childrenDisplayGroup, ""); + + treeAssociation.establishConnection(); + + table.setRowHeight(tree.getRowHeight()); + + super.establishConnection(); + + // cause sort ordering to apply to root node of tree + if (childrenKey != null) { + getTableAssociation().sortTarget = (EODisplayGroup) treeAssociation.getRoot(); + } + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + super.breakConnection(); + treeAssociation.breakConnection(); + + // restore original source display group + getTableAssociation().sortTarget = null; } /** - * Gets the TreeModelAssociation currently used for the tree. - * If not tree is not specified, returns null. - */ - public TreeModelAssociation getTreeModelAssociation() - { - if ( tree == null ) return null; - if (!( tree.getModel() instanceof TreeModelAssociation )) return null; - return (TreeModelAssociation) tree.getModel(); + * Called by establishConnection if setTree was not called. This implementation + * returns a stock JTree. Override to provide your own customized JTree. Note + * that TreeTableCellRenderer will further customize this tree when + * configureColumn is called. + */ + protected JTree createTree() { + return new JTree(); } /** - * Sets the JTree to be used for the column renderer. - * If not specified, createTree() will be called to create a JTree. - */ - public void setTree( JTree aTree ) - { - tree = aTree; + * Called by establishConnection to create a tree association only if no tree + * association has already been created. This implementation returns a stock + * TreeAssociation. Override to return your own customized TreeAssociation. + */ + protected TreeModelAssociation createTreeAssociation(JTree aTree) { + return new TreeAssociation(aTree); + } + + /** + * Called by establishConnection to configure the column with a + * TreeTableCellRenderer using the current JTree. Override to further customize + * the column, or customize your column yourself after the call to + * establishConnection. + */ + protected void configureColumn(TableColumn aColumn) { + aColumn.setCellRenderer(new TreeTableCellRenderer(tree)); + } + + /** + * Gets the JTree currently used for the column renderer. If not specified, + * returns null. + */ + public JTree getTree() { + return tree; + } + + /** + * Gets the TreeModelAssociation currently used for the tree. If not tree is not + * specified, returns null. + */ + public TreeModelAssociation getTreeModelAssociation() { + if (tree == null) + return null; + if (!(tree.getModel() instanceof TreeModelAssociation)) + return null; + return (TreeModelAssociation) tree.getModel(); + } + + /** + * Sets the JTree to be used for the column renderer. If not specified, + * createTree() will be called to create a JTree. + */ + public void setTree(JTree aTree) { + tree = aTree; } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.9 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.9 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.8 2002/05/03 21:31:50 mpowers - * Actually works better without selectionPaintedImmediately. + * Revision 1.8 2002/05/03 21:31:50 mpowers Actually works better without + * selectionPaintedImmediately. * - * Revision 1.7 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.7 2002/04/12 21:05:58 mpowers Now distinguishing changes in titles + * group even better. * - * Revision 1.6 2002/03/08 23:18:48 mpowers - * Added accessor for tree association. + * Revision 1.6 2002/03/08 23:18:48 mpowers Added accessor for tree association. * - * Revision 1.5 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.5 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.4 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.4 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.3 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.3 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.2 2002/03/04 22:48:22 mpowers - * Now working with table association to sort the root node. + * Revision 1.2 2002/03/04 22:48:22 mpowers Now working with table association + * to sort the root node. * - * Revision 1.1 2002/03/01 23:42:09 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.1 2002/03/01 23:42:09 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java index 86bfa69..b0070d4 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/TreeModelAssociation.java @@ -48,1704 +48,1396 @@ import net.wotonomy.ui.EOAssociation; import net.wotonomy.ui.EODisplayGroup; /** -* TreeModelAssociation binds a JTree or similar component -* that uses a TreeModel to a display group's -* list of displayable objects, each of which may have -* a list of child objects managed by another display -* group, and so on. TreeModelAssociation works exactly -* like a ListAssociation, with the additional capability -* to specify a "Children" aspect, that will allow child -* objects to be retrieved from a parent display group. -* -*
    -* -*
  • titles: a property convertable to a string for -* display in the nodes of the tree. The objects in -* the bound display group will be used to populate the -* initial root nodes of the tree (more accurately, -* children of the offscreen root node in the tree).
  • -* -*
  • children: a property of a node value that returns -* zero, one or many objects, each of which will correspond -* to a child node for the corresponding node in the tree. -* The data source of the bound display group is replaced -* a data source that populates the display group with -* the selection in the tree component as determined by -* calling fetchObjectsIntoChildrenGroup. -* If this aspect is not bound, the tree behaves like a list. -*

    -* Binding this aspect with a null display group is the same -* as binding it with the titles display group. -* In this configuration the contents of the titles -* display group will be replaced with the selection in the -* tree component, as specified above, replacing the existing -* data source. -*

    -* In that case, the display groups for the nodes in -* the tree will still use the original data source -* for resolving their children key, and programmatically -* setting the contents of the display group will still -* repopulate the root nodes of the tree. -*
  • -* -*
  • isLeaf: a property of a node value that returns -* a value convertable to a boolean value (aside from -* an actual boolean value, zeroes evaluate to true, -* as does any String containing "yes" or "true" or that -* is convertable to a number equal to zero; other values -* evaluate to false). -*

    -* If the isLeaf aspect is not bound, -* the tree must force nodes to load their children to -* determine whether they are leaf nodes (in effect -* loading the grandchildren for any expanded node). -* If bound, child loading is deferred until the node -* is actually expanded. -*

    -* For example, binding this value to a null -* display group and the key "false" will result in a -* deferred-loading tree that works much like Windows -* Explorer's network volume browser - all nodes appear -* with "pluses" until they are expanded. -*

    -* Note that the display group is ignored: the property -* will be applied directly to the object corresponding -* to the node.
  • -* -*
-* -* This class acts as the TreeModel for the controlled -* component: calling yourcomponent.getModel() will -* return this association. The tree model methods on -* this class are public and may be used to affect changes -* on the controlled components.

-* -* The titles display group's contents are inserted -* into a new display group that acts as the root node. -* After that point, changes in the titles display group -* will cause the tree model to reset itself, creating -* a new display group for the root node. -*

-* -* If a separate display group is bound to the children -* aspect, it will -* be used to hold the selected objects and their siblings -* and selection will be maintained there, and the titles -* display group selection will not be updated. -* Any editing or detail associations should in that case -* be attached to the children display group, not the titles -* group.

-* -* Each node in the tree is an EODisplayGroup that -* contains the child objects of the object it represents -* in the tree. These objects can be programmatically -* inserted, updated, or removed using DisplayGroup -* methods. Each node's takes its parent group's -* sortOrderings until a sort ordering is explicitly -* specified - setting a sort ordering to null will resume -* using the parent group's sort ordering.

-* -* Each node in the tree also implements MutableTreeNode. -* The value that a node represents is the titles property -* value of the object in the parent's displayed objects -* list at the index corresponding to the index of the node. -* Calling toString on a node returns the string representation -* of the titles property value, and setUserObject will update -* that value directly in the corresponding object. -* Moving a node from one parent to another will remove the -* actual object in the parent display group and insert it -* into the destination display group.

-* -* In short, any nodes obtained from this class' -* implementation of TreeModel may be cast as either -* EODisplayGroup or MutableTreeNode and maybe be -* programmatically manipulated in either manner. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeModelAssociation extends EOAssociation - implements TreeModel, TreeSelectionListener -{ - static final NSArray aspects = - new NSArray( new Object[] { - TitlesAspect, ChildrenAspect, IsLeafAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "model" - } ); - - private final static NSSelector getSelectionModel = - new NSSelector( "getSelectionModel" ); - private final static NSSelector setModel = - new NSSelector( "setModel", - new Class[] { TreeModel.class } ); - - EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup; - String titlesKey, childrenKey, leafKey; - DisplayGroupNode rootNode; - Vector listeners; - Object rootLabel; - - TreeSelectionModel selectionModel; - boolean selectionPaintedImmediately; - - boolean insertingChild; - boolean insertingAfter; - - EOObserverProxy recentChangesObserver; - - private boolean pleaseSelectRootNode; - - /** - * Constructor expecting a JTree or any other object - * that has void setModel(TreeModel) and TreeModel getSelectionModel() - * methods. This tree association will be used for the TreeModel. - * The root node will be labeled "Root".

- * - * As an alternate way to use a TreeModelAssociation, you may pass a - * TreeSelectionModel to the constructor and then manually set your - * component to use this class as its TreeModel. - */ - public TreeModelAssociation ( Object anObject ) - { - super( anObject ); - - titlesDisplayGroup = null; - titlesKey = null; - childrenDisplayGroup = null; - childrenKey = null; - leafDisplayGroup = null; - leafKey = null; - listeners = new Vector(); - - selectionPaintedImmediately = false; - - // after display group nodes process recent changes - recentChangesObserver = new EOObserverProxy( - this, new NSSelector( "processRecentChanges" ), - EODelayedObserver.ObserverPrioritySixth ); - EOObserverCenter.addObserver( recentChangesObserver, this ); - - insertingChild = true; - insertingAfter = true; - - pleaseSelectRootNode = false; - - rootLabel = "Root"; - rootNode = createNode( null, null ); - } - - /** - * Constructor expecting a JTree or similar component - * and specifying a label for the root node. - */ - public TreeModelAssociation( Object anObject, Object aRootLabel ) - { - this( anObject ); - rootLabel = aRootLabel; - rootNode.setUserObject( aRootLabel ); - } - - /** - * Gets the current root label. - */ - public Object rootLabel() - { - return rootLabel; - } - - /** - * Gets the current root label. - */ - public Object getRootLabel() - { - return rootLabel(); - } - - /** - * Sets the root label. - */ - public void setRootLabel( Object aLabel ) - { - rootLabel = aLabel; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return setModel.implementedByObject( anObject ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return TitlesAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { - if ( TitlesAspect.equals( anAspect ) ) - { - titlesDisplayGroup = aDisplayGroup; - titlesKey = aKey; - } - if ( ChildrenAspect.equals( anAspect ) ) - { - childrenDisplayGroup = aDisplayGroup; - childrenKey = aKey; - } - if ( IsLeafAspect.equals( anAspect ) ) - { - leafDisplayGroup = aDisplayGroup; - leafKey = aKey; - } - if ( childrenDisplayGroup == null ) - { - childrenDisplayGroup = titlesDisplayGroup; - } - super.bindAspect( anAspect, aDisplayGroup, aKey ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - if ( titlesDisplayGroup == null ) - { - throw new WotonomyException( - "TreeModelAssociation: Titles aspect must be bound" ); - } - - // populate the root node - rootNode = createNode( titlesDisplayGroup, null ); - rootNode.setObjectArray( titlesDisplayGroup.displayedObjects() ); - rootNode.setSortOrderings( titlesDisplayGroup.sortOrderings() ); - - EODataSource dataSource = childrenDisplayGroup.dataSource(); - if ( dataSource == null ) dataSource = titlesDisplayGroup.dataSource(); - while ( dataSource instanceof DelegatingTreeDataSource ) - { // unwrap any existing delegating data sources - dataSource = ((DelegatingTreeDataSource)dataSource).delegateDataSource; - } - // create a new delegating data source - childrenDisplayGroup.setDataSource( - new DelegatingTreeDataSource( this, dataSource ) ); - - //TODO: find out why omitting this line causes weird selection behavior - childrenDisplayGroup.setSortOrderings( new NSArray() ); - - // check for alternate usage - if ( object() instanceof TreeSelectionModel ) - { - selectionModel = (TreeSelectionModel) object(); - } - else // use specified object - { - try - { - setModel.invoke( object(), new Object[] { this } ); - selectionModel = (TreeSelectionModel) - getSelectionModel.invoke( object(), new Object[] {} ); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - } - - addAsListener(); - super.establishConnection(); -/* - fireRootStructureChanged(); - -// titlesGroupChanged = true; -// subjectChanged(); - - // update the children group - removeAsListener(); - childrenDisplayGroup.fetch(); - addAsListener(); - - // update selection - selectFromDisplayGroup( titlesDisplayGroup ); -*/ - } - - protected void fireRootStructureChanged() - { - int count = rootNode.displayedObjects().count(); - int[] childIndices = new int[ count ]; - Object[] children = new Object[ count ]; - for ( int i = 0; i < count; i++ ) - { - childIndices[i] = i; - children[i] = rootNode.getChildNodeAt( i ); - } - - // must fire a tree structure changed with children, - // otherwise the tree gets weird selection behavior - fireTreeStructureChanged( this, new Object[] { rootNode }, - childIndices, children ); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - if ( childrenDisplayGroup != null ) - { - if ( childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource ) - { - if ( titlesDisplayGroup == childrenDisplayGroup ) - { - titlesDisplayGroup.setDataSource( ((DelegatingTreeDataSource) - childrenDisplayGroup.dataSource()).delegateDataSource ); - } - else - { - childrenDisplayGroup.setDataSource( null ); - } - } - } - - removeAsListener(); - super.breakConnection(); - } - - protected void addAsListener() - { - isListening = true; - selectionModel.addTreeSelectionListener( this ); - } - - protected void removeAsListener() - { - isListening = false; - selectionModel.removeTreeSelectionListener( this ); - } - - protected boolean isListening = false; - private boolean pleaseIgnore = false; - protected boolean titlesGroupChanged = false; - protected boolean childrenGroupChanged = false; - - /** - * Overridden to better discriminate what is changed. - */ - public void objectWillChange( Object anObject ) - { - if ( ! isListening ) return; - - if ( anObject == titlesDisplayGroup ) - { - titlesGroupChanged = true; - } - if ( anObject == childrenDisplayGroup ) - { - childrenGroupChanged = true; - if ( childrenDisplayGroup.qualifier() != null ) - { - if ( ( rootNode.qualifier() == null ) || - ! childrenDisplayGroup.qualifier().equals( rootNode.qualifier() ) ) - { - // quietly move qualifier from children group to root node - rootNode.setQualifier( childrenDisplayGroup.qualifier() ); - childrenDisplayGroup.setQualifier( null ); - rootNode.updateDisplayedObjects(); - } - } - } - super.objectWillChange( anObject ); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - // titles aspect - if ( titlesGroupChanged ) - { - if ( titlesDisplayGroup.contentsChanged() ) - { - NSArray displayedObjects = titlesDisplayGroup.displayedObjects(); - NSArray childrenObjects; - if ( titlesDisplayGroup != childrenDisplayGroup - || displayedObjects.count() - != (childrenObjects = objectsFetchedIntoChildrenGroup()).count() - || ! displayedObjects.containsAll( childrenObjects ) ) - { - populateFromDisplayGroup( displayedObjects ); - } - } - } - - if ( childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged() ) - { - selectFromDisplayGroup( childrenDisplayGroup ); - } - - titlesGroupChanged = false; - childrenGroupChanged = false; - } - - /** - * Called by subjectChanged() in response to an external change in the titles display group. - */ - void populateFromDisplayGroup( List displayedObjects ) - { - // trigger processRecentChanges - willChange(); - - // workaround: see below - int previousCount = rootNode.previouslyDisplayedObjects.length; - - // update the root node - rootNode.setObjectArray( displayedObjects ); - - //FIXME: workaround for what appears to be a bug in JTree: - // if root node is not visible and has no children, insert events are ignored - if ( previousCount == 0 ) - { - fireRootStructureChanged(); - } - } - - /** - * Package access so DisplayGroupNode can replace the selection after an update. - */ - void selectFromDisplayGroup( EODisplayGroup aDisplayGroup ) - { // System.out.println( "selectFromDisplayGroup: " + aDisplayGroup.selectedObjects() ); - - removeAsListener(); - - TreePath[] paths = selectionModel.getSelectionPaths(); - NSArray selectedObjects = aDisplayGroup.selectedObjects(); - - // assemble current selection list - List treeSelection = new LinkedList(); - if ( paths != null ) - { - for ( int i = 0; i < paths.length; i ++ ) - { - treeSelection.add( - ((DisplayGroupNode)paths[i].getLastPathComponent()).getUserObject() ); - } - } - - if ( ! ( selectedObjects.size() == treeSelection.size() - && treeSelection.containsAll( selectedObjects ) ) ) - { - selectionModel.clearSelection(); - - // workaround to select root node from valueChanged() - if ( pleaseSelectRootNode ) - { - selectionModel.addSelectionPath( new TreePath( this.getRoot() ) ); - pleaseSelectRootNode = false; - } - - //FIXME: display group is assumed to have only one instance of each object - for ( int i = 0; i < selectedObjects.count(); i++ ) - { - //FIXME: selects only the first instance for now - //selectionModel.addSelectionPaths( - // getPathsForObject( - // selectedObjects.objectAtIndex( i ) ) ); - selectionModel.addSelectionPath( - getPathForObject( - selectedObjects.objectAtIndex( i ) ) ); - } - } - - addAsListener(); - } - - /** - * Returns the first node found that represents the - * specified object, or null if not found. - * This implementation simply calls getPathForObject. - */ - public Object getNodeForObject( Object anObject ) - { - TreePath result = getPathForObject( anObject ); - if ( result != null ) - { - return result.getLastPathComponent(); - } - return null; - } - - /** - * Returns the object represented by the specified node - * which must be a display group node from this tree. - */ - public Object getObjectForNode( Object aNode ) - { - if ( aNode instanceof DisplayGroupNode ) - { - return ((DisplayGroupNode)aNode).getUserObject(); - } - - // not a display group node - throw new WotonomyException( - "Not a display group node: " + aNode ); - } - - /** - * Returns the tree path for the specified node, - * which must be a display group node from this tree. - */ - public TreePath getPathForNode( Object aNode ) - { - if ( aNode instanceof DisplayGroupNode ) - { - return ((DisplayGroupNode)aNode).treePath(); - } - - // not a display group node - throw new WotonomyException( - "Not a display group node: " + aNode ); - } - - /** - * Returns the first tree path for the node that represents - * the specified object, or null if the object does not exist in this tree. - * This implementation does a breadth-first search of the tree - * for the object, looking only at nodes that have been loaded. - * This means that if the object does not exist in the tree, - * the entire tree must be traversed. - */ - public TreePath getPathForObject( Object anObject ) - { - return getPathForObjectInPath( anObject, new TreePath( this.getRoot() ) ); - } - - /** - * Returns the tree path for the node that represents - * the specified object, - * or null if the object does not exist in this tree. - * This implementation does a breadth-first search of the tree - * for the object, looking only at nodes that have been loaded. - * This means that the entire tree is traversed. - */ - public TreePath[] getPathsForObject( Object anObject ) - { - return getPathsForObjectInPath( anObject, new TreePath( this.getRoot() ) ); - } - - /** - * A breadth-first search of the tree starting - * at the specified tree path, comparing by reference. - * Returns immediately with the first match. - */ - private TreePath getPathForObjectInPath( Object anObject, TreePath aPath ) - { - LinkedList queue = new LinkedList(); - - // add the specified path - queue.addLast( aPath ); - - return processQueue( anObject, queue, null ); - } - - /** - * A breadth-first search of the tree starting - * at the specified tree path, comparing by reference. - * The entire branch is searched before returning - * an array of all matches. - */ - private TreePath[] getPathsForObjectInPath( Object anObject, TreePath aPath ) - { - LinkedList queue = new LinkedList(); - - // add the specified path - queue.addLast( aPath ); - - List result = new LinkedList(); - processQueue( anObject, queue, result ); - TreePath[] paths = new TreePath[ result.size() ]; - for ( int i = 0; i < paths.length; i++ ) - { - paths[i] = (TreePath) result.get(i); - } - return paths; - } - - /** - * Processes the specified queue, appending results to aResult if it exists, - * or returning immediately with a TreePath is aResult is null. - */ - private TreePath processQueue( Object anObject, LinkedList aQueue, List aResult ) - { - TreePath path; - while ( ! aQueue.isEmpty() ) - { - path = (TreePath) aQueue.removeFirst(); - path = checkNode( anObject, path, aQueue ); - if ( path != null ) - { - if ( aResult != null ) - { - aResult.add( path ); - } - else - { - return path; - } - } - } - return null; - } - - /** - * Compares the specified object by reference each of the children of - * the node at the end of the specified tree path, adding nodes that - * do not match but have fetched object to the end of the specified queue. - * Returns the path of the first child node that matches the specified object, - * or null if no match was found. - */ - private TreePath checkNode( Object anObject, TreePath aPath, LinkedList aQueue ) - { - TreePath result = null; - Object child; - Object parent = aPath.getLastPathComponent(); - int count = getChildCount( parent ); - - for ( int i = 0; i < count; i++ ) - { - child = getChild( parent, i ); - - // add to queue if node has fetched children - if ( ((DisplayGroupNode)child).isFetched ) - { - aQueue.addLast( aPath.pathByAddingChild( child ) ); - } - - // compares by reference - if ( ((DisplayGroupNode)child).object() == anObject ) - { - // assumes same object cannot be in display group twice - result = aPath.pathByAddingChild( child ); + * TreeModelAssociation binds a JTree or similar component that uses a TreeModel + * to a display group's list of displayable objects, each of which may have a + * list of child objects managed by another display group, and so on. + * TreeModelAssociation works exactly like a ListAssociation, with the + * additional capability to specify a "Children" aspect, that will allow child + * objects to be retrieved from a parent display group. + * + *
    + * + *
  • titles: a property convertable to a string for display in the nodes of + * the tree. The objects in the bound display group will be used to populate the + * initial root nodes of the tree (more accurately, children of the offscreen + * root node in the tree).
  • + * + *
  • children: a property of a node value that returns zero, one or many + * objects, each of which will correspond to a child node for the corresponding + * node in the tree. The data source of the bound display group is replaced a + * data source that populates the display group with the selection in the tree + * component as determined by calling fetchObjectsIntoChildrenGroup. If this + * aspect is not bound, the tree behaves like a list.
    + *
    + * Binding this aspect with a null display group is the same as binding it with + * the titles display group. In this configuration the contents of the titles + * display group will be replaced with the selection in the tree component, as + * specified above, replacing the existing data source.
    + *
    + * In that case, the display groups for the nodes in the tree will still use the + * original data source for resolving their children key, and programmatically + * setting the contents of the display group will still repopulate the root + * nodes of the tree.
  • + * + *
  • isLeaf: a property of a node value that returns a value convertable to a + * boolean value (aside from an actual boolean value, zeroes evaluate to true, + * as does any String containing "yes" or "true" or that is convertable to a + * number equal to zero; other values evaluate to false).
    + *
    + * If the isLeaf aspect is not bound, the tree must force nodes to load their + * children to determine whether they are leaf nodes (in effect loading the + * grandchildren for any expanded node). If bound, child loading is deferred + * until the node is actually expanded.
    + *
    + * For example, binding this value to a null display group and the key "false" + * will result in a deferred-loading tree that works much like Windows + * Explorer's network volume browser - all nodes appear with "pluses" until they + * are expanded.
    + *
    + * Note that the display group is ignored: the property will be applied directly + * to the object corresponding to the node.
  • + * + *
+ * + * This class acts as the TreeModel for the controlled component: calling + * yourcomponent.getModel() will return this association. The tree model methods + * on this class are public and may be used to affect changes on the controlled + * components.
+ *
+ * + * The titles display group's contents are inserted into a new display group + * that acts as the root node. After that point, changes in the titles display + * group will cause the tree model to reset itself, creating a new display group + * for the root node.
+ *
+ * + * If a separate display group is bound to the children aspect, it will be used + * to hold the selected objects and their siblings and selection will be + * maintained there, and the titles display group selection will not be updated. + * Any editing or detail associations should in that case be attached to the + * children display group, not the titles group.
+ *
+ * + * Each node in the tree is an EODisplayGroup that contains the child objects of + * the object it represents in the tree. These objects can be programmatically + * inserted, updated, or removed using DisplayGroup methods. Each node's takes + * its parent group's sortOrderings until a sort ordering is explicitly + * specified - setting a sort ordering to null will resume using the parent + * group's sort ordering.
+ *
+ * + * Each node in the tree also implements MutableTreeNode. The value that a node + * represents is the titles property value of the object in the parent's + * displayed objects list at the index corresponding to the index of the node. + * Calling toString on a node returns the string representation of the titles + * property value, and setUserObject will update that value directly in the + * corresponding object. Moving a node from one parent to another will remove + * the actual object in the parent display group and insert it into the + * destination display group.
+ *
+ * + * In short, any nodes obtained from this class' implementation of TreeModel may + * be cast as either EODisplayGroup or MutableTreeNode and maybe be + * programmatically manipulated in either manner. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeModelAssociation extends EOAssociation implements TreeModel, TreeSelectionListener { + static final NSArray aspects = new NSArray(new Object[] { TitlesAspect, ChildrenAspect, IsLeafAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "model" }); + + private final static NSSelector getSelectionModel = new NSSelector("getSelectionModel"); + private final static NSSelector setModel = new NSSelector("setModel", new Class[] { TreeModel.class }); + + EODisplayGroup titlesDisplayGroup, childrenDisplayGroup, leafDisplayGroup; + String titlesKey, childrenKey, leafKey; + DisplayGroupNode rootNode; + Vector listeners; + Object rootLabel; + + TreeSelectionModel selectionModel; + boolean selectionPaintedImmediately; + + boolean insertingChild; + boolean insertingAfter; + + EOObserverProxy recentChangesObserver; + + private boolean pleaseSelectRootNode; + + /** + * Constructor expecting a JTree or any other object that has void + * setModel(TreeModel) and TreeModel getSelectionModel() methods. This tree + * association will be used for the TreeModel. The root node will be labeled + * "Root".
+ *
+ * + * As an alternate way to use a TreeModelAssociation, you may pass a + * TreeSelectionModel to the constructor and then manually set your component to + * use this class as its TreeModel. + */ + public TreeModelAssociation(Object anObject) { + super(anObject); + + titlesDisplayGroup = null; + titlesKey = null; + childrenDisplayGroup = null; + childrenKey = null; + leafDisplayGroup = null; + leafKey = null; + listeners = new Vector(); + + selectionPaintedImmediately = false; + + // after display group nodes process recent changes + recentChangesObserver = new EOObserverProxy(this, new NSSelector("processRecentChanges"), + EODelayedObserver.ObserverPrioritySixth); + EOObserverCenter.addObserver(recentChangesObserver, this); + + insertingChild = true; + insertingAfter = true; + + pleaseSelectRootNode = false; + + rootLabel = "Root"; + rootNode = createNode(null, null); + } + + /** + * Constructor expecting a JTree or similar component and specifying a label for + * the root node. + */ + public TreeModelAssociation(Object anObject, Object aRootLabel) { + this(anObject); + rootLabel = aRootLabel; + rootNode.setUserObject(aRootLabel); + } + + /** + * Gets the current root label. + */ + public Object rootLabel() { + return rootLabel; + } + + /** + * Gets the current root label. + */ + public Object getRootLabel() { + return rootLabel(); + } + + /** + * Sets the root label. + */ + public void setRootLabel(Object aLabel) { + rootLabel = aLabel; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return setModel.implementedByObject(anObject); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return TitlesAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + if (TitlesAspect.equals(anAspect)) { + titlesDisplayGroup = aDisplayGroup; + titlesKey = aKey; + } + if (ChildrenAspect.equals(anAspect)) { + childrenDisplayGroup = aDisplayGroup; + childrenKey = aKey; + } + if (IsLeafAspect.equals(anAspect)) { + leafDisplayGroup = aDisplayGroup; + leafKey = aKey; + } + if (childrenDisplayGroup == null) { + childrenDisplayGroup = titlesDisplayGroup; + } + super.bindAspect(anAspect, aDisplayGroup, aKey); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + if (titlesDisplayGroup == null) { + throw new WotonomyException("TreeModelAssociation: Titles aspect must be bound"); + } + + // populate the root node + rootNode = createNode(titlesDisplayGroup, null); + rootNode.setObjectArray(titlesDisplayGroup.displayedObjects()); + rootNode.setSortOrderings(titlesDisplayGroup.sortOrderings()); + + EODataSource dataSource = childrenDisplayGroup.dataSource(); + if (dataSource == null) + dataSource = titlesDisplayGroup.dataSource(); + while (dataSource instanceof DelegatingTreeDataSource) { // unwrap any existing delegating data sources + dataSource = ((DelegatingTreeDataSource) dataSource).delegateDataSource; + } + // create a new delegating data source + childrenDisplayGroup.setDataSource(new DelegatingTreeDataSource(this, dataSource)); + + // TODO: find out why omitting this line causes weird selection behavior + childrenDisplayGroup.setSortOrderings(new NSArray()); + + // check for alternate usage + if (object() instanceof TreeSelectionModel) { + selectionModel = (TreeSelectionModel) object(); + } else // use specified object + { + try { + setModel.invoke(object(), new Object[] { this }); + selectionModel = (TreeSelectionModel) getSelectionModel.invoke(object(), new Object[] {}); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + } + + addAsListener(); + super.establishConnection(); + /* + * fireRootStructureChanged(); + * + * // titlesGroupChanged = true; // subjectChanged(); + * + * // update the children group removeAsListener(); + * childrenDisplayGroup.fetch(); addAsListener(); + * + * // update selection selectFromDisplayGroup( titlesDisplayGroup ); + */ + } + + protected void fireRootStructureChanged() { + int count = rootNode.displayedObjects().count(); + int[] childIndices = new int[count]; + Object[] children = new Object[count]; + for (int i = 0; i < count; i++) { + childIndices[i] = i; + children[i] = rootNode.getChildNodeAt(i); + } + + // must fire a tree structure changed with children, + // otherwise the tree gets weird selection behavior + fireTreeStructureChanged(this, new Object[] { rootNode }, childIndices, children); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + if (childrenDisplayGroup != null) { + if (childrenDisplayGroup.dataSource() instanceof DelegatingTreeDataSource) { + if (titlesDisplayGroup == childrenDisplayGroup) { + titlesDisplayGroup.setDataSource( + ((DelegatingTreeDataSource) childrenDisplayGroup.dataSource()).delegateDataSource); + } else { + childrenDisplayGroup.setDataSource(null); + } + } + } + + removeAsListener(); + super.breakConnection(); + } + + protected void addAsListener() { + isListening = true; + selectionModel.addTreeSelectionListener(this); + } + + protected void removeAsListener() { + isListening = false; + selectionModel.removeTreeSelectionListener(this); + } + + protected boolean isListening = false; + private boolean pleaseIgnore = false; + protected boolean titlesGroupChanged = false; + protected boolean childrenGroupChanged = false; + + /** + * Overridden to better discriminate what is changed. + */ + public void objectWillChange(Object anObject) { + if (!isListening) + return; + + if (anObject == titlesDisplayGroup) { + titlesGroupChanged = true; + } + if (anObject == childrenDisplayGroup) { + childrenGroupChanged = true; + if (childrenDisplayGroup.qualifier() != null) { + if ((rootNode.qualifier() == null) || !childrenDisplayGroup.qualifier().equals(rootNode.qualifier())) { + // quietly move qualifier from children group to root node + rootNode.setQualifier(childrenDisplayGroup.qualifier()); + childrenDisplayGroup.setQualifier(null); + rootNode.updateDisplayedObjects(); + } + } + } + super.objectWillChange(anObject); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + // titles aspect + if (titlesGroupChanged) { + if (titlesDisplayGroup.contentsChanged()) { + NSArray displayedObjects = titlesDisplayGroup.displayedObjects(); + NSArray childrenObjects; + if (titlesDisplayGroup != childrenDisplayGroup + || displayedObjects.count() != (childrenObjects = objectsFetchedIntoChildrenGroup()).count() + || !displayedObjects.containsAll(childrenObjects)) { + populateFromDisplayGroup(displayedObjects); + } + } + } + + if (childrenDisplayGroup.selectionChanged() && !childrenDisplayGroup.contentsChanged()) { + selectFromDisplayGroup(childrenDisplayGroup); + } + + titlesGroupChanged = false; + childrenGroupChanged = false; + } + + /** + * Called by subjectChanged() in response to an external change in the titles + * display group. + */ + void populateFromDisplayGroup(List displayedObjects) { + // trigger processRecentChanges + willChange(); + + // workaround: see below + int previousCount = rootNode.previouslyDisplayedObjects.length; + + // update the root node + rootNode.setObjectArray(displayedObjects); + + // FIXME: workaround for what appears to be a bug in JTree: + // if root node is not visible and has no children, insert events are ignored + if (previousCount == 0) { + fireRootStructureChanged(); + } + } + + /** + * Package access so DisplayGroupNode can replace the selection after an update. + */ + void selectFromDisplayGroup(EODisplayGroup aDisplayGroup) { // System.out.println( "selectFromDisplayGroup: " + + // aDisplayGroup.selectedObjects() ); + + removeAsListener(); + + TreePath[] paths = selectionModel.getSelectionPaths(); + NSArray selectedObjects = aDisplayGroup.selectedObjects(); + + // assemble current selection list + List treeSelection = new LinkedList(); + if (paths != null) { + for (int i = 0; i < paths.length; i++) { + treeSelection.add(((DisplayGroupNode) paths[i].getLastPathComponent()).getUserObject()); + } + } + + if (!(selectedObjects.size() == treeSelection.size() && treeSelection.containsAll(selectedObjects))) { + selectionModel.clearSelection(); + + // workaround to select root node from valueChanged() + if (pleaseSelectRootNode) { + selectionModel.addSelectionPath(new TreePath(this.getRoot())); + pleaseSelectRootNode = false; + } + + // FIXME: display group is assumed to have only one instance of each object + for (int i = 0; i < selectedObjects.count(); i++) { + // FIXME: selects only the first instance for now + // selectionModel.addSelectionPaths( + // getPathsForObject( + // selectedObjects.objectAtIndex( i ) ) ); + selectionModel.addSelectionPath(getPathForObject(selectedObjects.objectAtIndex(i))); + } + } + + addAsListener(); + } + + /** + * Returns the first node found that represents the specified object, or null if + * not found. This implementation simply calls getPathForObject. + */ + public Object getNodeForObject(Object anObject) { + TreePath result = getPathForObject(anObject); + if (result != null) { + return result.getLastPathComponent(); + } + return null; + } + + /** + * Returns the object represented by the specified node which must be a display + * group node from this tree. + */ + public Object getObjectForNode(Object aNode) { + if (aNode instanceof DisplayGroupNode) { + return ((DisplayGroupNode) aNode).getUserObject(); + } + + // not a display group node + throw new WotonomyException("Not a display group node: " + aNode); + } + + /** + * Returns the tree path for the specified node, which must be a display group + * node from this tree. + */ + public TreePath getPathForNode(Object aNode) { + if (aNode instanceof DisplayGroupNode) { + return ((DisplayGroupNode) aNode).treePath(); + } + + // not a display group node + throw new WotonomyException("Not a display group node: " + aNode); + } + + /** + * Returns the first tree path for the node that represents the specified + * object, or null if the object does not exist in this tree. This + * implementation does a breadth-first search of the tree for the object, + * looking only at nodes that have been loaded. This means that if the object + * does not exist in the tree, the entire tree must be traversed. + */ + public TreePath getPathForObject(Object anObject) { + return getPathForObjectInPath(anObject, new TreePath(this.getRoot())); + } + + /** + * Returns the tree path for the node that represents the specified object, or + * null if the object does not exist in this tree. This implementation does a + * breadth-first search of the tree for the object, looking only at nodes that + * have been loaded. This means that the entire tree is traversed. + */ + public TreePath[] getPathsForObject(Object anObject) { + return getPathsForObjectInPath(anObject, new TreePath(this.getRoot())); + } + + /** + * A breadth-first search of the tree starting at the specified tree path, + * comparing by reference. Returns immediately with the first match. + */ + private TreePath getPathForObjectInPath(Object anObject, TreePath aPath) { + LinkedList queue = new LinkedList(); + + // add the specified path + queue.addLast(aPath); + + return processQueue(anObject, queue, null); + } + + /** + * A breadth-first search of the tree starting at the specified tree path, + * comparing by reference. The entire branch is searched before returning an + * array of all matches. + */ + private TreePath[] getPathsForObjectInPath(Object anObject, TreePath aPath) { + LinkedList queue = new LinkedList(); + + // add the specified path + queue.addLast(aPath); + + List result = new LinkedList(); + processQueue(anObject, queue, result); + TreePath[] paths = new TreePath[result.size()]; + for (int i = 0; i < paths.length; i++) { + paths[i] = (TreePath) result.get(i); + } + return paths; + } + + /** + * Processes the specified queue, appending results to aResult if it exists, or + * returning immediately with a TreePath is aResult is null. + */ + private TreePath processQueue(Object anObject, LinkedList aQueue, List aResult) { + TreePath path; + while (!aQueue.isEmpty()) { + path = (TreePath) aQueue.removeFirst(); + path = checkNode(anObject, path, aQueue); + if (path != null) { + if (aResult != null) { + aResult.add(path); + } else { + return path; + } + } + } + return null; + } + + /** + * Compares the specified object by reference each of the children of the node + * at the end of the specified tree path, adding nodes that do not match but + * have fetched object to the end of the specified queue. Returns the path of + * the first child node that matches the specified object, or null if no match + * was found. + */ + private TreePath checkNode(Object anObject, TreePath aPath, LinkedList aQueue) { + TreePath result = null; + Object child; + Object parent = aPath.getLastPathComponent(); + int count = getChildCount(parent); + + for (int i = 0; i < count; i++) { + child = getChild(parent, i); + + // add to queue if node has fetched children + if (((DisplayGroupNode) child).isFetched) { + aQueue.addLast(aPath.pathByAddingChild(child)); + } + + // compares by reference + if (((DisplayGroupNode) child).object() == anObject) { + // assumes same object cannot be in display group twice + result = aPath.pathByAddingChild(child); //System.out.println( "TRUE: " + ((DisplayGroupNode)child).object() + " == " + anObject ); - } + } // else // { //System.out.println( ((DisplayGroupNode)child).object() + " != " + anObject ); // } - } - return result; - } - - // interface TreeSelectionListener - - public void valueChanged(TreeSelectionEvent e) - { - if ( ! isListening ) return; - selectFromSelectionModel(); - } - - /** - * Determines whether the selection should be painted - * immediately after the user clicks and therefore - * before the children display group is updated. - * When the children group is bound to many associations - * or is bound to a master-detail association, updating - * the display group can take a perceptibly long time. - * This property defaults to false. - * @see #setSelectionPaintedImmediately - */ - public boolean isSelectionPaintedImmediately() - { - return selectionPaintedImmediately; - } - - /** - * Sets whether the selection should be painted immediately. - * Setting this property to true will let the tree paint - * first before the display group is updated. - * This means that any tree selection listers will - * also be notified before the display group is updated - * and will have to invokeLater if they want to see the - * updated display group. - */ - public void setSelectionPaintedImmediately( boolean isImmediate ) - { - selectionPaintedImmediately = isImmediate; - } - - /** - * Package access so DisplayGroupNode can replace the selection. - * Returns the display group containing the current selection: titles or children. - */ - EODisplayGroup selectFromSelectionModel() - { // System.out.print( "selectFromSelectionModel: " ); - removeAsListener(); - DisplayGroupNode node; - TreePath parentPath; - TreePath[] selectedPaths = selectionModel.getSelectionPaths(); - NSMutableArray selectionList = new NSMutableArray(); - if ( selectedPaths != null ) - { - for ( int i = 0; i < selectedPaths.length; i++ ) - { - // root node is zero - ignore root node - if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) ) - { - // select root in selectFromDisplayGroup() - pleaseSelectRootNode = true; - } - else - { - node = (DisplayGroupNode) - selectedPaths[i].getLastPathComponent(); - Object o = node.object(); - - if ( selectionList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - selectionList.addObject( o ); - } - } - } - } - childrenDisplayGroup.fetch(); //note that we're not currently listening for changes - if ( ! childrenDisplayGroup.selectObjectsIdenticalTo( selectionList ) ) - { - addAsListener(); // because we don't have a listener stack - selectFromDisplayGroup( childrenDisplayGroup ); - removeAsListener(); - } - addAsListener(); - return childrenDisplayGroup; // titles is now children if children not explicitly set - } - - // interface TreeModel - - public Object getRoot() - { - return rootNode; - } - - public Object getChild(Object parent, int index) - { - // interestingly, this gets called by - // BasicTreeUI.paintVerticalPartOfLeg for - // the last child of each expanded tree node. - Object result = ((DisplayGroupNode)parent).getChildNodeAt( index ); + } + return result; + } + + // interface TreeSelectionListener + + public void valueChanged(TreeSelectionEvent e) { + if (!isListening) + return; + selectFromSelectionModel(); + } + + /** + * Determines whether the selection should be painted immediately after the user + * clicks and therefore before the children display group is updated. When the + * children group is bound to many associations or is bound to a master-detail + * association, updating the display group can take a perceptibly long time. + * This property defaults to false. + * + * @see #setSelectionPaintedImmediately + */ + public boolean isSelectionPaintedImmediately() { + return selectionPaintedImmediately; + } + + /** + * Sets whether the selection should be painted immediately. Setting this + * property to true will let the tree paint first before the display group is + * updated. This means that any tree selection listers will also be notified + * before the display group is updated and will have to invokeLater if they want + * to see the updated display group. + */ + public void setSelectionPaintedImmediately(boolean isImmediate) { + selectionPaintedImmediately = isImmediate; + } + + /** + * Package access so DisplayGroupNode can replace the selection. Returns the + * display group containing the current selection: titles or children. + */ + EODisplayGroup selectFromSelectionModel() { // System.out.print( "selectFromSelectionModel: " ); + removeAsListener(); + DisplayGroupNode node; + TreePath parentPath; + TreePath[] selectedPaths = selectionModel.getSelectionPaths(); + NSMutableArray selectionList = new NSMutableArray(); + if (selectedPaths != null) { + for (int i = 0; i < selectedPaths.length; i++) { + // root node is zero - ignore root node + if ((selectedPaths[i].getLastPathComponent() == rootNode)) { + // select root in selectFromDisplayGroup() + pleaseSelectRootNode = true; + } else { + node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent(); + Object o = node.object(); + + if (selectionList.indexOfIdenticalObject(o) == NSArray.NotFound) { + selectionList.addObject(o); + } + } + } + } + childrenDisplayGroup.fetch(); // note that we're not currently listening for changes + if (!childrenDisplayGroup.selectObjectsIdenticalTo(selectionList)) { + addAsListener(); // because we don't have a listener stack + selectFromDisplayGroup(childrenDisplayGroup); + removeAsListener(); + } + addAsListener(); + return childrenDisplayGroup; // titles is now children if children not explicitly set + } + + // interface TreeModel + + public Object getRoot() { + return rootNode; + } + + public Object getChild(Object parent, int index) { + // interestingly, this gets called by + // BasicTreeUI.paintVerticalPartOfLeg for + // the last child of each expanded tree node. + Object result = ((DisplayGroupNode) parent).getChildNodeAt(index); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getChildNodeAt( index ); - } + } - public int getChildCount(Object parent) - { - int result = ((DisplayGroupNode)parent).getChildCount(); + public int getChildCount(Object parent) { + int result = ((DisplayGroupNode) parent).getChildCount(); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getChildCount(); - } + } - public boolean isLeaf(Object node) - { - boolean result = ((DisplayGroupNode)node).isLeaf(); + public boolean isLeaf(Object node) { + boolean result = ((DisplayGroupNode) node).isLeaf(); //((DisplayGroupNode)node).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)node).isLeaf(); - } - - /** - * Returns whether this node is visible in the UI. - * This implementation returns true. - *

- * Subclasses should return false if they can - * determine that the node is not displayed or - * expanded or otherwise visible. Non-visible - * nodes will fetch only when they are shown. - */ - public boolean isVisible(Object node) - { - return true; - } - - public void valueForPathChanged(TreePath path, Object newValue) - { - ((DisplayGroupNode)path.getLastPathComponent()).setUserObject( newValue ); - } - - public int getIndexOfChild(Object parent, Object child) - { - int result = ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child ); + } + + /** + * Returns whether this node is visible in the UI. This implementation returns + * true.
+ *
+ * Subclasses should return false if they can determine that the node is not + * displayed or expanded or otherwise visible. Non-visible nodes will fetch only + * when they are shown. + */ + public boolean isVisible(Object node) { + return true; + } + + public void valueForPathChanged(TreePath path, Object newValue) { + ((DisplayGroupNode) path.getLastPathComponent()).setUserObject(newValue); + } + + public int getIndexOfChild(Object parent, Object child) { + int result = ((DisplayGroupNode) parent).getIndex((DisplayGroupNode) child); //((DisplayGroupNode)parent).suppressRecentChangeProcessing(); -return result; + return result; // return ((DisplayGroupNode)parent).getIndex( (DisplayGroupNode) child ); - } - - public void addTreeModelListener(TreeModelListener aListener) - { - listeners.add( aListener ); - } - public void removeTreeModelListener(TreeModelListener aListener) - { - listeners.remove( aListener ); - } - - /** - * Fires a tree nodes changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + } + + public void addTreeModelListener(TreeModelListener aListener) { + listeners.add(aListener); + } + + public void removeTreeModelListener(TreeModelListener aListener) { + listeners.remove(aListener); + } + + /** + * Fires a tree nodes changed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesChanged: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - ((TreeModelListener)it.nextElement()).treeNodesChanged( event ); - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesChanged: caught: " + exc ); - System.out.println( "Source:" + source ); - System.out.println( "Path:" ); - for ( int i = 0; i < path.length; i++ ) - { - System.out.print( path[i] + "-" ); - } - System.out.println(); - System.out.println( "Indices:" ); - for ( int i = 0; i < childIndices.length; i++ ) - { - System.out.print( childIndices[i] + "-" ); - } - System.out.println(); - System.out.println( "Children:" ); - for ( int i = 0; i < children.length; i++ ) - { - System.out.print( children[i] + "-" ); - } - System.out.println(); - exc.printStackTrace(); - } - } - } - - /** - * Fires a tree nodes inserted event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesInserted(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + ((TreeModelListener) it.nextElement()).treeNodesChanged(event); + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesChanged: caught: " + exc); + System.out.println("Source:" + source); + System.out.println("Path:"); + for (int i = 0; i < path.length; i++) { + System.out.print(path[i] + "-"); + } + System.out.println(); + System.out.println("Indices:"); + for (int i = 0; i < childIndices.length; i++) { + System.out.print(childIndices[i] + "-"); + } + System.out.println(); + System.out.println("Children:"); + for (int i = 0; i < children.length; i++) { + System.out.print(children[i] + "-"); + } + System.out.println(); + exc.printStackTrace(); + } + } + } + + /** + * Fires a tree nodes inserted event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesInserted: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - ((TreeModelListener)it.nextElement()).treeNodesInserted( event ); - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesInserted: caught: " + exc ); - } - } - } - - /** - * Fires a tree nodes removed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeNodesRemoved(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + ((TreeModelListener) it.nextElement()).treeNodesInserted(event); + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesInserted: caught: " + exc); + } + } + } + + /** + * Fires a tree nodes removed event to all listeners. Provided as a convenience + * if you need to make manual changes to the tree model. + */ + public void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireTreeNodesRemoved: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - try - { - //NOTE: removing nodes causes tree to fire selection change event - // which confuses us if we're rearranging nodes (when sorting, for example). - boolean wasListening = isListening; - if ( wasListening ) isListening = false; - ((TreeModelListener)it.nextElement()).treeNodesRemoved( event ); - if ( wasListening ) isListening = true; - } - catch ( Exception exc ) - { - System.out.println( "TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc ); - } - } - } - - /** - * Fires a tree structure changed event to all listeners. - * Provided as a convenience if you need to make manual - * changes to the tree model. - */ - public void fireTreeStructureChanged(Object source, - Object[] path, - int[] childIndices, - Object[] children) - { - - willChange(); // queue processRecentChanges - TreeModelEvent event = new TreeModelEvent( - source, path, childIndices, children ); + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + try { + // NOTE: removing nodes causes tree to fire selection change event + // which confuses us if we're rearranging nodes (when sorting, for example). + boolean wasListening = isListening; + if (wasListening) + isListening = false; + ((TreeModelListener) it.nextElement()).treeNodesRemoved(event); + if (wasListening) + isListening = true; + } catch (Exception exc) { + System.out.println("TreeModelAssociation.fireTreeNodesRemoved: caught: " + exc); + } + } + } + + /** + * Fires a tree structure changed event to all listeners. Provided as a + * convenience if you need to make manual changes to the tree model. + */ + public void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { + + willChange(); // queue processRecentChanges + TreeModelEvent event = new TreeModelEvent(source, path, childIndices, children); //System.out.println( "fireStructureChanged: " + event ); - Enumeration it = listeners.elements(); - while ( it.hasMoreElements() ) - { - ((TreeModelListener)it.nextElement()).treeStructureChanged( event ); - } - } - - /** - * Creates and returns a new display group node. - */ - public DisplayGroupNode createNode( EODisplayGroup aParentGroup, Object anObject ) - { - return new MutableDisplayGroupNode( this, aParentGroup, anObject ); - } - - /** - * Gets whether new objects programmatically inserted into the children - * display group should be inserted as a child of the first selected node. - * If false, new objects are inserted as siblings of the first selected node. - * Default value is true. - */ - public boolean isInsertingChild() - { - return insertingChild; - } - - /** - * Sets whether new objects programmatically inserted into the children - * display group should be inserted as a child of the first selected node. - * If false, new objects are inserted as siblings of the first selected node. - * Default value is true. - */ - public void setInsertingChild( boolean asChild ) - { - insertingChild = asChild; - } - - /** - * Determines where new objects programmatically inserted into the children - * display group should be inserted, based on the value of insertingChild. - * If insertingChild, isInsertingAfter causes objects to be inserted at - * the end of the selected node's child list; otherwise, objects are inserted - * at the beginning of the list. - * If inserting as a sibling, isInsertingAfter causes objects to be inserted - * before the selected node in the selected node's parent's child list; - * otherwise, objects are inserted after the selected node in the child list. - * Default value is true. - */ - public boolean isInsertingAfter() - { - return insertingAfter; - } - - /** - * Determines where new objects programmatically inserted into the children - * display group should be inserted, based on the value of insertingChild. - * If insertingChild, isInsertingAfter causes objects to be inserted at - * the end of the selected node's child list; otherwise, objects are inserted - * at the beginning of the list. - * If inserting as a sibling, isInsertingAfter causes objects to be inserted - * before the selected node in the selected node's parent's child list; - * otherwise, objects are inserted after the selected node in the child list. - * Default value is true. - */ - public void setInsertingAfter( boolean after ) - { - insertingAfter = after; - } - - /** - * Called to by the children group's data source when it receives - * an insertObject message, usually after an object has been inserted - * into the children display group. - * Return the object that should be passed to the titles display - * group's data source's implementation of insertObject, or return - * null to prevent that method from being called.

- * This implementation inserts the specified object into the tree - * as determined by calling isInsertingChild and isInsertingAfter, - * then returns the unmodified object. If there's no selection, or - * no selection model, the root node is assumed to be selected. - * And if the root node is selected, the new node will obviously be - * inserted as a child. Override to customize. - */ - protected Object objectInsertedIntoChildrenGroup( Object anObject ) - { - // determine selection - DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot(); - if ( selectionModel != null ) - { - // get selected path - TreePath path = selectionModel.getSelectionPath(); - - // get selected node - if ( path != null ) - { - selectedNode = (DisplayGroupNode) path.getLastPathComponent(); - } - } - // determine location of insertion - int index = 0; - if ( ( isInsertingChild() ) || ( selectedNode == getRoot() ) ) - { - if ( isInsertingAfter() ) - { - index = selectedNode.getChildCount(); - } - } - else // inserting as sibling - { - DisplayGroupNode parentNode = selectedNode.getParentGroup(); - index = parentNode.getIndex( selectedNode ); - if ( isInsertingAfter() ) - { - index++; - } - selectedNode = parentNode; - } - - // insert and return - selectedNode.insertObjectAtIndex( anObject, index ); - return anObject; - } - - /** - * Called to by the children group's data source when it receives - * a deleteObject message, usually after an object has been deleted - * from the children display group. - * Return the object that should be passed to the titles display - * group's data source's implementation of deleteObject, or return - * null to prevent that method from being called.

- * This implementation deletes all instances of the selected object - * from the tree nodes that are currently loaded, and returns the - * unmodified object. Override to customize. - */ - protected Object objectDeletedFromChildrenGroup( Object anObject ) - { - TreePath[] paths = getPathsForObject( anObject ); - if ( paths != null ) - { - for ( int i = 0; i < paths.length; i++ ) - { - ((DisplayGroupNode)paths[i].getLastPathComponent()).removeFromParent(); - } - } - return anObject; - } - - /** - * Called to by the children group's data source to populate it - * with all selected nodes and their siblings. To customize, - * override this method, or specify a different data source for - * the children display group. - */ - protected NSArray objectsFetchedIntoChildrenGroup() - { - DisplayGroupNode node; - TreePath parentPath; - TreePath[] selectedPaths = selectionModel.getSelectionPaths(); - NSMutableArray objectList = new NSMutableArray(); - if ( selectedPaths != null ) - { - for ( int i = 0; i < selectedPaths.length; i++ ) - { - // root node is zero - ignore root node - if ( ( selectedPaths[i].getLastPathComponent() == rootNode ) ) - { - // select root in selectFromDisplayGroup() - pleaseSelectRootNode = true; - } - else - { - node = (DisplayGroupNode) - selectedPaths[i].getLastPathComponent(); - Object o = node.object(); - - // add all children of parent to object list - includes self - if ( node.parentGroup != null ) - { - Enumeration e = - node.parentGroup.displayedObjects().objectEnumerator(); - while ( e.hasMoreElements() ) - { - // add only if not already in list - o = e.nextElement(); - if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - objectList.addObject( o ); - } - } - } - else // no parent node - add the node by itself - { - // add only if not already in list - if ( objectList.indexOfIdenticalObject(o) == NSArray.NotFound ) - { - objectList.addObject( o ); - } - } - } - } - } - - // if no selection - if ( objectList.size() == 0 ) - { - // populate with children of root - objectList.addAll( rootNode.displayedObjects() ); - } - return objectList; - } - - /** - * Queues processRecentChanges to be run in the event queue. - */ - private void willChange() - { - EOObserverCenter.notifyObserversObjectWillChange( this ); - } - - /** - * Tells the children display group to refetch, so that it reflects - * any changes that were made in the node tree, - * and then updates the selection in the selection model. - * Triggered in response to willChange(). - */ - public void processRecentChanges() - { - Runnable update = new Runnable() - { - public void run() - { - removeAsListener(); // prevent data source refetch: see fetchObjects() - childrenDisplayGroup.fetch(); - addAsListener(); - selectFromDisplayGroup( childrenDisplayGroup ); - } - }; - if ( isListening ) - { - if ( selectionPaintedImmediately ) - { - // if painting selection immediately, run even later - // so that AWT's repaint event fires before we do. - SwingUtilities.invokeLater( update ); - } - else - { - // otherwise run now - update.run(); - } - } - } - - /** - * Delegates most behaviors to the specified data source, - * except fetchObjects, which calls fetchObjectsIntoChildrenGroup - * on the tree model association. If delegate is null, - * calls are passed to the superclass which is a PropertyDataSource. - */ - static class DelegatingTreeDataSource extends PropertyDataSource - { - TreeModelAssociation parentAssociation; - EODataSource delegateDataSource; - - public DelegatingTreeDataSource( - TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource ) - { - parentAssociation = aTreeModelAssociation; - delegateDataSource = aDataSource; - } - - /** - * Calls to delegateDataSource if it exists, otherwise - * calls to super. - */ - public Object createObject() - { - if ( delegateDataSource != null ) - { - return delegateDataSource.createObject(); - } - return super.createObject(); - } - - /** - * Calls objectInsertedIntoChildrenGroup, and if not null - * calls to delegateDataSource.insertObject if it exists, - * and super.insertObjectAtIndex if not. - */ - public void insertObjectAtIndex( Object anObject, int anIndex ) - { - anObject = - parentAssociation.objectInsertedIntoChildrenGroup( - anObject ); - if ( anObject != null ) - { - if ( delegateDataSource != null ) - { - if ( delegateDataSource instanceof OrderedDataSource ) - { - ((OrderedDataSource)delegateDataSource).insertObjectAtIndex( anObject, anIndex ); - } - else - { - delegateDataSource.insertObject( anObject ); - } - } - else - { - super.insertObjectAtIndex( anObject, anIndex ); - } - } - } - - /** - * Calls objectDeletedIntoChildrenGroup, and if not null - * calls to delegateDataSource if it exists. - */ - public void deleteObject( Object anObject ) - { - anObject = - parentAssociation.objectDeletedFromChildrenGroup( - anObject ); - if ( anObject != null ) - { - if ( delegateDataSource != null ) - { - delegateDataSource.deleteObject( anObject ); - } - super.deleteObject( anObject ); - } - } - - /** - * Overridden to return the delegate's editing context, - * the titles display group's editing context, - * and failing that calling to super. - */ - public EOEditingContext editingContext () - { - EOEditingContext result = null; - if ( delegateDataSource != null ) - { - result = delegateDataSource.editingContext(); - } - if ( result == null ) - { - EODataSource parentDataSource = - parentAssociation.titlesDisplayGroup.dataSource(); - if ( parentDataSource != this && parentDataSource != null ) - { - result = parentAssociation.titlesDisplayGroup. - dataSource().editingContext(); - } - } - if ( result == null ) - { - result = super.editingContext(); - } - return result; - } - - /** - * Returns a List containing the objects in this - * data source. - */ - public NSArray fetchObjects () - { - // if titles group is doing double-duty as children group - if ( parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup ) - { - // if we're not initiating this fetch - if ( parentAssociation.isListening ) - { - // need to call to delegate to see if we should update values - if ( delegateDataSource != null ) - { + Enumeration it = listeners.elements(); + while (it.hasMoreElements()) { + ((TreeModelListener) it.nextElement()).treeStructureChanged(event); + } + } + + /** + * Creates and returns a new display group node. + */ + public DisplayGroupNode createNode(EODisplayGroup aParentGroup, Object anObject) { + return new MutableDisplayGroupNode(this, aParentGroup, anObject); + } + + /** + * Gets whether new objects programmatically inserted into the children display + * group should be inserted as a child of the first selected node. If false, new + * objects are inserted as siblings of the first selected node. Default value is + * true. + */ + public boolean isInsertingChild() { + return insertingChild; + } + + /** + * Sets whether new objects programmatically inserted into the children display + * group should be inserted as a child of the first selected node. If false, new + * objects are inserted as siblings of the first selected node. Default value is + * true. + */ + public void setInsertingChild(boolean asChild) { + insertingChild = asChild; + } + + /** + * Determines where new objects programmatically inserted into the children + * display group should be inserted, based on the value of insertingChild. If + * insertingChild, isInsertingAfter causes objects to be inserted at the end of + * the selected node's child list; otherwise, objects are inserted at the + * beginning of the list. If inserting as a sibling, isInsertingAfter causes + * objects to be inserted before the selected node in the selected node's + * parent's child list; otherwise, objects are inserted after the selected node + * in the child list. Default value is true. + */ + public boolean isInsertingAfter() { + return insertingAfter; + } + + /** + * Determines where new objects programmatically inserted into the children + * display group should be inserted, based on the value of insertingChild. If + * insertingChild, isInsertingAfter causes objects to be inserted at the end of + * the selected node's child list; otherwise, objects are inserted at the + * beginning of the list. If inserting as a sibling, isInsertingAfter causes + * objects to be inserted before the selected node in the selected node's + * parent's child list; otherwise, objects are inserted after the selected node + * in the child list. Default value is true. + */ + public void setInsertingAfter(boolean after) { + insertingAfter = after; + } + + /** + * Called to by the children group's data source when it receives an + * insertObject message, usually after an object has been inserted into the + * children display group. Return the object that should be passed to the titles + * display group's data source's implementation of insertObject, or return null + * to prevent that method from being called.
+ *
+ * This implementation inserts the specified object into the tree as determined + * by calling isInsertingChild and isInsertingAfter, then returns the unmodified + * object. If there's no selection, or no selection model, the root node is + * assumed to be selected. And if the root node is selected, the new node will + * obviously be inserted as a child. Override to customize. + */ + protected Object objectInsertedIntoChildrenGroup(Object anObject) { + // determine selection + DisplayGroupNode selectedNode = (DisplayGroupNode) getRoot(); + if (selectionModel != null) { + // get selected path + TreePath path = selectionModel.getSelectionPath(); + + // get selected node + if (path != null) { + selectedNode = (DisplayGroupNode) path.getLastPathComponent(); + } + } + // determine location of insertion + int index = 0; + if ((isInsertingChild()) || (selectedNode == getRoot())) { + if (isInsertingAfter()) { + index = selectedNode.getChildCount(); + } + } else // inserting as sibling + { + DisplayGroupNode parentNode = selectedNode.getParentGroup(); + index = parentNode.getIndex(selectedNode); + if (isInsertingAfter()) { + index++; + } + selectedNode = parentNode; + } + + // insert and return + selectedNode.insertObjectAtIndex(anObject, index); + return anObject; + } + + /** + * Called to by the children group's data source when it receives a deleteObject + * message, usually after an object has been deleted from the children display + * group. Return the object that should be passed to the titles display group's + * data source's implementation of deleteObject, or return null to prevent that + * method from being called.
+ *
+ * This implementation deletes all instances of the selected object from the + * tree nodes that are currently loaded, and returns the unmodified object. + * Override to customize. + */ + protected Object objectDeletedFromChildrenGroup(Object anObject) { + TreePath[] paths = getPathsForObject(anObject); + if (paths != null) { + for (int i = 0; i < paths.length; i++) { + ((DisplayGroupNode) paths[i].getLastPathComponent()).removeFromParent(); + } + } + return anObject; + } + + /** + * Called to by the children group's data source to populate it with all + * selected nodes and their siblings. To customize, override this method, or + * specify a different data source for the children display group. + */ + protected NSArray objectsFetchedIntoChildrenGroup() { + DisplayGroupNode node; + TreePath parentPath; + TreePath[] selectedPaths = selectionModel.getSelectionPaths(); + NSMutableArray objectList = new NSMutableArray(); + if (selectedPaths != null) { + for (int i = 0; i < selectedPaths.length; i++) { + // root node is zero - ignore root node + if ((selectedPaths[i].getLastPathComponent() == rootNode)) { + // select root in selectFromDisplayGroup() + pleaseSelectRootNode = true; + } else { + node = (DisplayGroupNode) selectedPaths[i].getLastPathComponent(); + Object o = node.object(); + + // add all children of parent to object list - includes self + if (node.parentGroup != null) { + Enumeration e = node.parentGroup.displayedObjects().objectEnumerator(); + while (e.hasMoreElements()) { + // add only if not already in list + o = e.nextElement(); + if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) { + objectList.addObject(o); + } + } + } else // no parent node - add the node by itself + { + // add only if not already in list + if (objectList.indexOfIdenticalObject(o) == NSArray.NotFound) { + objectList.addObject(o); + } + } + } + } + } + + // if no selection + if (objectList.size() == 0) { + // populate with children of root + objectList.addAll(rootNode.displayedObjects()); + } + return objectList; + } + + /** + * Queues processRecentChanges to be run in the event queue. + */ + private void willChange() { + EOObserverCenter.notifyObserversObjectWillChange(this); + } + + /** + * Tells the children display group to refetch, so that it reflects any changes + * that were made in the node tree, and then updates the selection in the + * selection model. Triggered in response to willChange(). + */ + public void processRecentChanges() { + Runnable update = new Runnable() { + public void run() { + removeAsListener(); // prevent data source refetch: see fetchObjects() + childrenDisplayGroup.fetch(); + addAsListener(); + selectFromDisplayGroup(childrenDisplayGroup); + } + }; + if (isListening) { + if (selectionPaintedImmediately) { + // if painting selection immediately, run even later + // so that AWT's repaint event fires before we do. + SwingUtilities.invokeLater(update); + } else { + // otherwise run now + update.run(); + } + } + } + + /** + * Delegates most behaviors to the specified data source, except fetchObjects, + * which calls fetchObjectsIntoChildrenGroup on the tree model association. If + * delegate is null, calls are passed to the superclass which is a + * PropertyDataSource. + */ + static class DelegatingTreeDataSource extends PropertyDataSource { + TreeModelAssociation parentAssociation; + EODataSource delegateDataSource; + + public DelegatingTreeDataSource(TreeModelAssociation aTreeModelAssociation, EODataSource aDataSource) { + parentAssociation = aTreeModelAssociation; + delegateDataSource = aDataSource; + } + + /** + * Calls to delegateDataSource if it exists, otherwise calls to super. + */ + public Object createObject() { + if (delegateDataSource != null) { + return delegateDataSource.createObject(); + } + return super.createObject(); + } + + /** + * Calls objectInsertedIntoChildrenGroup, and if not null calls to + * delegateDataSource.insertObject if it exists, and super.insertObjectAtIndex + * if not. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + anObject = parentAssociation.objectInsertedIntoChildrenGroup(anObject); + if (anObject != null) { + if (delegateDataSource != null) { + if (delegateDataSource instanceof OrderedDataSource) { + ((OrderedDataSource) delegateDataSource).insertObjectAtIndex(anObject, anIndex); + } else { + delegateDataSource.insertObject(anObject); + } + } else { + super.insertObjectAtIndex(anObject, anIndex); + } + } + } + + /** + * Calls objectDeletedIntoChildrenGroup, and if not null calls to + * delegateDataSource if it exists. + */ + public void deleteObject(Object anObject) { + anObject = parentAssociation.objectDeletedFromChildrenGroup(anObject); + if (anObject != null) { + if (delegateDataSource != null) { + delegateDataSource.deleteObject(anObject); + } + super.deleteObject(anObject); + } + } + + /** + * Overridden to return the delegate's editing context, the titles display + * group's editing context, and failing that calling to super. + */ + public EOEditingContext editingContext() { + EOEditingContext result = null; + if (delegateDataSource != null) { + result = delegateDataSource.editingContext(); + } + if (result == null) { + EODataSource parentDataSource = parentAssociation.titlesDisplayGroup.dataSource(); + if (parentDataSource != this && parentDataSource != null) { + result = parentAssociation.titlesDisplayGroup.dataSource().editingContext(); + } + } + if (result == null) { + result = super.editingContext(); + } + return result; + } + + /** + * Returns a List containing the objects in this data source. + */ + public NSArray fetchObjects() { + // if titles group is doing double-duty as children group + if (parentAssociation.titlesDisplayGroup == parentAssociation.childrenDisplayGroup) { + // if we're not initiating this fetch + if (parentAssociation.isListening) { + // need to call to delegate to see if we should update values + if (delegateDataSource != null) { // System.out.println( "fetching from delegate (slow!)" ); - NSArray result = delegateDataSource.fetchObjects(); - NSArray rootObjects = parentAssociation.rootNode.displayedObjects(); - // if titles data source has different objects, return them - if ( rootObjects.count() != result.count() - || ! rootObjects.containsAll( result ) ) - { - // this will force the root node to repopulate in subjectChanged() + NSArray result = delegateDataSource.fetchObjects(); + NSArray rootObjects = parentAssociation.rootNode.displayedObjects(); + // if titles data source has different objects, return them + if (rootObjects.count() != result.count() || !rootObjects.containsAll(result)) { + // this will force the root node to repopulate in subjectChanged() //System.out.println( "fetchObjects: data source" ); - return result; - } - } - } - } - // otherwise: just repopulate the titles group + return result; + } + } + } + } + // otherwise: just repopulate the titles group //System.out.println( "fetchObjects: objectsFetchedIntoChildrenGroup" ); - return parentAssociation.objectsFetchedIntoChildrenGroup(); - } - - /** - * 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 EODataSource - dataSourceQualifiedByKey ( String aKey ) - { - if ( delegateDataSource != null ) - { - return delegateDataSource.dataSourceQualifiedByKey( aKey ); - } - return null; - } - - /** - * 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 ) - { - if ( delegateDataSource != null ) - { - delegateDataSource.qualifyWithRelationshipKey( aKey, anObject ); - } - } - - /** - * Returns the value from the delegateDataSource, if it exists. - * Otherwise calls super. - */ - public EOClassDescription classDescriptionForObjects() - { - if ( delegateDataSource != null ) - { - return delegateDataSource.classDescriptionForObjects(); - } - return super.classDescriptionForObjects(); - } - - } - + return parentAssociation.objectsFetchedIntoChildrenGroup(); + } + + /** + * 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 EODataSource dataSourceQualifiedByKey(String aKey) { + if (delegateDataSource != null) { + return delegateDataSource.dataSourceQualifiedByKey(aKey); + } + return null; + } + + /** + * 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) { + if (delegateDataSource != null) { + delegateDataSource.qualifyWithRelationshipKey(aKey, anObject); + } + } + + /** + * Returns the value from the delegateDataSource, if it exists. Otherwise calls + * super. + */ + public EOClassDescription classDescriptionForObjects() { + if (delegateDataSource != null) { + return delegateDataSource.classDescriptionForObjects(); + } + return super.classDescriptionForObjects(); + } + + } + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.20 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.20 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.19 2002/05/03 21:41:18 mpowers - * No longer clearing the selection model when updating from display group: - * we now only modify if a change needs to be made. - * No longer listening for selection change during firing of delete events: - * delete events cause JTree's to update their selection model. - * Fix for paintsSelectionImmediately: TreeAssociation.processRecentChanges() - * must happen after the screen is painted, or the selection is not displayed. + * Revision 1.19 2002/05/03 21:41:18 mpowers No longer clearing the selection + * model when updating from display group: we now only modify if a change needs + * to be made. No longer listening for selection change during firing of delete + * events: delete events cause JTree's to update their selection model. Fix for + * paintsSelectionImmediately: TreeAssociation.processRecentChanges() must + * happen after the screen is painted, or the selection is not displayed. * - * Revision 1.18 2002/04/23 19:12:28 mpowers - * Reimplemented fireEventsForChanges. Fitter and happier. + * Revision 1.18 2002/04/23 19:12:28 mpowers Reimplemented fireEventsForChanges. + * Fitter and happier. * - * Revision 1.17 2002/04/19 21:18:46 mpowers - * Removed tree event coalescing, which was causing way too many problems. - * The fireChangeEvent algorithm is way faster than before, so we should - * still be better off than before. At least now, we don't have to track - * whether the view component has encountered a particular node. + * Revision 1.17 2002/04/19 21:18:46 mpowers Removed tree event coalescing, + * which was causing way too many problems. The fireChangeEvent algorithm is way + * faster than before, so we should still be better off than before. At least + * now, we don't have to track whether the view component has encountered a + * particular node. * - * Revision 1.16 2002/04/18 20:36:11 mpowers - * TreeModelAssociation now populates children group before selected objects. - * Got rid of the forceOnSync workaround for cancelled selection change. + * Revision 1.16 2002/04/18 20:36:11 mpowers TreeModelAssociation now populates + * children group before selected objects. Got rid of the forceOnSync workaround + * for cancelled selection change. * - * Revision 1.15 2002/04/15 21:52:50 mpowers - * Tightening up TreeModelAssociation and DisplayGroupNode. - * Now only firing root structure changed once. - * Now disposing of root's children. - * Better event coalescing. + * Revision 1.15 2002/04/15 21:52:50 mpowers Tightening up TreeModelAssociation + * and DisplayGroupNode. Now only firing root structure changed once. Now + * disposing of root's children. Better event coalescing. * - * Revision 1.14 2002/04/12 21:05:58 mpowers - * Now distinguishing changes in titles group even better. + * Revision 1.14 2002/04/12 21:05:58 mpowers Now distinguishing changes in + * titles group even better. * - * Revision 1.11 2002/04/10 21:20:04 mpowers - * Better handling for tree nodes when working with editing contexts. - * Better handling for invalidation. No longer broadcasting events - * when nodes have not been "registered" in the tree. + * Revision 1.11 2002/04/10 21:20:04 mpowers Better handling for tree nodes when + * working with editing contexts. Better handling for invalidation. No longer + * broadcasting events when nodes have not been "registered" in the tree. * - * Revision 1.10 2002/04/03 20:01:24 mpowers - * Removed printlns. + * Revision 1.10 2002/04/03 20:01:24 mpowers Removed printlns. * - * Revision 1.8 2002/03/11 03:16:28 mpowers - * Better handling of change events; coalescing changes to children group. + * Revision 1.8 2002/03/11 03:16:28 mpowers Better handling of change events; + * coalescing changes to children group. * - * Revision 1.7 2002/03/08 23:19:57 mpowers - * Refactoring of DelegatingTreeDataSource to facilitate binding of titles - * and children aspects to the same display group. + * Revision 1.7 2002/03/08 23:19:57 mpowers Refactoring of + * DelegatingTreeDataSource to facilitate binding of titles and children aspects + * to the same display group. * - * Revision 1.6 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.6 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.5 2002/03/06 13:04:16 mpowers - * Implemented cascading qualifiers in tree nodes. + * Revision 1.5 2002/03/06 13:04:16 mpowers Implemented cascading qualifiers in + * tree nodes. * - * Revision 1.4 2002/03/04 22:47:48 mpowers - * Fixed sort ordering for titles group. Optimization for delegate selection. + * Revision 1.4 2002/03/04 22:47:48 mpowers Fixed sort ordering for titles + * group. Optimization for delegate selection. * - * Revision 1.3 2002/03/04 12:28:47 mpowers - * Revised case where children and titles are bound to same display group. + * Revision 1.3 2002/03/04 12:28:47 mpowers Revised case where children and + * titles are bound to same display group. * - * Revision 1.2 2002/03/01 23:42:09 mpowers - * Implemented TreeColumnAssociation, and updated documentation. + * Revision 1.2 2002/03/01 23:42:09 mpowers Implemented TreeColumnAssociation, + * and updated documentation. * - * Revision 1.1 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.1 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.38 2002/02/18 03:46:08 mpowers - * Implemented TreeTableCellRenderer. + * Revision 1.38 2002/02/18 03:46:08 mpowers Implemented TreeTableCellRenderer. * - * Revision 1.37 2002/02/13 21:20:15 mpowers - * Updated comments. + * Revision 1.37 2002/02/13 21:20:15 mpowers Updated comments. * - * Revision 1.36 2001/11/21 15:13:25 mpowers - * Better repainting for selectionPaintedImmediately. - * Better handling for selection with multiple instances of the same - * object in the tree (from yjcheung). + * Revision 1.36 2001/11/21 15:13:25 mpowers Better repainting for + * selectionPaintedImmediately. Better handling for selection with multiple + * instances of the same object in the tree (from yjcheung). * - * Revision 1.35 2001/11/20 19:13:51 mpowers - * Finished implementation of children group's specialized data source. + * Revision 1.35 2001/11/20 19:13:51 mpowers Finished implementation of children + * group's specialized data source. * - * Revision 1.34 2001/11/19 16:30:37 mpowers - * Tree repaint strategy is now a preference: selectionPaintedImmediately. + * Revision 1.34 2001/11/19 16:30:37 mpowers Tree repaint strategy is now a + * preference: selectionPaintedImmediately. * - * Revision 1.33 2001/11/15 17:56:41 mpowers - * Initial implementation of data source for the children display group. + * Revision 1.33 2001/11/15 17:56:41 mpowers Initial implementation of data + * source for the children display group. * - * Revision 1.32 2001/11/14 00:05:54 mpowers - * Eliminated the run later in favor of repainting the component immediately. - * This makes things more predictable for users of the association that - * want to listen to mouse or selection events on the tree. + * Revision 1.32 2001/11/14 00:05:54 mpowers Eliminated the run later in favor + * of repainting the component immediately. This makes things more predictable + * for users of the association that want to listen to mouse or selection events + * on the tree. * - * Revision 1.31 2001/11/02 20:43:15 mpowers - * Fixes for delegate's shouldChangeSelection veto (from yjcheung). + * Revision 1.31 2001/11/02 20:43:15 mpowers Fixes for delegate's + * shouldChangeSelection veto (from yjcheung). * - * Revision 1.30 2001/10/29 20:42:56 mpowers - * On selection change, repainting tree before notifying display group; - * using NSRunLoop instead of SwingUtilities. + * Revision 1.30 2001/10/29 20:42:56 mpowers On selection change, repainting + * tree before notifying display group; using NSRunLoop instead of + * SwingUtilities. * - * Revision 1.29 2001/10/12 20:12:53 mpowers - * Better handling of selection change vetoing when changing selection - * to a node that is not the sibling of the originally selected node. + * Revision 1.29 2001/10/12 20:12:53 mpowers Better handling of selection change + * vetoing when changing selection to a node that is not the sibling of the + * originally selected node. * - * Revision 1.28 2001/09/14 13:40:26 mpowers - * User-initiated selection changes are now handled on the next event loop - * so that the component repaints the new selection before any potentially - * lengthy logic is triggered by the selection change. + * Revision 1.28 2001/09/14 13:40:26 mpowers User-initiated selection changes + * are now handled on the next event loop so that the component repaints the new + * selection before any potentially lengthy logic is triggered by the selection + * change. * - * Revision 1.27 2001/09/10 14:10:03 mpowers - * Tree now handles multiple instances of the same object. + * Revision 1.27 2001/09/10 14:10:03 mpowers Tree now handles multiple instances + * of the same object. * - * Revision 1.26 2001/07/18 13:03:32 mpowers - * TreeNodes now refetch only on demand. Previously, once a node had - * been fetched, it was always refetched after an invalidate, even if - * the node was not being displayed. + * Revision 1.26 2001/07/18 13:03:32 mpowers TreeNodes now refetch only on + * demand. Previously, once a node had been fetched, it was always refetched + * after an invalidate, even if the node was not being displayed. * - * Revision 1.25 2001/05/14 15:25:35 mpowers - * No longer copying titles group's data source to children group. + * Revision 1.25 2001/05/14 15:25:35 mpowers No longer copying titles group's + * data source to children group. * - * Revision 1.24 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.24 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.23 2001/05/01 00:52:32 mpowers - * Implemented breadth-first traversal of tree for node. + * Revision 1.23 2001/05/01 00:52:32 mpowers Implemented breadth-first traversal + * of tree for node. * - * Revision 1.22 2001/04/26 01:15:19 mpowers - * Major clean-up of DisplayGroupNode: fitter, happier, more productive. + * Revision 1.22 2001/04/26 01:15:19 mpowers Major clean-up of DisplayGroupNode: + * fitter, happier, more productive. * - * Revision 1.21 2001/04/22 23:13:35 mpowers - * Minor bug. + * Revision 1.21 2001/04/22 23:13:35 mpowers Minor bug. * - * Revision 1.20 2001/04/22 23:05:33 mpowers - * Totally revised DisplayGroupNode so each object gets its own node - * (so the nodes are no longer fixed by index). + * Revision 1.20 2001/04/22 23:05:33 mpowers Totally revised DisplayGroupNode so + * each object gets its own node (so the nodes are no longer fixed by index). * - * Revision 1.19 2001/04/21 23:06:33 mpowers - * A major revisiting to support the revising of DisplayGroupNode. + * Revision 1.19 2001/04/21 23:06:33 mpowers A major revisiting to support the + * revising of DisplayGroupNode. * - * Revision 1.18 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.18 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.17 2001/03/29 21:35:08 mpowers - * Now handling circular references in the graph. + * Revision 1.17 2001/03/29 21:35:08 mpowers Now handling circular references in + * the graph. * - * Revision 1.16 2001/03/22 21:25:42 mpowers - * Fixed some nasty issues with jtree's internal state and array bounds. + * Revision 1.16 2001/03/22 21:25:42 mpowers Fixed some nasty issues with + * jtree's internal state and array bounds. * - * Revision 1.15 2001/03/19 21:37:58 mpowers - * Improved refresh of titles display group. - * Fixed dangling selection problem after refresh. + * Revision 1.15 2001/03/19 21:37:58 mpowers Improved refresh of titles display + * group. Fixed dangling selection problem after refresh. * - * Revision 1.14 2001/03/09 22:08:57 mpowers - * Trying to handle the dangling reference problem after an update. + * Revision 1.14 2001/03/09 22:08:57 mpowers Trying to handle the dangling + * reference problem after an update. * - * Revision 1.13 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.13 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.12 2001/01/25 02:16:25 mpowers - * TreeModelAssociation now returns DisplayGroupNode.getUserObject. + * Revision 1.12 2001/01/25 02:16:25 mpowers TreeModelAssociation now returns + * DisplayGroupNode.getUserObject. * - * Revision 1.11 2001/01/24 18:14:40 mpowers - * Fixed problem with leaving children aspect unspecified. + * Revision 1.11 2001/01/24 18:14:40 mpowers Fixed problem with leaving children + * aspect unspecified. * - * Revision 1.10 2001/01/24 17:49:15 mpowers - * Added getObjectForNode and getNodeForObject convenience methods. + * Revision 1.10 2001/01/24 17:49:15 mpowers Added getObjectForNode and + * getNodeForObject convenience methods. * - * Revision 1.9 2001/01/24 17:44:11 mpowers - * Renamed getPathForNode to getPathForObject to be more precise. - * And created a new getPathForNode method. + * Revision 1.9 2001/01/24 17:44:11 mpowers Renamed getPathForNode to + * getPathForObject to be more precise. And created a new getPathForNode method. * - * Revision 1.8 2001/01/24 17:20:29 mpowers - * Children display group now holds siblings of selected objects - * in addition to the selected objects. + * Revision 1.8 2001/01/24 17:20:29 mpowers Children display group now holds + * siblings of selected objects in addition to the selected objects. * - * Revision 1.5 2001/01/19 23:21:15 mpowers - * Fine tuning events broadcast from TreeModelAssociation. + * Revision 1.5 2001/01/19 23:21:15 mpowers Fine tuning events broadcast from + * TreeModelAssociation. * - * Revision 1.4 2001/01/18 21:27:29 mpowers - * Major rework of TreeModelAssociation. + * Revision 1.4 2001/01/18 21:27:29 mpowers Major rework of + * TreeModelAssociation. * - * Revision 1.2 2001/01/11 20:29:19 mpowers - * Expanded access to tree event firing methods. + * Revision 1.2 2001/01/11 20:29:19 mpowers Expanded access to tree event firing + * methods. * - * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:49:18 mpowers Contributing wotonomy. * - * Revision 1.20 2000/12/20 16:25:42 michael - * Added log to all files. + * Revision 1.20 2000/12/20 16:25:42 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java index 1fef587..579a595 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AbsoluteLayout.java @@ -25,50 +25,43 @@ import java.awt.LayoutManager; import java.io.Serializable; /** - * AbsoluteLayout specifies that all components in the - * container will be placed according to their size - * and their location relative to the container's origin.

+ * AbsoluteLayout specifies that all components in the container will be placed + * according to their size and their location relative to the container's + * origin.
+ *
* - * You can achieve the same effect by setting a container's - * layout manager to null, but this class allows you to subclass - * it if you need specific control or functionality. + * You can achieve the same effect by setting a container's layout manager to + * null, but this class allows you to subclass it if you need specific control + * or functionality. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public class AbsoluteLayout implements LayoutManager, Serializable -{ - public void addLayoutComponent(String name, - Component comp) - { - } +public class AbsoluteLayout implements LayoutManager, Serializable { + public void addLayoutComponent(String name, Component comp) { + } - public void removeLayoutComponent(Component comp) - { - } + public void removeLayoutComponent(Component comp) { + } - public Dimension preferredLayoutSize(Container parent) - { - return minimumLayoutSize( parent ); - } + public Dimension preferredLayoutSize(Container parent) { + return minimumLayoutSize(parent); + } - public Dimension minimumLayoutSize(Container parent) - { - int width = 0; - int height = 0; + public Dimension minimumLayoutSize(Container parent) { + int width = 0; + int height = 0; - Component[] c = parent.getComponents(); - for ( int i = 0; i < c.length; i++ ) - { - width = Math.max( width, c[i].getLocation().x + c[i].getBounds().width ); - height = Math.max( height, c[i].getLocation().y + c[i].getBounds().height ); - } + Component[] c = parent.getComponents(); + for (int i = 0; i < c.length; i++) { + width = Math.max(width, c[i].getLocation().x + c[i].getBounds().width); + height = Math.max(height, c[i].getLocation().y + c[i].getBounds().height); + } - return new Dimension( width, height ); - } + return new Dimension(width, height); + } - public void layoutContainer(Container parent) - { - } + public void layoutContainer(Container parent) { + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java index c36f5e2..9300d35 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlphaTextField.java @@ -19,317 +19,286 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.ui.swing.components; /** -* AlphaTextField is a "smart" text field that restricts the user's input. The -* input can be restricted to alphabetic, alphanumeric, or all characters. The -* maximum number of characters can also be limited. -* The defaults for this component is alphabetic only string of unlimited length. -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class AlphaTextField extends SmartTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Sets the input to alphabetic characters only. The characters "a-z" and "A-Z" -* are the only valid characters. All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHABETIC = 0; - -/** -* Sets the input to alphanumeric characters only. The characters "a-z", "A-Z" -* and "0-9" are the only valid characters. All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHANUMERIC = 1; - -/** -* Sets the input to alphanumeric characters and a few special characters only. -* The valid characters are "a-z", "A-Z", "0-9", space, "-", "_", "\", and ":". -* This is helpful for file names (with paths) as input strings. -* All other characters will be ignored. -* @see #getAlphaType() -*/ - public static final int ALPHANUMERIC_PLUS = 2; - -/** -* Sets the input to all characters without restriction. -* @see #getAlphaType() -*/ - public static final int ALL = 3; - - -/******************************* -* DATA MEMBERS -*******************************/ - - // The level of input restrictions, defaults to ALPHABETIC - private int alphaType; - - // The maximum length of the input string, defaults to 0, no maximum - private int stringLength; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* The default constructor of this class. The default string of this text -* field is set to the empty string (""). The maximum length is set to 0, -* which specifies no limit. -*/ - public AlphaTextField() - { - this("", 0); - } - -/** -* Constructor of this class with the initial text of the text field specified. -* The maximum length is set to 0, which specifies no limit. -* @param text Initial text of the text field. -*/ - public AlphaTextField(String text) - { - this(text, 0); - } - -/** -* Constructor of this class with width (in columns) of the text field specified. -* The initial text is set to the empty string (""). The maximum length is set -* to 0, which specifies no limit. -* @param columns The width of the text field in characters. -*/ - public AlphaTextField(int columns) - { - this("", columns); - } - -/** -* Constructor of this class with width (in columns) and initial text of the -* text field specified. The maximum length is set to 0, which specifies no limit. -* @param text Initial text of the text field. -* @param columns The width of the text field in characters. -*/ - public AlphaTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Constructor that allows the user to set the Alpha type of the text field -* and the maximum string length. -* @param anAlphaType The character restriction type. -* @param aLength The maximum number of characters allowed in the string. -*/ - public AlphaTextField(int anAlphaType, int aLength) - { - super( "", 0 ); - setAlphaType( anAlphaType ); - setStringLength( aLength ); - } - -/** -* Gets the current restriction type of this text field. -* @see #ALPHABETIC -* @see #ALPHANUMERIC -* @see #ALPHANUMERIC_PLUS -* @see #ALL -* @return The current restriction type as defined by the constansts of this class. -*/ - public int getAlphaType() - { - return alphaType; - } - -/** -* Sets the restriction type of this text field. -* @see #ALPHABETIC -* @see #ALPHANUMERIC -* @see #ALPHANUMERIC_PLUS -* @see #ALL -* @param newAlphaType The restriction of this text field. -*/ - public void setAlphaType(int newAlphaType) - { - switch (newAlphaType) - { - case ALPHABETIC: - case ALPHANUMERIC: - case ALPHANUMERIC_PLUS: - case ALL: - { - alphaType = newAlphaType; - break; - } - default: - { - alphaType = ALPHABETIC; - break; - } - } - } - -/** -* Sets the maximum string length of this text field. If the length is set to -* zero, then there is no limit. The default string length is zero. Negative -* sizes will set the length to zero. -* @param newStringLength The maximum length of the string that the user can input. -*/ - public void setStringLength(int newStringLength) - { - if (newStringLength < 0) - { - stringLength = 0; - } - else - { - stringLength = newStringLength; - } - } - -/** -* Gets the current length of the maximum string size the user can enter. -* @return The maximum length the string of the text field can be. -*/ - public int getStringLength() - { - return stringLength; - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - - protected boolean isValidCharacter(char aChar) - { - // if its a non-printable character, then its ok - if ((aChar < ' ') || (aChar > '~')) - { - return true; - } - - // can only be a printable character now, check it for validation - return isValidCharacterType(aChar); - } - - protected boolean isValidString(String aString) - { - if (aString.length() > stringLength) - { - return false; - } - - for (int i = 0; i < aString.length(); ++i) - { - if (!(isValidCharacterType(aString.charAt(i)))) - { - return false; - } - } - - return true; - } - - protected void postProcessing() - { - // No need to do anything. - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - - private boolean isValidCharacterType(char aChar) - { - switch (alphaType) - { - case ALPHABETIC: - { - if (!(isValidAlphabeticCharacter(aChar))) - { - return false; - } - break; - } - case ALPHANUMERIC: - { - if (!(isValidAlphanumericCharacter(aChar))) - { - return false; - } - break; - } - case ALPHANUMERIC_PLUS: - { - if (!(isValidAlphanumericPlusCharacter(aChar))) - { - return false; - } - break; - } - case ALL: - { - if (!(isValidAllCharacter(aChar))) - { - return false; - } - break; - } - default: - { - return false; - } - } - - return true; - } - - private boolean isValidAlphabeticCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z'))) - { - return false; - } - return true; - } - - private boolean isValidAlphanumericCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) - { - return false; - } - return true; - } - - private boolean isValidAlphanumericPlusCharacter(char aChar) - { - if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) - { - if ((aChar != ' ') && (aChar != '_') && (aChar != '-') && (aChar != ':') && (aChar != '\\')) - { - return false; - } - } - return true; - } - - private boolean isValidAllCharacter(char aChar) - { - if ((aChar < ' ') || (aChar > '~')) - { - return false; - } - return true; - } + * AlphaTextField is a "smart" text field that restricts the user's input. The + * input can be restricted to alphabetic, alphanumeric, or all characters. The + * maximum number of characters can also be limited. The defaults for this + * component is alphabetic only string of unlimited length. + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class AlphaTextField extends SmartTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Sets the input to alphabetic characters only. The characters "a-z" and "A-Z" + * are the only valid characters. All other characters will be ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHABETIC = 0; + + /** + * Sets the input to alphanumeric characters only. The characters "a-z", "A-Z" + * and "0-9" are the only valid characters. All other characters will be + * ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHANUMERIC = 1; + + /** + * Sets the input to alphanumeric characters and a few special characters only. + * The valid characters are "a-z", "A-Z", "0-9", space, "-", "_", "\", and ":". + * This is helpful for file names (with paths) as input strings. All other + * characters will be ignored. + * + * @see #getAlphaType() + */ + public static final int ALPHANUMERIC_PLUS = 2; + + /** + * Sets the input to all characters without restriction. + * + * @see #getAlphaType() + */ + public static final int ALL = 3; + + /******************************* + * DATA MEMBERS + *******************************/ + + // The level of input restrictions, defaults to ALPHABETIC + private int alphaType; + + // The maximum length of the input string, defaults to 0, no maximum + private int stringLength; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * The default constructor of this class. The default string of this text field + * is set to the empty string (""). The maximum length is set to 0, which + * specifies no limit. + */ + public AlphaTextField() { + this("", 0); + } + + /** + * Constructor of this class with the initial text of the text field specified. + * The maximum length is set to 0, which specifies no limit. + * + * @param text Initial text of the text field. + */ + public AlphaTextField(String text) { + this(text, 0); + } + + /** + * Constructor of this class with width (in columns) of the text field + * specified. The initial text is set to the empty string (""). The maximum + * length is set to 0, which specifies no limit. + * + * @param columns The width of the text field in characters. + */ + public AlphaTextField(int columns) { + this("", columns); + } + + /** + * Constructor of this class with width (in columns) and initial text of the + * text field specified. The maximum length is set to 0, which specifies no + * limit. + * + * @param text Initial text of the text field. + * @param columns The width of the text field in characters. + */ + public AlphaTextField(String text, int columns) { + super(text, columns); + } + + /** + * Constructor that allows the user to set the Alpha type of the text field and + * the maximum string length. + * + * @param anAlphaType The character restriction type. + * @param aLength The maximum number of characters allowed in the string. + */ + public AlphaTextField(int anAlphaType, int aLength) { + super("", 0); + setAlphaType(anAlphaType); + setStringLength(aLength); + } + + /** + * Gets the current restriction type of this text field. + * + * @see #ALPHABETIC + * @see #ALPHANUMERIC + * @see #ALPHANUMERIC_PLUS + * @see #ALL + * @return The current restriction type as defined by the constansts of this + * class. + */ + public int getAlphaType() { + return alphaType; + } + + /** + * Sets the restriction type of this text field. + * + * @see #ALPHABETIC + * @see #ALPHANUMERIC + * @see #ALPHANUMERIC_PLUS + * @see #ALL + * @param newAlphaType The restriction of this text field. + */ + public void setAlphaType(int newAlphaType) { + switch (newAlphaType) { + case ALPHABETIC: + case ALPHANUMERIC: + case ALPHANUMERIC_PLUS: + case ALL: { + alphaType = newAlphaType; + break; + } + default: { + alphaType = ALPHABETIC; + break; + } + } + } + + /** + * Sets the maximum string length of this text field. If the length is set to + * zero, then there is no limit. The default string length is zero. Negative + * sizes will set the length to zero. + * + * @param newStringLength The maximum length of the string that the user can + * input. + */ + public void setStringLength(int newStringLength) { + if (newStringLength < 0) { + stringLength = 0; + } else { + stringLength = newStringLength; + } + } + + /** + * Gets the current length of the maximum string size the user can enter. + * + * @return The maximum length the string of the text field can be. + */ + public int getStringLength() { + return stringLength; + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + protected boolean isValidCharacter(char aChar) { + // if its a non-printable character, then its ok + if ((aChar < ' ') || (aChar > '~')) { + return true; + } + + // can only be a printable character now, check it for validation + return isValidCharacterType(aChar); + } + + protected boolean isValidString(String aString) { + if (aString.length() > stringLength) { + return false; + } + + for (int i = 0; i < aString.length(); ++i) { + if (!(isValidCharacterType(aString.charAt(i)))) { + return false; + } + } + + return true; + } + + protected void postProcessing() { + // No need to do anything. + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + private boolean isValidCharacterType(char aChar) { + switch (alphaType) { + case ALPHABETIC: { + if (!(isValidAlphabeticCharacter(aChar))) { + return false; + } + break; + } + case ALPHANUMERIC: { + if (!(isValidAlphanumericCharacter(aChar))) { + return false; + } + break; + } + case ALPHANUMERIC_PLUS: { + if (!(isValidAlphanumericPlusCharacter(aChar))) { + return false; + } + break; + } + case ALL: { + if (!(isValidAllCharacter(aChar))) { + return false; + } + break; + } + default: { + return false; + } + } + + return true; + } + + private boolean isValidAlphabeticCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z'))) { + return false; + } + return true; + } + + private boolean isValidAlphanumericCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) { + return false; + } + return true; + } + + private boolean isValidAlphanumericPlusCharacter(char aChar) { + if (((aChar < 'A') || (aChar > 'Z')) && ((aChar < 'a') || (aChar > 'z')) && ((aChar < '0') || (aChar > '9'))) { + if ((aChar != ' ') && (aChar != '_') && (aChar != '-') && (aChar != ':') && (aChar != '\\')) { + return false; + } + } + return true; + } + + private boolean isValidAllCharacter(char aChar) { + if ((aChar < ' ') || (aChar > '~')) { + return false; + } + return true; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java index 46d2693..fb4824c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/AlternatingRowCellRenderer.java @@ -28,102 +28,79 @@ import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that wraps another TableCellRenderer -* and sets the background to the specified color for odd-numbered rows. -* This makes every other row appear to be a different color, -* which helps users distinguish rows of data in densely-packed -* tables. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that wraps another TableCellRenderer and sets the + * background to the specified color for odd-numbered rows. This makes every + * other row appear to be a different color, which helps users distinguish rows + * of data in densely-packed tables. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class AlternatingRowCellRenderer implements TableCellRenderer { - protected TableCellRenderer wrappedRenderer; - protected Color alternateColor; - - /** - * Default constructor uses a lighter shade of the system control color - * and wraps a DefaultTableCellRenderer. - */ - public AlternatingRowCellRenderer() - { - this( new DefaultTableCellRenderer() ); - } - - /** - * Uses the specified color for the background of the alternating rows, - * and wraps a DefaultTableCellRenderer. - */ - public AlternatingRowCellRenderer( - Color aColor ) - { - this( aColor, new DefaultTableCellRenderer() ); - } + protected TableCellRenderer wrappedRenderer; + protected Color alternateColor; - /** - * Uses the uses a lighter shade of the system control color - * for the background of the alternating rows, - * and wraps the specified TableCellRenderer. - */ - public AlternatingRowCellRenderer( - TableCellRenderer aRenderer ) - { - Color c = UIManager.getColor( "control" ); - c = new Color( // lighten this color just slightly - (int) ( c.getRed() + ( ( 255 - c.getRed() ) / 1.5 ) ), - (int) ( c.getGreen() + ( ( 255 - c.getGreen() ) / 1.5 ) ), - (int) ( c.getBlue() + ( ( 255 - c.getBlue() ) / 1.5 ) ) ); + /** + * Default constructor uses a lighter shade of the system control color and + * wraps a DefaultTableCellRenderer. + */ + public AlternatingRowCellRenderer() { + this(new DefaultTableCellRenderer()); + } - alternateColor = c; - wrappedRenderer = aRenderer; - } + /** + * Uses the specified color for the background of the alternating rows, and + * wraps a DefaultTableCellRenderer. + */ + public AlternatingRowCellRenderer(Color aColor) { + this(aColor, new DefaultTableCellRenderer()); + } - /** - * Uses the specified color for the background of the alternating rows, - * and wraps the specified TableCellRenderer. - */ - public AlternatingRowCellRenderer( - Color aColor, TableCellRenderer aRenderer ) - { - alternateColor = aColor; - wrappedRenderer = aRenderer; - } + /** + * Uses the uses a lighter shade of the system control color for the background + * of the alternating rows, and wraps the specified TableCellRenderer. + */ + public AlternatingRowCellRenderer(TableCellRenderer aRenderer) { + Color c = UIManager.getColor("control"); + c = new Color( // lighten this color just slightly + (int) (c.getRed() + ((255 - c.getRed()) / 1.5)), (int) (c.getGreen() + ((255 - c.getGreen()) / 1.5)), + (int) (c.getBlue() + ((255 - c.getBlue()) / 1.5))); - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - Component result = wrappedRenderer.getTableCellRendererComponent( - table, value, isSelected, hasFocus, row, column ); - if ( ! isSelected ) - { - if ( row % 2 == 0 ) - { - if ( ! result.getBackground().equals( table.getBackground() ) ) - { - result.setBackground( table.getBackground() ); - } - } - else - { - if ( ! result.getBackground().equals( alternateColor ) ) - { - // jdk1.3's default renderer is opaque - if ( result instanceof JComponent ) - { - ((JComponent)result).setOpaque( true ); - } - - result.setBackground( alternateColor ); - } - } - } - return result; - } -} + alternateColor = c; + wrappedRenderer = aRenderer; + } + /** + * Uses the specified color for the background of the alternating rows, and + * wraps the specified TableCellRenderer. + */ + public AlternatingRowCellRenderer(Color aColor, TableCellRenderer aRenderer) { + alternateColor = aColor; + wrappedRenderer = aRenderer; + } + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + Component result = wrappedRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, + column); + if (!isSelected) { + if (row % 2 == 0) { + if (!result.getBackground().equals(table.getBackground())) { + result.setBackground(table.getBackground()); + } + } else { + if (!result.getBackground().equals(alternateColor)) { + // jdk1.3's default renderer is opaque + if (result instanceof JComponent) { + ((JComponent) result).setOpaque(true); + } + result.setBackground(alternateColor); + } + } + } + return result; + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java index 1c438b6..632fb59 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterFlowLayout.java @@ -25,491 +25,505 @@ import java.awt.FlowLayout; import java.awt.Insets; /** - * BetterFlowLayout works just like FlowLayout, except that - * you can specify a vertical orientation in addition to the - * usual horizontal orientations. You can also specify that - * all the components be sized to the same height and/or width. - * By default, the behavior is identical to FlowLayout. + * BetterFlowLayout works just like FlowLayout, except that you can specify a + * vertical orientation in addition to the usual horizontal orientations. You + * can also specify that all the components be sized to the same height and/or + * width. By default, the behavior is identical to FlowLayout. * * @author michael@mpowers.net * @author $Author: cgruber $ - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ public class BetterFlowLayout extends FlowLayout { - /** - * This value indicates vertical orientation and - * that each column of components should be top-justified. - */ - public static final int TOP = 32; - - /** - * This value indicates vertical orientation and - * that each column of components should be centered. - */ - public static final int CENTER_VERTICAL = 16; - - /** - * This value indicates vertical orientation and - * that each column of components should be bottom-justified. - */ - public static final int BOTTOM = 8; - - /** - * Tracks orientation. - */ - protected boolean isHorizontal = true; - - /** - * Tracks component sizing of width. - */ - protected boolean isWidthUniform = false; - /** - * Tracks component sizing of height. - */ - protected boolean isHeightUniform = false; - - /** - * Constructs a new Flow Layout with a centered alignment and a - * default 5-unit horizontal and vertical gap. - */ - public BetterFlowLayout() { - this(CENTER, 5, 5); - } - - /** - * Constructs a new Flow Layout with the specified alignment and a - * default 5-unit horizontal and vertical gap. - * The value of the alignment argument must be one of - * BetterFlowLayout.LEFT, BetterFlowLayout.RIGHT, - * or BetterFlowLayout.CENTER. - * @param align the alignment value - */ - public BetterFlowLayout(int align) { - this(align, 5, 5); - } - - /** - * Creates a new flow layout manager with the indicated alignment - * and the indicated horizontal and vertical gaps. - *

- * The value of the alignment argument must be one of - * BetterFlowLayout.LEFT, BetterFlowLayout.RIGHT, - * or BetterFlowLayout.CENTER. - * @param align the alignment value. - * @param hgap the horizontal gap between components. - * @param vgap the vertical gap between components. - */ - public BetterFlowLayout(int align, int hgap, int vgap) { - setHgap(hgap); - setVgap(vgap); - setAlignment(align); - } - - /** - * Sets whether all components should have the same height. - * @param isUniform the new value. - * @see #isHeightUniform - */ - public void setHeightUniform(boolean isUniform) { - isHeightUniform = isUniform; - } - - /** - * Sets whether all components should have the same width. - * @param isUniform the new value. - * @see #isWidthUniform - */ - public void setWidthUniform(boolean isUniform) { - isWidthUniform = isUniform; - } - - /** - * Determines whether all components will have the same height. - * The uniform height will be the maximum of the preferred heights - * of all the components in the container. - * This value defaults to false. - * @return whether components will have the same height. - */ - public boolean isHeightUniform() { - return isHeightUniform; - } - - /** - * Determines whether all components will have the same width. - * The uniform height will be the maximum of the preferred widths - * of all the components in the container. - * This value defaults to false. - * @return whether components will have the same width. - */ - public boolean isWidthUniform() { - return isWidthUniform; - } - - /** - * Sets the alignment for this layout. - * Possible values for horizontal orientation are LEFT, - * RIGHT, and CENTER. - * Possible values for vertical orientation are TOP, - * BOTTOM, and CENTER_VERTICAL. - * @param align the alignment value. - * @see java.awt.FlowLayout#getAlignment - */ - public void setAlignment(int align) { - if ( ( align == TOP ) || ( align == BOTTOM ) || ( align == CENTER_VERTICAL ) ) - { - isHorizontal = false; - } - else - { - isHorizontal = true; - } - - super.setAlignment( align ); - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSize(Container target) { - if ( isHorizontal ) { - return preferredLayoutSizeHorizontal( target ); - } else { - return preferredLayoutSizeVertical( target ); - } - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSizeHorizontal(Container target) { - synchronized (target.getTreeLock()) { - Dimension dim = new Dimension(0, 0); - int nmembers = target.getComponentCount(); - int maxWidth = 0; - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - dim.height = Math.max(dim.height, d.height); - maxWidth = Math.max(maxWidth, d.width); - if (i > 0) { - dim.width += getHgap(); - } - dim.width += d.width; - } - } - if ( isWidthUniform ) - dim.width = ( maxWidth + getHgap() ) * nmembers - getHgap(); - Insets insets = target.getInsets(); - dim.width += insets.left + insets.right + getHgap()*2; - dim.height += insets.top + insets.bottom + getVgap()*2; - return dim; - } - } - - /** - * Returns the preferred dimensions for this layout given the components - * in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container. - * @see Container - * @see #minimumLayoutSize - * @see java.awt.Container#getPreferredSize - */ - public Dimension preferredLayoutSizeVertical(Container target) { - synchronized (target.getTreeLock()) { - Dimension dim = new Dimension(0, 0); - int nmembers = target.getComponentCount(); - int maxHeight = 0; - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - dim.width = Math.max(dim.width, d.width); - maxHeight = Math.max(maxHeight, d.height); - if (i > 0) { - dim.height += getVgap(); - } - dim.height += d.height; - } - } - if ( isHeightUniform ) - dim.height = ( maxHeight + getVgap() ) * nmembers - getVgap(); - Insets insets = target.getInsets(); - dim.width += insets.left + insets.right + getHgap()*2; - dim.height += insets.top + insets.bottom + getVgap()*2; - return dim; - } - } - - /** - * Returns the minimum dimensions needed to layout the components - * contained in the specified target container. - * @param target the component which needs to be laid out - * @return the minimum dimensions to lay out the - * subcomponents of the specified container. - * @see #preferredLayoutSize - * @see java.awt.Container - * @see java.awt.Container#doLayout - */ - public Dimension minimumLayoutSize(Container target) { - // preferred size is also the minimum size - if ( isHorizontal ) { - return preferredLayoutSizeHorizontal( target ); - } else { - return preferredLayoutSizeVertical( target ); - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this BetterFlowLayout object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - public void layoutContainer(Container target) { - if ( isHorizontal ) { - layoutContainerHorizontal( target ); - } else { - layoutContainerVertical( target ); - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this BetterFlowLayout object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - protected void layoutContainerHorizontal(Container target) { - synchronized (target.getTreeLock()) { - Insets insets = target.getInsets(); - int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap()*2); - int nmembers = target.getComponentCount(); - int x = 0, y = insets.top + getVgap(); - int rowh = 0, start = 0; - - boolean ltr = true; // target.getComponentOrientation().isLeftToRight(); - Dimension uniform = getUniformDimension( target ); - - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - if ( isWidthUniform ) - d.width = uniform.width; - if ( isHeightUniform ) - d.height = uniform.height; - m.setSize(d.width, d.height); - - if ((x == 0) || ((x + d.width) <= maxwidth)) { - if (x > 0) { - x += getHgap(); - } - x += d.width; - rowh = Math.max(rowh, d.height); - } else { - moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr); - x = d.width; - y += getVgap() + rowh; - rowh = d.height; - start = i; - } - } - } - moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr); - } - } - - /** - * Centers the elements in the specified row, if there is any slack. - * @param target the component which needs to be moved - * @param x the x coordinate - * @param y the y coordinate - * @param width the width dimensions - * @param height the height dimensions - * @param rowStart the beginning of the row - * @param rowEnd the the ending of the row - */ - private void moveComponentsHorizontal(Container target, int x, int y, int width, int height, - int rowStart, int rowEnd, boolean ltr) { - synchronized (target.getTreeLock()) { - switch (getAlignment()) { - case LEFT: - x += ltr ? 0 : width; - break; - case CENTER: - x += width / 2; - break; - case RIGHT: - x += ltr ? width : 0; - break; + /** + * This value indicates vertical orientation and that each column of components + * should be top-justified. + */ + public static final int TOP = 32; + + /** + * This value indicates vertical orientation and that each column of components + * should be centered. + */ + public static final int CENTER_VERTICAL = 16; + + /** + * This value indicates vertical orientation and that each column of components + * should be bottom-justified. + */ + public static final int BOTTOM = 8; + + /** + * Tracks orientation. + */ + protected boolean isHorizontal = true; + + /** + * Tracks component sizing of width. + */ + protected boolean isWidthUniform = false; + /** + * Tracks component sizing of height. + */ + protected boolean isHeightUniform = false; + + /** + * Constructs a new Flow Layout with a centered alignment and a default 5-unit + * horizontal and vertical gap. + */ + public BetterFlowLayout() { + this(CENTER, 5, 5); + } + + /** + * Constructs a new Flow Layout with the specified alignment and a default + * 5-unit horizontal and vertical gap. The value of the alignment argument must + * be one of BetterFlowLayout.LEFT, + * BetterFlowLayout.RIGHT, or BetterFlowLayout.CENTER. + * + * @param align the alignment value + */ + public BetterFlowLayout(int align) { + this(align, 5, 5); + } + + /** + * Creates a new flow layout manager with the indicated alignment and the + * indicated horizontal and vertical gaps. + *

+ * The value of the alignment argument must be one of + * BetterFlowLayout.LEFT, BetterFlowLayout.RIGHT, or + * BetterFlowLayout.CENTER. + * + * @param align the alignment value. + * @param hgap the horizontal gap between components. + * @param vgap the vertical gap between components. + */ + public BetterFlowLayout(int align, int hgap, int vgap) { + setHgap(hgap); + setVgap(vgap); + setAlignment(align); + } + + /** + * Sets whether all components should have the same height. + * + * @param isUniform the new value. + * @see #isHeightUniform + */ + public void setHeightUniform(boolean isUniform) { + isHeightUniform = isUniform; + } + + /** + * Sets whether all components should have the same width. + * + * @param isUniform the new value. + * @see #isWidthUniform + */ + public void setWidthUniform(boolean isUniform) { + isWidthUniform = isUniform; + } + + /** + * Determines whether all components will have the same height. The uniform + * height will be the maximum of the preferred heights of all the components in + * the container. This value defaults to false. + * + * @return whether components will have the same height. + */ + public boolean isHeightUniform() { + return isHeightUniform; + } + + /** + * Determines whether all components will have the same width. The uniform + * height will be the maximum of the preferred widths of all the components in + * the container. This value defaults to false. + * + * @return whether components will have the same width. + */ + public boolean isWidthUniform() { + return isWidthUniform; + } + + /** + * Sets the alignment for this layout. Possible values for horizontal + * orientation are LEFT, RIGHT, and + * CENTER. Possible values for vertical orientation are + * TOP, BOTTOM, and CENTER_VERTICAL. + * + * @param align the alignment value. + * @see java.awt.FlowLayout#getAlignment + */ + public void setAlignment(int align) { + if ((align == TOP) || (align == BOTTOM) || (align == CENTER_VERTICAL)) { + isHorizontal = false; + } else { + isHorizontal = true; + } + + super.setAlignment(align); + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSize(Container target) { + if (isHorizontal) { + return preferredLayoutSizeHorizontal(target); + } else { + return preferredLayoutSizeVertical(target); + } + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSizeHorizontal(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + int maxWidth = 0; + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + dim.height = Math.max(dim.height, d.height); + maxWidth = Math.max(maxWidth, d.width); + if (i > 0) { + dim.width += getHgap(); + } + dim.width += d.width; + } + } + if (isWidthUniform) + dim.width = (maxWidth + getHgap()) * nmembers - getHgap(); + Insets insets = target.getInsets(); + dim.width += insets.left + insets.right + getHgap() * 2; + dim.height += insets.top + insets.bottom + getVgap() * 2; + return dim; + } + } + + /** + * Returns the preferred dimensions for this layout given the components in the + * specified target container. + * + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container. + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSizeVertical(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + int maxHeight = 0; + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + dim.width = Math.max(dim.width, d.width); + maxHeight = Math.max(maxHeight, d.height); + if (i > 0) { + dim.height += getVgap(); + } + dim.height += d.height; + } + } + if (isHeightUniform) + dim.height = (maxHeight + getVgap()) * nmembers - getVgap(); + Insets insets = target.getInsets(); + dim.width += insets.left + insets.right + getHgap() * 2; + dim.height += insets.top + insets.bottom + getVgap() * 2; + return dim; + } + } + + /** + * Returns the minimum dimensions needed to layout the components contained in + * the specified target container. + * + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the subcomponents of the specified + * container. + * @see #preferredLayoutSize + * @see java.awt.Container + * @see java.awt.Container#doLayout + */ + public Dimension minimumLayoutSize(Container target) { + // preferred size is also the minimum size + if (isHorizontal) { + return preferredLayoutSizeHorizontal(target); + } else { + return preferredLayoutSizeVertical(target); + } + } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this BetterFlowLayout object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + public void layoutContainer(Container target) { + if (isHorizontal) { + layoutContainerHorizontal(target); + } else { + layoutContainerVertical(target); + } + } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this BetterFlowLayout object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + protected void layoutContainerHorizontal(Container target) { + synchronized (target.getTreeLock()) { + Insets insets = target.getInsets(); + int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap() * 2); + int nmembers = target.getComponentCount(); + int x = 0, y = insets.top + getVgap(); + int rowh = 0, start = 0; + + boolean ltr = true; // target.getComponentOrientation().isLeftToRight(); + Dimension uniform = getUniformDimension(target); + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + if (isWidthUniform) + d.width = uniform.width; + if (isHeightUniform) + d.height = uniform.height; + m.setSize(d.width, d.height); + + if ((x == 0) || ((x + d.width) <= maxwidth)) { + if (x > 0) { + x += getHgap(); + } + x += d.width; + rowh = Math.max(rowh, d.height); + } else { + moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr); + x = d.width; + y += getVgap() + rowh; + rowh = d.height; + start = i; + } + } + } + moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr); + } + } + + /** + * Centers the elements in the specified row, if there is any slack. + * + * @param target the component which needs to be moved + * @param x the x coordinate + * @param y the y coordinate + * @param width the width dimensions + * @param height the height dimensions + * @param rowStart the beginning of the row + * @param rowEnd the the ending of the row + */ + private void moveComponentsHorizontal(Container target, int x, int y, int width, int height, int rowStart, + int rowEnd, boolean ltr) { + synchronized (target.getTreeLock()) { + switch (getAlignment()) { + case LEFT: + x += ltr ? 0 : width; + break; + case CENTER: + x += width / 2; + break; + case RIGHT: + x += ltr ? width : 0; + break; //1.2 case LEADING: //1.2 break; //1.2 case TRAILING: //1.2 x += width; //1.2 break; + } + for (int i = rowStart; i < rowEnd; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + if (ltr) { + m.setLocation(x, y + (height - m.getBounds().height) / 2); + } else { + m.setLocation(target.getBounds().width - x - m.getBounds().width, + y + (height - m.getBounds().height) / 2); + } + x += m.getBounds().width + getHgap(); + } + } + } } - for (int i = rowStart ; i < rowEnd ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - if (ltr) { - m.setLocation(x, y + (height - m.getBounds().height) / 2); - } else { - m.setLocation(target.getBounds().width - x - m.getBounds().width, y + (height - m.getBounds().height) / 2); - } - x += m.getBounds().width + getHgap(); - } + + /** + * Lays out the container. This method lets each component take its preferred + * size by reshaping the components in the target container in order to satisfy + * the constraints of this BetterFlowLayout object. + * + * @param target the specified component being laid out. + * @see Container + * @see java.awt.Container#doLayout + */ + protected void layoutContainerVertical(Container target) { + synchronized (target.getTreeLock()) { + + Insets insets = target.getInsets(); + int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap() * 2); + int nmembers = target.getComponentCount(); + int y = 0, x = insets.left + getHgap(); + int colw = 0, start = 0; + + Dimension uniform = getUniformDimension(target); + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + if (isWidthUniform) + d.width = uniform.width; + if (isHeightUniform) + d.height = uniform.height; + m.setSize(d.width, d.height); + + if ((y == 0) || ((y + d.height) <= maxheight)) { + if (y > 0) { + y += getVgap(); + } + y += d.height; + colw = Math.max(colw, d.width); + } else { + moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i); + y = d.height; + x += getHgap() + colw; + colw = d.width; + start = i; + } + } + } + moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers); + } } - } - } - - /** - * Lays out the container. This method lets each component take - * its preferred size by reshaping the components in the - * target container in order to satisfy the constraints of - * this BetterFlowLayout object. - * @param target the specified component being laid out. - * @see Container - * @see java.awt.Container#doLayout - */ - protected void layoutContainerVertical(Container target) { - synchronized (target.getTreeLock()) { - - Insets insets = target.getInsets(); - int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap()*2); - int nmembers = target.getComponentCount(); - int y = 0, x = insets.left + getHgap(); - int colw = 0, start = 0; - - Dimension uniform = getUniformDimension( target ); - for (int i = 0 ; i < nmembers ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - Dimension d = m.getPreferredSize(); - if ( isWidthUniform ) - d.width = uniform.width; - if ( isHeightUniform ) - d.height = uniform.height; - m.setSize(d.width, d.height); - - if ((y == 0) || ((y + d.height) <= maxheight)) { - if (y > 0) { - y += getVgap(); - } - y += d.height; - colw = Math.max(colw, d.width); - } else { - moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i ); - y = d.height; - x += getHgap() + colw; - colw = d.width; - start = i; - } - } - } - moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers ); - } - } - - /** - * Centers the elements in the specified row, if there is any slack. - * @param target the component which needs to be moved - * @param x the x coordinate - * @param y the y coordinate - * @param width the width dimensions - * @param height the height dimensions - * @param colStart the beginning of the column - * @param colEnd the the ending of the column - */ - private void moveComponentsVertical(Container target, int x, int y, int width, int height, - int colStart, int colEnd) { - synchronized (target.getTreeLock()) { - switch (getAlignment()) { - case TOP: - y += 0; - break; - case CENTER_VERTICAL: - y += ( height / 2 ); // - preferredLayoutSize( target ).height ) / 2 ); - break; - case BOTTOM: - y += height; - break; - } - for (int i = colStart ; i < colEnd ; i++) { - Component m = target.getComponent(i); - if (m.isVisible()) { - m.setLocation(x + (width - m.getBounds().width) / 2, y ); + + /** + * Centers the elements in the specified row, if there is any slack. + * + * @param target the component which needs to be moved + * @param x the x coordinate + * @param y the y coordinate + * @param width the width dimensions + * @param height the height dimensions + * @param colStart the beginning of the column + * @param colEnd the the ending of the column + */ + private void moveComponentsVertical(Container target, int x, int y, int width, int height, int colStart, + int colEnd) { + synchronized (target.getTreeLock()) { + switch (getAlignment()) { + case TOP: + y += 0; + break; + case CENTER_VERTICAL: + y += (height / 2); // - preferredLayoutSize( target ).height ) / 2 ); + break; + case BOTTOM: + y += height; + break; + } + for (int i = colStart; i < colEnd; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + m.setLocation(x + (width - m.getBounds().width) / 2, y); // m.setLocation(x, y ); // m.setSize( width, m.getBounds().height ); //! - y += m.getBounds().height + getVgap(); - } - } - } - } - - /** - * Returns a dimension representing the maximum preferred - * height and width of all the components in the container. - * @param target the container to scan. - * @return a dimension containing the maximum values. - */ - protected Dimension getUniformDimension(Container target) { - Component m = null; - Dimension preferred = null; - int maxWidth = 0, maxHeight = 0; - int nmembers = target.getComponentCount(); - for ( int i = 0; i < nmembers; i++ ) { - m = target.getComponent( i ); - if ( m.isVisible() ) { - preferred = m.getPreferredSize(); - maxWidth = Math.max( maxWidth, preferred.width ); - maxHeight = Math.max( maxHeight, preferred.height ); - } - } - return new Dimension( maxWidth, maxHeight ); - } - - /** - * Returns a string representation of this BetterFlowLayout - * object and its values. - * @return a string representation of this layout. - */ - public String toString() { - String str = ""; - switch (getAlignment()) { - case TOP: str = ",align=top"; break; - case CENTER_VERTICAL: str = ",align=vertical"; break; - case BOTTOM: str = ",align=bottom"; break; - default: return super.toString(); + y += m.getBounds().height + getVgap(); + } + } + } + } + + /** + * Returns a dimension representing the maximum preferred height and width of + * all the components in the container. + * + * @param target the container to scan. + * @return a dimension containing the maximum values. + */ + protected Dimension getUniformDimension(Container target) { + Component m = null; + Dimension preferred = null; + int maxWidth = 0, maxHeight = 0; + int nmembers = target.getComponentCount(); + for (int i = 0; i < nmembers; i++) { + m = target.getComponent(i); + if (m.isVisible()) { + preferred = m.getPreferredSize(); + maxWidth = Math.max(maxWidth, preferred.width); + maxHeight = Math.max(maxHeight, preferred.height); + } + } + return new Dimension(maxWidth, maxHeight); } - return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]"; - } + /** + * Returns a string representation of this BetterFlowLayout object + * and its values. + * + * @return a string representation of this layout. + */ + public String toString() { + String str = ""; + switch (getAlignment()) { + case TOP: + str = ",align=top"; + break; + case CENTER_VERTICAL: + str = ",align=vertical"; + break; + case BOTTOM: + str = ",align=bottom"; + break; + default: + return super.toString(); + } + return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]"; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java index 6e23ca1..238dd14 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterRootLayout.java @@ -28,247 +28,209 @@ import java.awt.Rectangle; import javax.swing.JPanel; import javax.swing.JRootPane; -/** -* A custom layout for a JRootPane that handles the the layout of a -* JRootPane's layeredPane, glassPane, and menuBar, and in addition -* handles four decorative components arranged in a border layout. -* Add the decorative components to the JRootPane using the directional -* constants; CENTER is reserved for the content pane and menu bar. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class BetterRootLayout extends BorderLayout -{ +/** + * A custom layout for a JRootPane that handles the the layout of a JRootPane's + * layeredPane, glassPane, and menuBar, and in addition handles four decorative + * components arranged in a border layout. Add the decorative components to the + * JRootPane using the directional constants; CENTER is reserved for the content + * pane and menu bar. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class BetterRootLayout extends BorderLayout { /** * Returns the amount of space the layout would like to have. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's preferred size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension preferredLayoutSize(Container parent) - { + */ + public Dimension preferredLayoutSize(Container parent) { JRootPane rootPane = (JRootPane) parent; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.preferredLayoutSize( parent ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.preferredLayoutSize(parent); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Returns the minimum amount of space the layout needs. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's minimum size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension minimumLayoutSize(Container parent) - { + */ + public Dimension minimumLayoutSize(Container parent) { JRootPane rootPane = (JRootPane) parent; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.minimumLayoutSize( parent ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.minimumLayoutSize(parent); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Returns the maximum amount of space the layout can use. * * @param the Container for which this layout manager is being used * @return a Dimension object containing the layout's maximum size * @throws ClassCastException if parent is not a JRootPane - */ - public Dimension maximumLayoutSize(Container target) - { + */ + public Dimension maximumLayoutSize(Container target) { JRootPane rootPane = (JRootPane) target; - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - + proxyPanel.setLayout(new BorderLayout()); + JPanel contentProxy = null; - if(rootPane.getContentPane() != null) { + if (rootPane.getContentPane() != null) { contentProxy = new JPanel(); - contentProxy.setMinimumSize( - rootPane.getContentPane().getMinimumSize() ); - contentProxy.setMaximumSize( - rootPane.getContentPane().getMaximumSize() ); - contentProxy.setPreferredSize( - rootPane.getContentPane().getPreferredSize() ); - proxyPanel.add( contentProxy, CENTER ); + contentProxy.setMinimumSize(rootPane.getContentPane().getMinimumSize()); + contentProxy.setMaximumSize(rootPane.getContentPane().getMaximumSize()); + contentProxy.setPreferredSize(rootPane.getContentPane().getPreferredSize()); + proxyPanel.add(contentProxy, CENTER); } JPanel menuProxy = null; - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { menuProxy = new JPanel(); - menuProxy.setMinimumSize( - rootPane.getJMenuBar().getMinimumSize() ); - menuProxy.setMaximumSize( - rootPane.getJMenuBar().getMaximumSize() ); - menuProxy.setPreferredSize( - rootPane.getJMenuBar().getPreferredSize() ); - proxyPanel.add( menuProxy, NORTH ); + menuProxy.setMinimumSize(rootPane.getJMenuBar().getMinimumSize()); + menuProxy.setMaximumSize(rootPane.getJMenuBar().getMaximumSize()); + menuProxy.setPreferredSize(rootPane.getJMenuBar().getPreferredSize()); + proxyPanel.add(menuProxy, NORTH); } - - this.addLayoutComponent( proxyPanel, CENTER ); - - Dimension result = super.maximumLayoutSize( target ); - this.removeLayoutComponent( proxyPanel ); - + this.addLayoutComponent(proxyPanel, CENTER); + + Dimension result = super.maximumLayoutSize(target); + + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); - + return result; } - + /** * Instructs the layout manager to perform the layout for the specified * container. * * @param the Container for which this layout manager is being used * @throws ClassCastException if parent is not a JRootPane - */ - public void layoutContainer(Container parent) - { + */ + public void layoutContainer(Container parent) { JRootPane rootPane = (JRootPane) parent; - + Rectangle b = parent.getBounds(); Insets i = rootPane.getInsets(); int w = b.width - i.right - i.left; int h = b.height - i.top - i.bottom; - + // layout panes - if(rootPane.getLayeredPane() != null) { + if (rootPane.getLayeredPane() != null) { rootPane.getLayeredPane().setBounds(i.left, i.top, w, h); } - if(rootPane.getGlassPane() != null) { + if (rootPane.getGlassPane() != null) { rootPane.getGlassPane().setBounds(i.left, i.top, w, h); } - + // handle proxy panel - + JPanel proxyPanel = new JPanel(); - proxyPanel.setLayout( new BorderLayout() ); - - this.addLayoutComponent( proxyPanel, CENTER ); - - super.layoutContainer( parent ); + proxyPanel.setLayout(new BorderLayout()); + + this.addLayoutComponent(proxyPanel, CENTER); + + super.layoutContainer(parent); // use proxy sizes to set sizes of layeredPane's children Rectangle proxyRect = proxyPanel.getBounds(); - if(rootPane.getJMenuBar() != null) { + if (rootPane.getJMenuBar() != null) { Rectangle menuRect = proxyPanel.getBounds(); menuRect.height = rootPane.getJMenuBar().getPreferredSize().height; - rootPane.getJMenuBar().setBounds( menuRect ); + rootPane.getJMenuBar().setBounds(menuRect); proxyRect.y += menuRect.height; proxyRect.height -= menuRect.height; } - if(rootPane.getContentPane() != null) { - rootPane.getContentPane().setBounds( proxyRect ); + if (rootPane.getContentPane() != null) { + rootPane.getContentPane().setBounds(proxyRect); } - this.removeLayoutComponent( proxyPanel ); - + this.removeLayoutComponent(proxyPanel); + proxyPanel.removeAll(); } - + /** - * Passes NORTH, SOUTH, EAST, WEST and CENTER to super implementation, - * and ignores all others. - */ - public void addLayoutComponent(Component comp, Object constraints) - { - if ( NORTH.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( SOUTH.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( EAST.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); + * Passes NORTH, SOUTH, EAST, WEST and CENTER to super implementation, and + * ignores all others. + */ + public void addLayoutComponent(Component comp, Object constraints) { + if (NORTH.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (SOUTH.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (EAST.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (WEST.equals(constraints)) { + super.addLayoutComponent(comp, constraints); + } else if (CENTER.equals(constraints)) { + super.addLayoutComponent(comp, constraints); } - else - if ( WEST.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - else - if ( CENTER.equals( constraints ) ) - { - super.addLayoutComponent( comp, constraints ); - } - + // otherwise, ignore } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java index deb0eb6..cb1d48a 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/BetterTableUI.java @@ -24,100 +24,84 @@ import javax.swing.event.MouseInputListener; import javax.swing.plaf.basic.BasicTableUI; /** -* BetterTableUI allows a JTable to be disabled by -* listening for MouseEvents and then forwarding them -* to the usual MouseInputHandler only if the table -* is enabled.

-* -* This class also works around a bug where an editable -* table's selection is changed when clicking in an edit -* cell while the control key is down. This typically -* happened while users were copying/pasting data from -* cell to cell.

-* -* To use, call JTable.setUI() on any -* JTable with a BetterTableUI as the parameter. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class BetterTableUI extends BasicTableUI implements MouseInputListener -{ -/** -* The listener to get all mouse events when the table is enabled. -*/ - protected MouseInputListener delegateHandler; - -/** -* Overridden to set self as mouse listener and create delegate. -*/ - protected MouseInputListener createMouseInputListener() - { - // normal handler is a protected inner class of parent - delegateHandler = new MouseInputHandler(); - - return this; - } - - // interface MouseInputListener - - public void mouseClicked(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseClicked(event); - } - } - - public void mouseDragged(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseDragged(event); - } - } - - public void mouseEntered(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseEntered(event); - } - } - - public void mouseExited(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseExited(event); - } - } - - public void mouseMoved(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseMoved(event); - } - } - - public void mousePressed(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - // workaround bug - control key removes an existing selection - if ( table.isEditing() && event.isControlDown() ) return; - - delegateHandler.mousePressed(event); - } - } - - public void mouseReleased(MouseEvent event) - { - if ( (table!=null) && (table.isEnabled()) ) - { - delegateHandler.mouseReleased(event); - } - } + * BetterTableUI allows a JTable to be disabled by listening for MouseEvents and + * then forwarding them to the usual MouseInputHandler only if the table is + * enabled.
+ *
+ * + * This class also works around a bug where an editable table's selection is + * changed when clicking in an edit cell while the control key is down. This + * typically happened while users were copying/pasting data from cell to cell. + *
+ *
+ * + * To use, call JTable.setUI() on any JTable with a BetterTableUI + * as the parameter. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class BetterTableUI extends BasicTableUI implements MouseInputListener { + /** + * The listener to get all mouse events when the table is enabled. + */ + protected MouseInputListener delegateHandler; + + /** + * Overridden to set self as mouse listener and create delegate. + */ + protected MouseInputListener createMouseInputListener() { + // normal handler is a protected inner class of parent + delegateHandler = new MouseInputHandler(); + + return this; + } + + // interface MouseInputListener + + public void mouseClicked(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseClicked(event); + } + } + + public void mouseDragged(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseDragged(event); + } + } + + public void mouseEntered(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseEntered(event); + } + } + + public void mouseExited(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseExited(event); + } + } + + public void mouseMoved(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseMoved(event); + } + } + + public void mousePressed(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + // workaround bug - control key removes an existing selection + if (table.isEditing() && event.isControlDown()) + return; + + delegateHandler.mousePressed(event); + } + } + + public void mouseReleased(MouseEvent event) { + if ((table != null) && (table.isEnabled())) { + delegateHandler.mouseReleased(event); + } + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java index 769e866..7d662ae 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ButtonPanel.java @@ -46,565 +46,527 @@ import javax.swing.JPanel; import javax.swing.UIManager; /** -* ButtonPanel handles display and event broadcasting of standard buttons like -* OK/Cancel/Save/etc. The constructor takes a list or array of strings, each -* representing a button to appear on the panel from left to right. -* Any button click will send an action event to all listeners with the action -* command containing the corresponding string. Note action events are simply -* forwarded from the buttons themselves, so the source of the event will be -* the button, not the button panel. The button panel is the source of the -* STATE_CHANGED events that notify about changes to the panel itself.

-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class ButtonPanel extends JPanel implements ActionListener, MouseMotionListener -{ - // TODO: Button text should be read from resources. -/** -* Specifies a "OK" button. -* This is also the action command sent by the OK button. -*/ - public static final String OK = "OK"; -/** -* Specifies a "Save" button. -* This is also the action command sent by the Save button. -*/ - public static final String SAVE = "Save"; -/** -* Specifies a "Refresh" button. -* This is also the action command sent by the Refresh button. -*/ - public static final String REFRESH = "Refresh"; -/** -* Specifies a "Clear All" button. -* This is also the action command sent by the Clear All button. -*/ - public static final String CLEAR_ALL = "Clear All"; -/** -* Specifies a "Refresh" button. -* This is also the action command sent by the Cancel button. -*/ - public static final String CANCEL = "Cancel"; -/** -* Specifies a "Yes" button. -* This is also the action command sent by the Yes button. -*/ - public static final String YES = "Yes"; -/** -* Specifies a "No" button. -* This is also the action command sent by the No button. -*/ - public static final String NO = "No"; -/** -* Specifies an "Add" button. -* This is also the action command sent by the Add button. -*/ - public static final String ADD = "Add"; -/** -* Specifies a "Remove" button. -* This is also the action command sent by the Remove button. -*/ - public static final String REMOVE = "Remove"; -/** -* This is the action command to all listeners when the button state is changed. -*/ - public static final String STATE_CHANGED = "STATE_CHANGED"; - -/** -* This is the container to which buttons are added. -*/ - protected Container buttonContainer = null; // useful for subclasses -/** -* This is the list of all buttons on the panel. -*/ - protected Vector buttonList = null; -/** -* The insets for this panel, so they can be modified. -*/ - protected Insets insets = new Insets( 5, 5, 5, 5 ); - -/** -* This is the layout manager - which must be a FlowLayout or subclass. -*/ - protected FlowLayout buttonPanelLayout = null; - - // for action multicasting - protected ActionListener actionListener = null; - - -/** -* Constructs a ButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public ButtonPanel() - { - buttonList = new Vector(); - initLayout(); - - // default labels for bean layout - setLabels( new String[] { "One", "Two", "Three" } ); - } - -/** -* This method is responsible for the initial layout of the panel. -* Subclasses can implement different layouts, but this method -* is responsible for initializing buttonContainer and buttonPanelLayout -* and setting the container to use the layout. -*/ - protected void initLayout() - { - this.setInsets( super.getInsets() ); - buttonContainer = this; - buttonPanelLayout = new BetterFlowLayout( BetterFlowLayout.RIGHT ); - buttonContainer.setLayout(buttonPanelLayout); - ((BetterFlowLayout)buttonPanelLayout).setWidthUniform( true ); - - // setBackground( Color.blue ); // useful for debugging - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public ButtonPanel( String[] buttonList ) - { - this(); - setLabels( buttonList ); - } - -/** -* Constructs a ButtonPane using specified actions. For each action, a button -* is created, that when pressed the corresponding action is activated. The -* "name" of the action is used as the title of the button. -* @param actionList An array of actions to be used to create buttons with. -*/ - public ButtonPanel( Action[] actionList ) - { - this(); - setLabels( actionList ); - } - -/** -* Creates the buttons to appear on the panel. Any existing buttons -* are replaced. The labels are used as names and action commands -* in addition to labels. -* @param labels An array of strings to be used in labeling the buttons. -* If null, all buttons will be removed. -*/ - public void setLabels( String[] labels ) - { - if ( labels == null ) - { - labels = new String[] {}; - } - - buttonContainer.removeAll(); - this.buttonList = new Vector( labels.length ); - - String item = null; - Component button; - for ( int i = 0; i < labels.length; i++ ) - { - item = labels[i]; - if ( item != null ) - { - button = createComponentWithLabel( item.toString() ); - this.buttonList.addElement( item ); - addComponentToPanel( button ); - button.setEnabled( this.isEnabled() ); -/* - if ( i == 0 ) - { - JRootPane root = SwingUtilities.getRootPane( button ); - if ( root != null ) - root.setDefaultButton( button ); - } -*/ - } - else - { - throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." ); - } - } - - this.revalidate(); - this.repaint(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) ); - } - -/** -* -*/ - public void setLabels( Action[] actions ) - { - if ( actions == null ) - { - actions = new Action[] {}; - } - - buttonContainer.removeAll(); - this.buttonList = new Vector( actions.length ); - - Action action = null; - Component button; - for ( int i = 0; i < actions.length; i++ ) - { - action = actions[i]; - if ( action != null ) - { - String name = ( String )action.getValue( Action.NAME ); - button = createComponentWithLabel( name ); - this.buttonList.addElement( name ); - addComponentToPanel( button ); - button.setEnabled( this.isEnabled() ? action.isEnabled() : false ); - - // Add the action to the "button" if it knows about action listeners. - try - { - Method addActionListenerMethod = - button.getClass().getMethod( "addActionListener", new Class[] { ActionListener.class } ); - addActionListenerMethod.invoke( button, new Object[] { action } ); - } - catch ( NoSuchMethodException e ) { /* Do Nothing */ } - catch ( IllegalAccessException e ) { e.printStackTrace(); /* TODO: Do Something? */ } - catch ( InvocationTargetException e ) { e.printStackTrace(); /* TODO: Do Something? */ } - - // Create a new listener for property change events and have - // the action broadcast to that listener. - PropertyChangeListener pcListener = new ActionChangeListener( button ); - action.addPropertyChangeListener( pcListener ); - } - else - { - throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." ); - } - } - - this.revalidate(); - this.repaint(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) ); - } - - -/** -* Gets the labels of the buttons that appear on the panel, ordered from left to right. -* @return A new list containing strings used in labeling the buttons. -*/ - public String[] getLabels() - { - String[] labels = new String[ buttonList.size() ]; - int i = 0; - for ( Enumeration it = buttonList.elements(); it.hasMoreElements(); ) - { - labels[i++] = it.nextElement().toString(); - } - return labels; - } - -/** -* Gets the first component having the specified name. -* @return A component with the specified name, or null if none match. -*/ - public Component getButton( String aLabel ) - { - if ( aLabel == null ) return null; - - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( aLabel.equals( c.getName() ) ) - { - return c; - } - } - return null; - } - -/** -* Creates a new component with the specified label. -* The label is also used for the component's name -* and action command, if any. -* (This implementation returns a JButton.) -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; // TODO: get string from resource - JButton newButton = new JButton(); // might allow other types in future - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - return newButton; - } - -/** -* Adds a component to the right-most side of the layout. -* @param aComponent The component to be added to the layout. -*/ - protected void addComponentToPanel( Component aComponent ) - { - buttonContainer.add( aComponent ); - } - - -/** -* Changes the alignment of the buttons in the panel. Defaults to right-justified. -* @param alignment A valid alignment code, per BetterFlowLayout implementation. -* @see BetterFlowLayout -*/ - public void setAlignment( int alignment ) - { - buttonPanelLayout.setAlignment(alignment); - buttonContainer.doLayout(); - } -/** -* Gets the alignment of the buttons in the panel. -* @return An alignment code, per FlowLayout implementation. -* @see FlowLayout -*/ - public int getAlignment() - { - return buttonPanelLayout.getAlignment(); - } - -/** -* Changes the horizontal spacing between components in the panel. -* @param newHgap the new spacing, in pixels. May not be negative. -*/ - public void setHgap( int newHgap ) - { - if ( newHgap < 0 ) return; // may not be negative - buttonPanelLayout.setHgap( newHgap ); - } - -/** -* Gets the current horizontal spacing between components. -* @return the current horizontal spacing, in pixels. -*/ - public int getHgap() - { - return buttonPanelLayout.getHgap(); - } - -/** -* Changes the vertical spacing between components in the panel. -* @param newVgap the new spacing, in pixels. May not be negative. -*/ - public void setVgap( int newVgap ) - { - if ( newVgap < 0 ) return; // may not be negative - buttonPanelLayout.setVgap( newVgap ); - } - -/** -* Gets the current vertical spacing between components. -* @return the current vertical spacing, in pixels. -*/ - public int getVgap() - { - return buttonPanelLayout.getVgap(); - } - -/** -* Changes the insets for this panel. -* @param newInsets the new insets. -*/ - public void setInsets( Insets newInsets ) - { - insets = newInsets; - } - -/** -* Overridden to return the user-specified insets for this panel. -* @return the current insets for this panel. -*/ - public Insets getInsets() - { - return insets; - } - -/** -* Overridden to call setEnabled on all components on panel. -* @param isEnabled whether to enable the panel and all components on it. -*/ - public void setEnabled( boolean isEnabled ) - { - super.setEnabled( isEnabled ); - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - buttonContainer.getComponent( i ).setEnabled( isEnabled ); - } - } - - // Action Multicast methods - -/** -* Adds an action listener to the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } - - // interface ActionListener - -/** -* Called by buttons on panel and by other components that -* might be set to broadcast events to this listener. -* @param e An action event to be received. -*/ - public void actionPerformed(ActionEvent e) - { - broadcastEvent(e); - } - -/** -* A property change listener that listens specifically for property changes -* from action objects. This is the class that ties in the action to the -* button. This class is added to an action as a property change listener. -* The corresponding component is referenced by this class toe easily handle -* updates to the component caused by changes to the action. -*/ - public class ActionChangeListener implements PropertyChangeListener - { - /** The UI component that is affected by the action's changes. */ - Component theComponent; - - /** - * Constructs an ActionChangeListener with the given component being - * the recipient of the action's changes. - * @param The component to bind with the action. - */ - public ActionChangeListener( Component aComponent ) - { - super(); - theComponent = aComponent; - } - - /** - * Called whenever a property changes on the action object. - * @pram e The property change event generated by the action. - */ - public void propertyChange( PropertyChangeEvent e ) - { - String propertyName = e.getPropertyName(); - if ( propertyName.equals( Action.NAME ) ) - { - String name = ( String )e.getNewValue(); - if ( theComponent instanceof AbstractButton ) - { - AbstractButton button = ( AbstractButton )theComponent; - String oldName = button.getName(); - button.setText( name ); - button.setName( name ); - button.setActionCommand( name ); - - // Replace the old name of the component with the new name - // in the ButtonPanel's list of components. - buttonList.setElementAt( name, buttonList.indexOf( oldName ) ); - } - - // TODO: If component is not a button (or doesn't define the getText() - // then what should be done. - } - else if ( propertyName.equals( "enabled" ) ) - { - Boolean enabled = ( Boolean )e.getNewValue(); - theComponent.setEnabled( ButtonPanel.this.isEnabled() ? enabled.booleanValue() : false ); - } - - // TODO: Icon? - } - } - - - // for testing - - public static void main( String[] argv ) - { - try - { - UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - JFrame dialog = new JFrame(); - BorderLayout bl = new BorderLayout( 20, 20 ); - - ButtonPanel panel = new ButtonPanel(); + * ButtonPanel handles display and event broadcasting of standard buttons like + * OK/Cancel/Save/etc. The constructor takes a list or array of strings, each + * representing a button to appear on the panel from left to right. Any button + * click will send an action event to all listeners with the action command + * containing the corresponding string. Note action events are simply forwarded + * from the buttons themselves, so the source of the event will be the button, + * not the button panel. The button panel is the source of the STATE_CHANGED + * events that notify about changes to the panel itself.
+ *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class ButtonPanel extends JPanel implements ActionListener, MouseMotionListener { + // TODO: Button text should be read from resources. + /** + * Specifies a "OK" button. This is also the action command sent by the OK + * button. + */ + public static final String OK = "OK"; + /** + * Specifies a "Save" button. This is also the action command sent by the Save + * button. + */ + public static final String SAVE = "Save"; + /** + * Specifies a "Refresh" button. This is also the action command sent by the + * Refresh button. + */ + public static final String REFRESH = "Refresh"; + /** + * Specifies a "Clear All" button. This is also the action command sent by the + * Clear All button. + */ + public static final String CLEAR_ALL = "Clear All"; + /** + * Specifies a "Refresh" button. This is also the action command sent by the + * Cancel button. + */ + public static final String CANCEL = "Cancel"; + /** + * Specifies a "Yes" button. This is also the action command sent by the Yes + * button. + */ + public static final String YES = "Yes"; + /** + * Specifies a "No" button. This is also the action command sent by the No + * button. + */ + public static final String NO = "No"; + /** + * Specifies an "Add" button. This is also the action command sent by the Add + * button. + */ + public static final String ADD = "Add"; + /** + * Specifies a "Remove" button. This is also the action command sent by the + * Remove button. + */ + public static final String REMOVE = "Remove"; + /** + * This is the action command to all listeners when the button state is changed. + */ + public static final String STATE_CHANGED = "STATE_CHANGED"; + + /** + * This is the container to which buttons are added. + */ + protected Container buttonContainer = null; // useful for subclasses + /** + * This is the list of all buttons on the panel. + */ + protected Vector buttonList = null; + /** + * The insets for this panel, so they can be modified. + */ + protected Insets insets = new Insets(5, 5, 5, 5); + + /** + * This is the layout manager - which must be a FlowLayout or subclass. + */ + protected FlowLayout buttonPanelLayout = null; + + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Constructs a ButtonPanel. Three buttons are created so the panel is filled + * when used in a GUI-builder environment. + */ + public ButtonPanel() { + buttonList = new Vector(); + initLayout(); + + // default labels for bean layout + setLabels(new String[] { "One", "Two", "Three" }); + } + + /** + * This method is responsible for the initial layout of the panel. Subclasses + * can implement different layouts, but this method is responsible for + * initializing buttonContainer and buttonPanelLayout and setting the container + * to use the layout. + */ + protected void initLayout() { + this.setInsets(super.getInsets()); + buttonContainer = this; + buttonPanelLayout = new BetterFlowLayout(BetterFlowLayout.RIGHT); + buttonContainer.setLayout(buttonPanelLayout); + ((BetterFlowLayout) buttonPanelLayout).setWidthUniform(true); + + // setBackground( Color.blue ); // useful for debugging + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public ButtonPanel(String[] buttonList) { + this(); + setLabels(buttonList); + } + + /** + * Constructs a ButtonPane using specified actions. For each action, a button is + * created, that when pressed the corresponding action is activated. The "name" + * of the action is used as the title of the button. + * + * @param actionList An array of actions to be used to create buttons with. + */ + public ButtonPanel(Action[] actionList) { + this(); + setLabels(actionList); + } + + /** + * Creates the buttons to appear on the panel. Any existing buttons are + * replaced. The labels are used as names and action commands in addition to + * labels. + * + * @param labels An array of strings to be used in labeling the buttons. If + * null, all buttons will be removed. + */ + public void setLabels(String[] labels) { + if (labels == null) { + labels = new String[] {}; + } + + buttonContainer.removeAll(); + this.buttonList = new Vector(labels.length); + + String item = null; + Component button; + for (int i = 0; i < labels.length; i++) { + item = labels[i]; + if (item != null) { + button = createComponentWithLabel(item.toString()); + this.buttonList.addElement(item); + addComponentToPanel(button); + button.setEnabled(this.isEnabled()); + /* + * if ( i == 0 ) { JRootPane root = SwingUtilities.getRootPane( button ); if ( + * root != null ) root.setDefaultButton( button ); } + */ + } else { + throw new IllegalArgumentException("ButtonPanel.setButtons: nulls are not allowed."); + } + } + + this.revalidate(); + this.repaint(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED)); + } + + /** + * + */ + public void setLabels(Action[] actions) { + if (actions == null) { + actions = new Action[] {}; + } + + buttonContainer.removeAll(); + this.buttonList = new Vector(actions.length); + + Action action = null; + Component button; + for (int i = 0; i < actions.length; i++) { + action = actions[i]; + if (action != null) { + String name = (String) action.getValue(Action.NAME); + button = createComponentWithLabel(name); + this.buttonList.addElement(name); + addComponentToPanel(button); + button.setEnabled(this.isEnabled() ? action.isEnabled() : false); + + // Add the action to the "button" if it knows about action listeners. + try { + Method addActionListenerMethod = button.getClass().getMethod("addActionListener", + new Class[] { ActionListener.class }); + addActionListenerMethod.invoke(button, new Object[] { action }); + } catch (NoSuchMethodException e) { + /* Do Nothing */ } catch (IllegalAccessException e) { + e.printStackTrace(); + /* TODO: Do Something? */ } catch (InvocationTargetException e) { + e.printStackTrace(); + /* TODO: Do Something? */ } + + // Create a new listener for property change events and have + // the action broadcast to that listener. + PropertyChangeListener pcListener = new ActionChangeListener(button); + action.addPropertyChangeListener(pcListener); + } else { + throw new IllegalArgumentException("ButtonPanel.setButtons: nulls are not allowed."); + } + } + + this.revalidate(); + this.repaint(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED)); + } + + /** + * Gets the labels of the buttons that appear on the panel, ordered from left to + * right. + * + * @return A new list containing strings used in labeling the buttons. + */ + public String[] getLabels() { + String[] labels = new String[buttonList.size()]; + int i = 0; + for (Enumeration it = buttonList.elements(); it.hasMoreElements();) { + labels[i++] = it.nextElement().toString(); + } + return labels; + } + + /** + * Gets the first component having the specified name. + * + * @return A component with the specified name, or null if none match. + */ + public Component getButton(String aLabel) { + if (aLabel == null) + return null; + + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (aLabel.equals(c.getName())) { + return c; + } + } + return null; + } + + /** + * Creates a new component with the specified label. The label is also used for + * the component's name and action command, if any. (This implementation returns + * a JButton.) + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; // TODO: get string from resource + JButton newButton = new JButton(); // might allow other types in future + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + return newButton; + } + + /** + * Adds a component to the right-most side of the layout. + * + * @param aComponent The component to be added to the layout. + */ + protected void addComponentToPanel(Component aComponent) { + buttonContainer.add(aComponent); + } + + /** + * Changes the alignment of the buttons in the panel. Defaults to + * right-justified. + * + * @param alignment A valid alignment code, per BetterFlowLayout implementation. + * @see BetterFlowLayout + */ + public void setAlignment(int alignment) { + buttonPanelLayout.setAlignment(alignment); + buttonContainer.doLayout(); + } + + /** + * Gets the alignment of the buttons in the panel. + * + * @return An alignment code, per FlowLayout implementation. + * @see FlowLayout + */ + public int getAlignment() { + return buttonPanelLayout.getAlignment(); + } + + /** + * Changes the horizontal spacing between components in the panel. + * + * @param newHgap the new spacing, in pixels. May not be negative. + */ + public void setHgap(int newHgap) { + if (newHgap < 0) + return; // may not be negative + buttonPanelLayout.setHgap(newHgap); + } + + /** + * Gets the current horizontal spacing between components. + * + * @return the current horizontal spacing, in pixels. + */ + public int getHgap() { + return buttonPanelLayout.getHgap(); + } + + /** + * Changes the vertical spacing between components in the panel. + * + * @param newVgap the new spacing, in pixels. May not be negative. + */ + public void setVgap(int newVgap) { + if (newVgap < 0) + return; // may not be negative + buttonPanelLayout.setVgap(newVgap); + } + + /** + * Gets the current vertical spacing between components. + * + * @return the current vertical spacing, in pixels. + */ + public int getVgap() { + return buttonPanelLayout.getVgap(); + } + + /** + * Changes the insets for this panel. + * + * @param newInsets the new insets. + */ + public void setInsets(Insets newInsets) { + insets = newInsets; + } + + /** + * Overridden to return the user-specified insets for this panel. + * + * @return the current insets for this panel. + */ + public Insets getInsets() { + return insets; + } + + /** + * Overridden to call setEnabled on all components on panel. + * + * @param isEnabled whether to enable the panel and all components on it. + */ + public void setEnabled(boolean isEnabled) { + super.setEnabled(isEnabled); + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + buttonContainer.getComponent(i).setEnabled(isEnabled); + } + } + + // Action Multicast methods + + /** + * Adds an action listener to the list that will be notified by button events + * and changes in button state. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by button + * events and changes in button state. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } + + // interface ActionListener + + /** + * Called by buttons on panel and by other components that might be set to + * broadcast events to this listener. + * + * @param e An action event to be received. + */ + public void actionPerformed(ActionEvent e) { + broadcastEvent(e); + } + + /** + * A property change listener that listens specifically for property changes + * from action objects. This is the class that ties in the action to the button. + * This class is added to an action as a property change listener. The + * corresponding component is referenced by this class toe easily handle updates + * to the component caused by changes to the action. + */ + public class ActionChangeListener implements PropertyChangeListener { + /** The UI component that is affected by the action's changes. */ + Component theComponent; + + /** + * Constructs an ActionChangeListener with the given component being the + * recipient of the action's changes. + * + * @param The component to bind with the action. + */ + public ActionChangeListener(Component aComponent) { + super(); + theComponent = aComponent; + } + + /** + * Called whenever a property changes on the action object. + * + * @pram e The property change event generated by the action. + */ + public void propertyChange(PropertyChangeEvent e) { + String propertyName = e.getPropertyName(); + if (propertyName.equals(Action.NAME)) { + String name = (String) e.getNewValue(); + if (theComponent instanceof AbstractButton) { + AbstractButton button = (AbstractButton) theComponent; + String oldName = button.getName(); + button.setText(name); + button.setName(name); + button.setActionCommand(name); + + // Replace the old name of the component with the new name + // in the ButtonPanel's list of components. + buttonList.setElementAt(name, buttonList.indexOf(oldName)); + } + + // TODO: If component is not a button (or doesn't define the getText() + // then what should be done. + } else if (propertyName.equals("enabled")) { + Boolean enabled = (Boolean) e.getNewValue(); + theComponent.setEnabled(ButtonPanel.this.isEnabled() ? enabled.booleanValue() : false); + } + + // TODO: Icon? + } + } + + // for testing + + public static void main(String[] argv) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + JFrame dialog = new JFrame(); + BorderLayout bl = new BorderLayout(20, 20); + + ButtonPanel panel = new ButtonPanel(); // ButtonPanel panel = new ButtonPanel( new String[] { "OkayOkay", "CancelCancel" } ); - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, BorderLayout.CENTER ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - - panel.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - panel.getButton( "One" ).setEnabled( false ); - - dialog.pack(); - dialog.setVisible( true ); - - try - { - BeanInfo info = Introspector.getBeanInfo( ButtonPanel.class ); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for ( int i = 0; i < props.length; i++ ) - { - System.out.println( props[i].getName() ); - } - } - catch (Exception exc) - { - System.out.println( exc ); - } + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, BorderLayout.CENTER); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + panel.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + panel.getButton("One").setEnabled(false); + dialog.pack(); + dialog.setVisible(true); + try { + BeanInfo info = Introspector.getBeanInfo(ButtonPanel.class); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (int i = 0; i < props.length; i++) { + System.out.println(props[i].getName()); + } + } catch (Exception exc) { + System.out.println(exc); + } - } - - public void mouseDragged(MouseEvent e) - { - } - - public void mouseMoved(MouseEvent e) - { - } + } + public void mouseDragged(MouseEvent e) { + } + public void mouseMoved(MouseEvent e) { + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java index 5e847ae..8116678 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/CheckButtonPanel.java @@ -27,246 +27,223 @@ import javax.swing.JCheckBox; import javax.swing.border.EmptyBorder; /** -* CheckButtonPanel is a simple extension of ButtonPanel. -* Differences are that it uses JCheckBoxes and the -* default alignment is vertical. The panel defaults to having -* no buttons selected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class CheckButtonPanel extends ButtonPanel -{ -/** -* Constructs a CheckButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public CheckButtonPanel() - { - super(); - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public CheckButtonPanel( String[] buttonList ) - { - super( buttonList ); - } - -/** -* Overridden to set vertical-center alignment and zero vgap. -*/ - protected void initLayout() - { - super.initLayout(); - buttonPanelLayout.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - buttonPanelLayout.setVgap( 0 ); - } - -/** -* Overridden to return a JRadioButton. -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; - JCheckBox newButton = new JCheckBox(); - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - - // reduce insets per java l&f guidelines (was 4 on each side) - newButton.setBorder( new EmptyBorder( 1, 4, 1, 4 ) ); - - return newButton; - } - -/** -* Sets the value of the button whose name matches the given text value. -* @param aName A String matching the name of one of the buttons. -* If null, empty, or not matching, nothing happens. -* @param aValue A value to set the button. -*/ - public void setValue(String aName, boolean aValue) - { - if ( aName != null ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( c.getName().equals( aName ) ) - { - ((AbstractButton)c).setSelected( aValue ); - c.repaint(); - return; - } - } - } - } - // null, empty, or not matching - exit. - System.out.println( "CheckButtonPanel.setValue: not found: " + aName ); - } - -/** -* Sets the state of the specified buttons to the specified value. -* @param aLabelArray An Array of Strings listing the buttons to be set. -* @param aValue The value to which the specified buttons will be set. -*/ - public void setValues(String[] aLabelArray, boolean aValue) - { - if ( aLabelArray != null ) - { - for ( int i = 0; i < aLabelArray.length; i++ ) - { - setValue( aLabelArray[i], aValue ); - } - } - } - -/** -* Convenience method to set all checkboxes on the panel. -* @param aValue The value to which all checkboxes on the panel will be set. -*/ - public void setAllValues(boolean aValue) - { - setValues( getLabels(), aValue ); - } - -/** -* Convenience method to check all boxes on the panel. -*/ - public void checkAll() - { - setAllValues( true ); - } - -/** -* Convenience method to clear all boxes on the panel. -*/ - public void clearAll() - { - setAllValues( false ); - } - -/** -* A convenience method to set only those buttons on the entire -* panel that should be checked. Buttons not in the list are unchecked. -* @param aLabelArray An Array of Strings listing the buttons to be set. -*/ - public void setCheckedValues(String[] aLabelArray) - { - setAllValues( false ); - setValues( aLabelArray, true ); - } - -/** -* Gets the labels of all checkboxes that are checked. -* @return A List of Strings containing the labels of the boxes that are checked. -*/ - public List getCheckedValueList() - { - Vector v = new Vector(); - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( ((AbstractButton)c).isSelected() ) - { - v.addElement(c.getName()); - } - } - } - return v; - } - -/** -* Gets the labels of all checkboxes that are checked. -* @return A String Array containing the labels of the boxes that are checked. -*/ - public String[] getCheckedValues() - { - List v = getCheckedValueList(); - String[] result = new String[ v.size() ]; - for ( int i = 0; i < v.size(); i++ ) - { - result[i] = (String) v.get(i); - } - return result; - } - -/** -* Gets the value of the specified button. -* @param aName A String matching the name of one of the buttons. -* @return True if the button is checked, False if it is not checked. -* NOTE: If the button is not found in the list, False is returned. -*/ - public boolean getValue( String aName ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( ( c instanceof AbstractButton ) && ( ((AbstractButton)c).isSelected() ) ) - { - if ( ((AbstractButton)c).getText().equals( aName ) ) - { - return ((AbstractButton)c).isSelected(); - } - } - } - return false; - } - - // for testing - - public static void main( String[] argv ) - { - try - { - javax.swing.UIManager.setLookAndFeel( javax.swing.UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - javax.swing.JFrame dialog = new javax.swing.JFrame(); - java.awt.BorderLayout bl = new java.awt.BorderLayout( 20, 20 ); - - CheckButtonPanel panel = new CheckButtonPanel( new String[] { "One", "Two", "Three" } ); - - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, java.awt.BorderLayout.CENTER ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - - panel.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - panel.getButton( "One" ).setEnabled( false ); - panel.setValues( new String[] { "One" }, true ); - panel.setValue( "Three", true ); + * CheckButtonPanel is a simple extension of ButtonPanel. Differences are that + * it uses JCheckBoxes and the default alignment is vertical. The panel defaults + * to having no buttons selected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class CheckButtonPanel extends ButtonPanel { + /** + * Constructs a CheckButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public CheckButtonPanel() { + super(); + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public CheckButtonPanel(String[] buttonList) { + super(buttonList); + } + + /** + * Overridden to set vertical-center alignment and zero vgap. + */ + protected void initLayout() { + super.initLayout(); + buttonPanelLayout.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + buttonPanelLayout.setVgap(0); + } + + /** + * Overridden to return a JRadioButton. + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; + JCheckBox newButton = new JCheckBox(); + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + + // reduce insets per java l&f guidelines (was 4 on each side) + newButton.setBorder(new EmptyBorder(1, 4, 1, 4)); + + return newButton; + } + + /** + * Sets the value of the button whose name matches the given text value. + * + * @param aName A String matching the name of one of the buttons. If null, + * empty, or not matching, nothing happens. + * @param aValue A value to set the button. + */ + public void setValue(String aName, boolean aValue) { + if (aName != null) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (c.getName().equals(aName)) { + ((AbstractButton) c).setSelected(aValue); + c.repaint(); + return; + } + } + } + } + // null, empty, or not matching - exit. + System.out.println("CheckButtonPanel.setValue: not found: " + aName); + } + + /** + * Sets the state of the specified buttons to the specified value. + * + * @param aLabelArray An Array of Strings listing the buttons to be set. + * @param aValue The value to which the specified buttons will be set. + */ + public void setValues(String[] aLabelArray, boolean aValue) { + if (aLabelArray != null) { + for (int i = 0; i < aLabelArray.length; i++) { + setValue(aLabelArray[i], aValue); + } + } + } + + /** + * Convenience method to set all checkboxes on the panel. + * + * @param aValue The value to which all checkboxes on the panel will be set. + */ + public void setAllValues(boolean aValue) { + setValues(getLabels(), aValue); + } + + /** + * Convenience method to check all boxes on the panel. + */ + public void checkAll() { + setAllValues(true); + } + + /** + * Convenience method to clear all boxes on the panel. + */ + public void clearAll() { + setAllValues(false); + } + + /** + * A convenience method to set only those buttons on the entire panel that + * should be checked. Buttons not in the list are unchecked. + * + * @param aLabelArray An Array of Strings listing the buttons to be set. + */ + public void setCheckedValues(String[] aLabelArray) { + setAllValues(false); + setValues(aLabelArray, true); + } + + /** + * Gets the labels of all checkboxes that are checked. + * + * @return A List of Strings containing the labels of the boxes that are + * checked. + */ + public List getCheckedValueList() { + Vector v = new Vector(); + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (((AbstractButton) c).isSelected()) { + v.addElement(c.getName()); + } + } + } + return v; + } + + /** + * Gets the labels of all checkboxes that are checked. + * + * @return A String Array containing the labels of the boxes that are checked. + */ + public String[] getCheckedValues() { + List v = getCheckedValueList(); + String[] result = new String[v.size()]; + for (int i = 0; i < v.size(); i++) { + result[i] = (String) v.get(i); + } + return result; + } + + /** + * Gets the value of the specified button. + * + * @param aName A String matching the name of one of the buttons. + * @return True if the button is checked, False if it is not checked. NOTE: If + * the button is not found in the list, False is returned. + */ + public boolean getValue(String aName) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if ((c instanceof AbstractButton) && (((AbstractButton) c).isSelected())) { + if (((AbstractButton) c).getText().equals(aName)) { + return ((AbstractButton) c).isSelected(); + } + } + } + return false; + } + + // for testing + + public static void main(String[] argv) { + try { + javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + javax.swing.JFrame dialog = new javax.swing.JFrame(); + java.awt.BorderLayout bl = new java.awt.BorderLayout(20, 20); + + CheckButtonPanel panel = new CheckButtonPanel(new String[] { "One", "Two", "Three" }); + + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, java.awt.BorderLayout.CENTER); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + + panel.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + panel.getButton("One").setEnabled(false); + panel.setValues(new String[] { "One" }, true); + panel.setValue("Three", true); // panel.setCheckedValues( new String[] { "Two" } ); - String[] values = panel.getCheckedValues(); - for ( int i = 0; i < values.length; i++ ) - { - System.out.println( values[i] ); - } + String[] values = panel.getCheckedValues(); + for (int i = 0; i < values.length; i++) { + System.out.println(values[i]); + } - dialog.pack(); - dialog.setVisible( true ); + dialog.pack(); + dialog.setVisible(true); - } + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java index a0a14ac..10feef7 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellEditor.java @@ -29,56 +29,42 @@ import javax.swing.JCheckBox; import javax.swing.JTable; /** -* A TableCellEditor that edits colors - it launches a color dialog when clicked. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellEditor that edits colors - it launches a color dialog when + * clicked. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ class ColorCellEditor extends DefaultCellEditor { - Color currentColor = null; - - public ColorCellEditor(JButton b) - { - super(new JCheckBox()); // unfortunately, the constructor - // expects a check box, combo box, - // or text field. - editorComponent = b; - setClickCountToStart(1); // this is usually 1 or 2. - - // must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - fireEditingStopped(); - } - } - ); - } - - protected void fireEditingStopped() - { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() - { - return currentColor; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) - { - ((JButton)editorComponent).setText(value.toString()); - currentColor = (Color)value; - return editorComponent; - } + Color currentColor = null; + + public ColorCellEditor(JButton b) { + super(new JCheckBox()); // unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // this is usually 1 or 2. + + // must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return currentColor; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + ((JButton) editorComponent).setText(value.toString()); + currentColor = (Color) value; + return editorComponent; + } } - - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java index 0552183..2fc97bb 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ColorCellRenderer.java @@ -28,54 +28,39 @@ import javax.swing.border.Border; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that renders colors. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that renders colors. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class ColorCellRenderer extends JLabel implements TableCellRenderer { - Border unselectedBorder = null; - Border selectedBorder = null; - boolean isBordered = true; + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; - public ColorCellRenderer(boolean isBordered) - { - super(); - this.isBordered = isBordered; - setOpaque(true); // must do this for background to show up. - } + public ColorCellRenderer(boolean isBordered) { + super(); + this.isBordered = isBordered; + setOpaque(true); // must do this for background to show up. + } - public Component getTableCellRendererComponent( - JTable table, Object color, - boolean isSelected, boolean hasFocus, - int row, int column) - { - setBackground((Color)color); - if (isBordered) - { - if (isSelected) - { - if (selectedBorder == null) - { - selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getSelectionBackground()); - } - setBorder(selectedBorder); - } - else - { - if (unselectedBorder == null) - { - unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getBackground()); - } - setBorder(unselectedBorder); - } - } - return this; - } + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, + int row, int column) { + setBackground((Color) color); + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getSelectionBackground()); + } + setBorder(selectedBorder); + } else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } } - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java index 2bf8dd6..67cb726 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ComboBoxCellRenderer.java @@ -26,32 +26,24 @@ import javax.swing.JTable; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that paints a JComboBox. Useful if -* you want to visibly display the JComboBox before the -* user clicks on the cell. -* -* @author bsafa@intersectsoft.com -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that paints a JComboBox. Useful if you want to visibly + * display the JComboBox before the user clicks on the cell. + * + * @author bsafa@intersectsoft.com + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class ComboBoxCellRenderer extends JComboBox implements TableCellRenderer { - public ComboBoxCellRenderer() - { - super(); - setOpaque(true); - } - - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - setBackground(Color.white); - setSelectedItem(value); - return this; - } + public ComboBoxCellRenderer() { + super(); + setOpaque(true); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + setBackground(Color.white); + setSelectedItem(value); + return this; + } } - - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java index 18ed035..8a3b08c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/DateTextField.java @@ -30,601 +30,507 @@ import java.util.Vector; import javax.swing.JOptionPane; import javax.swing.JTextField; - -/** -* DateTextField is a "smart" text field that restricts the user's input. The -* input is restructed to a string representing a date format. -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class DateTextField extends JTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Use the current date for this text field. -*/ - public static final int CURRENT_DATE = 0; - /** -* Use blanks for this text field. -*/ - public static final int BLANKS = 1; - -/** -* Use underscores for this text field. -*/ - public static final int UNDERSCORES = 2; - -/** -* Use just a 4-digit year for this text field. -*/ - public static final int YEAR = 3; - - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int PASTE = 22; // Ctl-V - private static final int CUT = 24; // Ctl-X - - -/******************************* -* DATA MEMEBERS -*******************************/ - private int defaultType = CURRENT_DATE; - - private boolean warningMessageActive = false; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default Constructor. -*/ - public DateTextField() - { - this(1, 1, 1999, 0); - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - -/** -* Constructor. -* @param month Number of the month, January being 1. -* @param data The day of the month. -* @param year The year. -*/ - public DateTextField(int month, int date, int year) - { - this(month, date, year, 0); - } - -/** -* Constructor. -* @param columns Width of the text field (in characters). -*/ - public DateTextField(int columns) - { - this(1, 1, 1998, columns); - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - -/** -* Constructor. -* @param month Number of the month, January being 1. -* @param data The day of the month. -* @param year The year. -* @param columns Width of the text field (in characters). -*/ - public DateTextField(int month, int date, int year, int columns) - { - super("", columns); - - super.setText(createDateString(month, date, year)); - - this.addFocusListener(new FocusAdapter() - { - public void focusLost(FocusEvent e) - { - if (!(e.isTemporary())) - { - validateDateString(e); - } - } - }); - } - -/** -* Sets the date type to display when the user has not entered any date yet. -* Default is the current date. -* @see #CURRENT_DATE -* @see #BLANKS -* @see #UNDERSCORES -* @param newDefaultType The type of date to display when there is no date data. -*/ - public void setDefaultType(int newDefaultType) - { - if (newDefaultType == BLANKS) - { - defaultType = BLANKS; - super.setText(" / / "); - } - else if (newDefaultType == UNDERSCORES) - { - defaultType = UNDERSCORES; - super.setText("__/__/____"); - } - else if (newDefaultType == YEAR) - { - defaultType = YEAR; - super.setText("0000"); - } - else - { - defaultType = CURRENT_DATE; - - Calendar rightNow = Calendar.getInstance(); - - super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, - rightNow.get(Calendar.DATE), - rightNow.get(Calendar.YEAR))); - } - } - -/** -* Returns the type of date to display when there is no user input. -* @see #CURRENT_DATE -* @see #BLANKS -* @see #UNDERSCORES -* @return The type of date to display when there is no date to display. -*/ - public int getDefaultType() - { - return defaultType; - } - -/** -* Sets the text field to the string representation of the specified date. -* @param aDate The date to set the text field to. -*/ - public void setDate(Date aDate) - { - Calendar aCalendar = Calendar.getInstance(); - - aCalendar.setTime(aDate); - - super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1, - aCalendar.get(Calendar.DATE), - aCalendar.get(Calendar.YEAR))); - } - -/** -* Sets the text field directly from a Date object. -* @param aDate The date to set the text field to. -*/ - public void setText( Date aDate ) - { - setDate( aDate ); - } - -/** -* Sets the text field to the date specified in the string. This is overridden -* from the parent class to insure a valid date is inputted. The format of the -* date expected is the type of date format this text field is currently set to. -* @param aString A string representing a date in this text field current format. -*/ - public void setText( String aString ) - { - Date testDate = null; - - if ( aString != null ) - { - ParsePosition position = new ParsePosition( 0 ); - - if ( defaultType == YEAR ) - { - SimpleDateFormat yearFormatter = new SimpleDateFormat( "yyyy" ); - testDate = yearFormatter.parse( aString, position ); - } - else - { - SimpleDateFormat fullDateFormatter = new SimpleDateFormat( "MM/dd/yyyy" ); - testDate = fullDateFormatter.parse( aString, position ); - } - } - - // The string is not a valid date, use default value for date then. - if ( testDate == null ) - { - Calendar aCalendar = Calendar.getInstance(); - - testDate = aCalendar.getTime(); - } - - setDate( testDate ); - } - -/** -* Returns the date as represented by the date string in the text field. -* @return The date in the text field. -*/ - public Date getDate() throws NumberFormatException - { - Calendar aCalendar = Calendar.getInstance(); - int year = 1980; - int month = 0; - int date = 1; - int[] tempArray = {1,3,5,7,8,10,12}; - Vector monthsWith31Days = new Vector(7); - - for (int i = 0; i < tempArray.length; ++i) - { - monthsWith31Days.addElement(new Integer(tempArray[i])); - } - - aCalendar.set(year, month, date, 12, 0, 0); - - try - { - String dateString = getText(); - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + dateString)); - - if (defaultType == YEAR) - { - year = Integer.parseInt(dateString); - - aCalendar.set(year, 0, 1, 12, 0, 0); - - return aCalendar.getTime(); - } - - month = Integer.parseInt(dateString.substring(0, 2).trim()); - date = Integer.parseInt(dateString.substring(3, 5).trim()); - year = Integer.parseInt(dateString.substring(6).trim()); - - if ((month < 1) || (month > 12)) - { - throw nfException; - } - - if ((date < 1) || (date > 31)) - { - throw nfException; - } - - if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month))))) - { - throw nfException; - } - - if ((date == 30) && (month == 2)) - { - throw nfException; - } - - if ((date == 29) && (month == 2)) - { - if ((year % 100) == 0) - { - if ((year % 400) != 0) - { - throw nfException; - } - } - else - { - if ((year % 4) != 0) - { - throw nfException; - } - } - } - } - catch (IndexOutOfBoundsException ioobe) - { - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText())); - throw nfException; - } - catch (NumberFormatException nfe) - { - NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText())); - throw nfException; - } - - aCalendar.set(year, (month - 1), date, 12, 0, 0); - - return aCalendar.getTime(); - } - - public void processKeyEvent(KeyEvent e) - { - String currentString = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int currentCaretPosition = 0; - int selectionStart = 0; - int selectionEnd = 0; - int modifierPosition = 0; - int modifierDirection = 1; - char modifierCharacter; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean cut = false; - boolean keyPressed = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - cut = (newChar == CUT); - - keyPressed = (e.paramString().startsWith("KEY_PRESSED")); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - if ((isPrintableCharacter(newChar)) || (backspace) || (delete)) - { - // Both the key "pressed" and key "released" events get passed - // in here for the delete and backspace key. Only processes - // these keys if the event is key "pressed". - if (((backspace) || (delete)) && (!(keyPressed))) - { - // Don't do anything, pass through to consumption. - } - else - { - // Analyze the current contents of the field - currentString = getText(); - currentLength = currentString.length(); - - char[] tempText = new char[currentLength]; - - currentCaretPosition = getCaretPosition(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - // if a range is selected, then get rid of it and place the caret - // at the begginning of the range and continue processing. - if (selectionStart != selectionEnd) - { - selectionEnd = selectionStart; - setSelectionEnd(selectionEnd); - - currentCaretPosition = selectionStart; - setCaretPosition(currentCaretPosition); - } - - if (currentCaretPosition <= currentLength) - { - // a number of delete or backspace was pressed, delete and - // backspace deletes a number and places a "space" there - - // if caret at start of string and the backspace pressed OR - // caret at end of string and delete or number pressed THEN - // don't do anything, otherwise process key stroke - if (((currentCaretPosition == 0) && (backspace)) || - ((currentCaretPosition == currentLength) && (!(backspace)))) - { - // Don't do any processing. - } - else - { - modifierPosition = currentCaretPosition; - if (backspace) - { - modifierDirection = -1; - modifierPosition += modifierDirection; - } - - // Overwrite the current position with the new character - // inputted or overwrite using a space or underscore if - // the backspace or delete key was pressed. - if (defaultType != YEAR) - { - modifierCharacter = - ((delete)||(backspace)) ? - ((defaultType == UNDERSCORES) ? '_' : ' ') : - newChar; - } - else - { - // We are dealing with a 4-digit year. Overwrite - // with new character or "0" if delete or backspace - // was pressed. - modifierCharacter = ((delete)||(backspace)) ? ('0') : newChar; - } - - if (currentString.charAt(modifierPosition) == '/') - { - modifierPosition += modifierDirection; - } - - for (int i = 0; i < currentLength; ++i) - { - if (i == modifierPosition) - { - tempText[i] = modifierCharacter; - } - else - { - tempText[i] = currentString.charAt(i); - } - } - - testString = new String(tempText); - if (isValidString(testString)) - { - super.setText(testString); - if (backspace) - { - setCaretPosition(modifierPosition); - } - else - { - setCaretPosition(modifierPosition + 1); - } - } - } - } - } - - e.consume(); - } - else if ((cut) || (paste)) - { - e.consume(); - } - // else its a non-printable character, let it pass through - } - else - { - e.consume(); - } - } - - super.processKeyEvent(e); - } - - private boolean isValidCharacter(char aChar) - { - if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) - { - return false; - } - return true; - } - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } - - private boolean isValidDate(int month, int date, int year) - { - if ((month < 1) || (month > 12)) - { - return false; - } - - if ((date < 1) || (date > 31)) - { - return false; - } - - if ((year < 0) || (year > 9999)) - { - return false; - } - - return true; - } - - private boolean isValidString(String aString) - { - return true; - } - - private String createDateString(int month, int date, int year) - { - String dateString = ""; - - if (isValidDate(month, date, year)) - { - if (defaultType != YEAR) - { - if (month < 10) - { - dateString = "0"; - } - - dateString += String.valueOf(month); - dateString += "/"; - - if (date < 10) - { - dateString += "0"; - } - - dateString += String.valueOf(date); - dateString += "/"; - } - - if (year < 1000) - { - dateString += "0"; - if (year < 100) - { - dateString += "0"; - if (year < 10) - { - dateString += "0"; - } - } - } - - dateString += String.valueOf(year); - } - else - { - if (defaultType == YEAR) - { - dateString = "1999"; - } - else - { - dateString = "01/01/1999"; - } - } - - return dateString; - } - - private void validateDateString(FocusEvent e) - { - if (!(warningMessageActive)) - { - try - { - getDate(); - } - catch (NumberFormatException nfe) - { - System.out.println("Invalid Date String!!!"); - warningMessageActive = true; - JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", JOptionPane.WARNING_MESSAGE); - warningMessageActive = false; - if (defaultType == YEAR) - { - super.setText("1999"); - } - else - { - super.setText("01/01/1999"); - } - } - } - } + * DateTextField is a "smart" text field that restricts the user's input. The + * input is restructed to a string representing a date format. + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DateTextField extends JTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Use the current date for this text field. + */ + public static final int CURRENT_DATE = 0; + + /** + * Use blanks for this text field. + */ + public static final int BLANKS = 1; + + /** + * Use underscores for this text field. + */ + public static final int UNDERSCORES = 2; + + /** + * Use just a 4-digit year for this text field. + */ + public static final int YEAR = 3; + + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int PASTE = 22; // Ctl-V + private static final int CUT = 24; // Ctl-X + + /******************************* + * DATA MEMEBERS + *******************************/ + private int defaultType = CURRENT_DATE; + + private boolean warningMessageActive = false; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default Constructor. + */ + public DateTextField() { + this(1, 1, 1999, 0); + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + + /** + * Constructor. + * + * @param month Number of the month, January being 1. + * @param data The day of the month. + * @param year The year. + */ + public DateTextField(int month, int date, int year) { + this(month, date, year, 0); + } + + /** + * Constructor. + * + * @param columns Width of the text field (in characters). + */ + public DateTextField(int columns) { + this(1, 1, 1998, columns); + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + + /** + * Constructor. + * + * @param month Number of the month, January being 1. + * @param data The day of the month. + * @param year The year. + * @param columns Width of the text field (in characters). + */ + public DateTextField(int month, int date, int year, int columns) { + super("", columns); + + super.setText(createDateString(month, date, year)); + + this.addFocusListener(new FocusAdapter() { + public void focusLost(FocusEvent e) { + if (!(e.isTemporary())) { + validateDateString(e); + } + } + }); + } + + /** + * Sets the date type to display when the user has not entered any date yet. + * Default is the current date. + * + * @see #CURRENT_DATE + * @see #BLANKS + * @see #UNDERSCORES + * @param newDefaultType The type of date to display when there is no date data. + */ + public void setDefaultType(int newDefaultType) { + if (newDefaultType == BLANKS) { + defaultType = BLANKS; + super.setText(" / / "); + } else if (newDefaultType == UNDERSCORES) { + defaultType = UNDERSCORES; + super.setText("__/__/____"); + } else if (newDefaultType == YEAR) { + defaultType = YEAR; + super.setText("0000"); + } else { + defaultType = CURRENT_DATE; + + Calendar rightNow = Calendar.getInstance(); + + super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), + rightNow.get(Calendar.YEAR))); + } + } + + /** + * Returns the type of date to display when there is no user input. + * + * @see #CURRENT_DATE + * @see #BLANKS + * @see #UNDERSCORES + * @return The type of date to display when there is no date to display. + */ + public int getDefaultType() { + return defaultType; + } + + /** + * Sets the text field to the string representation of the specified date. + * + * @param aDate The date to set the text field to. + */ + public void setDate(Date aDate) { + Calendar aCalendar = Calendar.getInstance(); + + aCalendar.setTime(aDate); + + super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1, aCalendar.get(Calendar.DATE), + aCalendar.get(Calendar.YEAR))); + } + + /** + * Sets the text field directly from a Date object. + * + * @param aDate The date to set the text field to. + */ + public void setText(Date aDate) { + setDate(aDate); + } + + /** + * Sets the text field to the date specified in the string. This is overridden + * from the parent class to insure a valid date is inputted. The format of the + * date expected is the type of date format this text field is currently set to. + * + * @param aString A string representing a date in this text field current + * format. + */ + public void setText(String aString) { + Date testDate = null; + + if (aString != null) { + ParsePosition position = new ParsePosition(0); + + if (defaultType == YEAR) { + SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); + testDate = yearFormatter.parse(aString, position); + } else { + SimpleDateFormat fullDateFormatter = new SimpleDateFormat("MM/dd/yyyy"); + testDate = fullDateFormatter.parse(aString, position); + } + } + + // The string is not a valid date, use default value for date then. + if (testDate == null) { + Calendar aCalendar = Calendar.getInstance(); + + testDate = aCalendar.getTime(); + } + + setDate(testDate); + } + + /** + * Returns the date as represented by the date string in the text field. + * + * @return The date in the text field. + */ + public Date getDate() throws NumberFormatException { + Calendar aCalendar = Calendar.getInstance(); + int year = 1980; + int month = 0; + int date = 1; + int[] tempArray = { 1, 3, 5, 7, 8, 10, 12 }; + Vector monthsWith31Days = new Vector(7); + + for (int i = 0; i < tempArray.length; ++i) { + monthsWith31Days.addElement(new Integer(tempArray[i])); + } + + aCalendar.set(year, month, date, 12, 0, 0); + + try { + String dateString = getText(); + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + dateString)); + + if (defaultType == YEAR) { + year = Integer.parseInt(dateString); + + aCalendar.set(year, 0, 1, 12, 0, 0); + + return aCalendar.getTime(); + } + + month = Integer.parseInt(dateString.substring(0, 2).trim()); + date = Integer.parseInt(dateString.substring(3, 5).trim()); + year = Integer.parseInt(dateString.substring(6).trim()); + + if ((month < 1) || (month > 12)) { + throw nfException; + } + + if ((date < 1) || (date > 31)) { + throw nfException; + } + + if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month))))) { + throw nfException; + } + + if ((date == 30) && (month == 2)) { + throw nfException; + } + + if ((date == 29) && (month == 2)) { + if ((year % 100) == 0) { + if ((year % 400) != 0) { + throw nfException; + } + } else { + if ((year % 4) != 0) { + throw nfException; + } + } + } + } catch (IndexOutOfBoundsException ioobe) { + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + getText())); + throw nfException; + } catch (NumberFormatException nfe) { + NumberFormatException nfException = new NumberFormatException( + new String("Invalid Date String: " + getText())); + throw nfException; + } + + aCalendar.set(year, (month - 1), date, 12, 0, 0); + + return aCalendar.getTime(); + } + + public void processKeyEvent(KeyEvent e) { + String currentString = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int currentCaretPosition = 0; + int selectionStart = 0; + int selectionEnd = 0; + int modifierPosition = 0; + int modifierDirection = 1; + char modifierCharacter; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean cut = false; + boolean keyPressed = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + cut = (newChar == CUT); + + keyPressed = (e.paramString().startsWith("KEY_PRESSED")); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut))) // A "key-typed" + // event + { + if (isValidCharacter(newChar)) { + if ((isPrintableCharacter(newChar)) || (backspace) || (delete)) { + // Both the key "pressed" and key "released" events get passed + // in here for the delete and backspace key. Only processes + // these keys if the event is key "pressed". + if (((backspace) || (delete)) && (!(keyPressed))) { + // Don't do anything, pass through to consumption. + } else { + // Analyze the current contents of the field + currentString = getText(); + currentLength = currentString.length(); + + char[] tempText = new char[currentLength]; + + currentCaretPosition = getCaretPosition(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + // if a range is selected, then get rid of it and place the caret + // at the begginning of the range and continue processing. + if (selectionStart != selectionEnd) { + selectionEnd = selectionStart; + setSelectionEnd(selectionEnd); + + currentCaretPosition = selectionStart; + setCaretPosition(currentCaretPosition); + } + + if (currentCaretPosition <= currentLength) { + // a number of delete or backspace was pressed, delete and + // backspace deletes a number and places a "space" there + + // if caret at start of string and the backspace pressed OR + // caret at end of string and delete or number pressed THEN + // don't do anything, otherwise process key stroke + if (((currentCaretPosition == 0) && (backspace)) + || ((currentCaretPosition == currentLength) && (!(backspace)))) { + // Don't do any processing. + } else { + modifierPosition = currentCaretPosition; + if (backspace) { + modifierDirection = -1; + modifierPosition += modifierDirection; + } + + // Overwrite the current position with the new character + // inputted or overwrite using a space or underscore if + // the backspace or delete key was pressed. + if (defaultType != YEAR) { + modifierCharacter = ((delete) || (backspace)) + ? ((defaultType == UNDERSCORES) ? '_' : ' ') + : newChar; + } else { + // We are dealing with a 4-digit year. Overwrite + // with new character or "0" if delete or backspace + // was pressed. + modifierCharacter = ((delete) || (backspace)) ? ('0') : newChar; + } + + if (currentString.charAt(modifierPosition) == '/') { + modifierPosition += modifierDirection; + } + + for (int i = 0; i < currentLength; ++i) { + if (i == modifierPosition) { + tempText[i] = modifierCharacter; + } else { + tempText[i] = currentString.charAt(i); + } + } + + testString = new String(tempText); + if (isValidString(testString)) { + super.setText(testString); + if (backspace) { + setCaretPosition(modifierPosition); + } else { + setCaretPosition(modifierPosition + 1); + } + } + } + } + } + + e.consume(); + } else if ((cut) || (paste)) { + e.consume(); + } + // else its a non-printable character, let it pass through + } else { + e.consume(); + } + } + + super.processKeyEvent(e); + } + + private boolean isValidCharacter(char aChar) { + if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { + return false; + } + return true; + } + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } + + private boolean isValidDate(int month, int date, int year) { + if ((month < 1) || (month > 12)) { + return false; + } + + if ((date < 1) || (date > 31)) { + return false; + } + + if ((year < 0) || (year > 9999)) { + return false; + } + + return true; + } + + private boolean isValidString(String aString) { + return true; + } + + private String createDateString(int month, int date, int year) { + String dateString = ""; + + if (isValidDate(month, date, year)) { + if (defaultType != YEAR) { + if (month < 10) { + dateString = "0"; + } + + dateString += String.valueOf(month); + dateString += "/"; + + if (date < 10) { + dateString += "0"; + } + + dateString += String.valueOf(date); + dateString += "/"; + } + + if (year < 1000) { + dateString += "0"; + if (year < 100) { + dateString += "0"; + if (year < 10) { + dateString += "0"; + } + } + } + + dateString += String.valueOf(year); + } else { + if (defaultType == YEAR) { + dateString = "1999"; + } else { + dateString = "01/01/1999"; + } + } + + return dateString; + } + + private void validateDateString(FocusEvent e) { + if (!(warningMessageActive)) { + try { + getDate(); + } catch (NumberFormatException nfe) { + System.out.println("Invalid Date String!!!"); + warningMessageActive = true; + JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", + JOptionPane.WARNING_MESSAGE); + warningMessageActive = false; + if (defaultType == YEAR) { + super.setText("1999"); + } else { + super.setText("01/01/1999"); + } + } + } + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java index b3e2a76..10b1b89 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/FormattedCellRenderer.java @@ -27,101 +27,92 @@ import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; /** -* A cell renderer for dealing with formatted content. -* Subclasses can specify formats or colors or styles for specific values -* or locations in the table by overridding getFormatForContext(), -* getForegroundForContext() and/or getBackgroundForContext(). -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class FormattedCellRenderer extends DefaultTableCellRenderer -{ - protected Format currentFormat, defaultFormat; - protected Color defaultForeground, defaultBackground; - protected Font defaultFont; - -/** -* Default constructor with no specified format. -*/ - public FormattedCellRenderer() - { - this( (Format) null ); - } - -/** -* Constructor specifying a format for renderered content. -*/ - public FormattedCellRenderer( Format aFormat ) - { - currentFormat = null; - defaultFormat = aFormat; - defaultForeground = super.getForeground(); - defaultBackground = super.getForeground(); - } - -/** -* Returns the format currently in use to format cell content. -* @return The Format that is currently being used. -*/ - public Format getFormat() - { - return defaultFormat; - } - -/** -* Sets the format to be used to format cell content. -*/ - public void setFormat( Format aFormat ) - { - defaultFormat = aFormat; - } - -/** -* Overrides to retain the default foreground color, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setForeground(Color c) { - super.setForeground(c); - defaultForeground = c; - } - -/** -* Overrides to retain the default background color, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setBackground(Color c) { - super.setBackground(c); - defaultBackground = c; - } - -/** -* Overrides to retain the default font, -* much the same as the DefaultCellRenderer does. -* We have to do this because DefaultCellRenderer's -* ivars are private. -*/ - public void setFont(Font f) { - super.setFont(f); - defaultFont = f; - } - -/** -* Overridden to format the value with the appropriate Format. If the -* value cannot be formatted with the Format, the superclass method is called. -* @param value An Object to be formatted. -*/ - protected void setValue(Object value) - { - if ( currentFormat != null ) - { - try - { + * A cell renderer for dealing with formatted content. Subclasses can specify + * formats or colors or styles for specific values or locations in the table by + * overridding getFormatForContext(), getForegroundForContext() and/or + * getBackgroundForContext(). + * + * @author michael@mpowers.net + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class FormattedCellRenderer extends DefaultTableCellRenderer { + protected Format currentFormat, defaultFormat; + protected Color defaultForeground, defaultBackground; + protected Font defaultFont; + + /** + * Default constructor with no specified format. + */ + public FormattedCellRenderer() { + this((Format) null); + } + + /** + * Constructor specifying a format for renderered content. + */ + public FormattedCellRenderer(Format aFormat) { + currentFormat = null; + defaultFormat = aFormat; + defaultForeground = super.getForeground(); + defaultBackground = super.getForeground(); + } + + /** + * Returns the format currently in use to format cell content. + * + * @return The Format that is currently being used. + */ + public Format getFormat() { + return defaultFormat; + } + + /** + * Sets the format to be used to format cell content. + */ + public void setFormat(Format aFormat) { + defaultFormat = aFormat; + } + + /** + * Overrides to retain the default foreground color, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setForeground(Color c) { + super.setForeground(c); + defaultForeground = c; + } + + /** + * Overrides to retain the default background color, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setBackground(Color c) { + super.setBackground(c); + defaultBackground = c; + } + + /** + * Overrides to retain the default font, much the same as the + * DefaultCellRenderer does. We have to do this because DefaultCellRenderer's + * ivars are private. + */ + public void setFont(Font f) { + super.setFont(f); + defaultFont = f; + } + + /** + * Overridden to format the value with the appropriate Format. If the value + * cannot be formatted with the Format, the superclass method is called. + * + * @param value An Object to be formatted. + */ + protected void setValue(Object value) { + if (currentFormat != null) { + try { // if ( ( value instanceof Number ) && ( value.toString().indexOf( "E" ) != -1 ) ) // { @@ -132,153 +123,140 @@ public class FormattedCellRenderer extends DefaultTableCellRenderer // System.out.println( "FormattedCellRenderer.setValue: converted = '" + currentFormat.format( value ) + "'" ); // } - // WORKAROUND: This works around what may be a rounding bug in DecimalFormat. (PR 256/297) - currentFormat.format( ZERO ); - - // DEBUG: code to test for weird one/zero problem (PR 256/297) - String result = currentFormat.format( value ); -/* above workaround seems to be working - if ( result.equals( "1" ) ) - { - System.out.println( "FormattedCellRenderer.setValue: Could be the ONE/ZERO problem!" ); - System.out.println( "FormattedCellRenderer.setValue: format = '" + currentFormat.getClass() + "'" ); - System.out.println( "FormattedCellRenderer.setValue: original value = '" + value + "'" ); - System.out.println( "FormattedCellRenderer.setValue: result = '" + result + "'" ); - } -*/ - setText( result ); - + // WORKAROUND: This works around what may be a rounding bug in DecimalFormat. + // (PR 256/297) + currentFormat.format(ZERO); + + // DEBUG: code to test for weird one/zero problem (PR 256/297) + String result = currentFormat.format(value); + /* + * above workaround seems to be working if ( result.equals( "1" ) ) { + * System.out.println( + * "FormattedCellRenderer.setValue: Could be the ONE/ZERO problem!" ); + * System.out.println( "FormattedCellRenderer.setValue: format = '" + + * currentFormat.getClass() + "'" ); System.out.println( + * "FormattedCellRenderer.setValue: original value = '" + value + "'" ); + * System.out.println( "FormattedCellRenderer.setValue: result = '" + result + + * "'" ); } + */ + setText(result); // setText( currentFormat.format( value ) ); - return; - } - catch ( IllegalArgumentException exc ) - { - // fall back on superclass implementation - } - } - super.setValue( value ); - } - - // FIXME: remove this when possible - private static Double ZERO = new Double( 0.0 ); - -/** -* Overridden to call context delegate methods. -*/ - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - Format format; - - // allow for context-sensitve formatting - format = getFormatForContext( table, value, isSelected, hasFocus, row, column ); - if ( format != null ) - { - currentFormat = format; - } - else - { - currentFormat = defaultFormat; - } - - Color color; - - // allow for context-sensitve foreground color - color = getForegroundForContext( table, value, isSelected, hasFocus, row, column ); - if ( color != null ) - { - super.setForeground( color ); - } - else - { - super.setForeground( defaultForeground ); - } - - // allow for context-sensitve background color - color = getBackgroundForContext( table, value, isSelected, hasFocus, row, column ); - if ( color != null ) - { - super.setBackground( color ); - } - else - { - super.setBackground( defaultBackground ); - } - - // have to call this here because super defaults to table's font - Component result = - super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); - // NOTE: DefaultTableCellRenderer returns itself. - - // allow for context-sensitve font - Font font = getFontForContext( table, value, isSelected, hasFocus, row, column ); - if ( font != null ) - { - result.setFont( font ); - } - else - { - result.setFont( defaultFont ); - } - - return result; - - } - -/** -* Override this method to provide a specific format for the -* specific cell to be rendered by this component. Any format -* returned by this method will take precedence of the format -* specified by setFormat().

-* This default implementation returns null. -* @return A Format for this cell, or null to rely on the the -* format specified by setFormat(). -*/ - public Format getFormatForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a foreground color for the renderer. -* Because the table specifies colors for selected cells, -* these colors will only be used when renderering unselected cells.

-* This default implementation returns null. -* @return A Color for the foreground of the cell, or null to rely on -* the table's default color scheme. -*/ - public Color getForegroundForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a background color for the renderer. -* Because the table specifies colors for selected cells, -* these colors will only be used when renderering unselected cells.

-* This default implementation returns null. -* @return A Color for the background of the cell, or null to rely on -* the table's default color scheme. -*/ - public Color getBackgroundForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } - -/** -* Override this method to provide a font for the renderer.

-* This default implementation returns null. -* @return A Font for the cell, or null to rely on the table's default font. -*/ - public Font getFontForContext(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - return null; - } + return; + } catch (IllegalArgumentException exc) { + // fall back on superclass implementation + } + } + super.setValue(value); + } + + // FIXME: remove this when possible + private static Double ZERO = new Double(0.0); + + /** + * Overridden to call context delegate methods. + */ + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + Format format; + + // allow for context-sensitve formatting + format = getFormatForContext(table, value, isSelected, hasFocus, row, column); + if (format != null) { + currentFormat = format; + } else { + currentFormat = defaultFormat; + } + + Color color; + + // allow for context-sensitve foreground color + color = getForegroundForContext(table, value, isSelected, hasFocus, row, column); + if (color != null) { + super.setForeground(color); + } else { + super.setForeground(defaultForeground); + } + + // allow for context-sensitve background color + color = getBackgroundForContext(table, value, isSelected, hasFocus, row, column); + if (color != null) { + super.setBackground(color); + } else { + super.setBackground(defaultBackground); + } + + // have to call this here because super defaults to table's font + Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + // NOTE: DefaultTableCellRenderer returns itself. + + // allow for context-sensitve font + Font font = getFontForContext(table, value, isSelected, hasFocus, row, column); + if (font != null) { + result.setFont(font); + } else { + result.setFont(defaultFont); + } + + return result; + + } + + /** + * Override this method to provide a specific format for the specific cell to be + * rendered by this component. Any format returned by this method will take + * precedence of the format specified by setFormat().
+ *
+ * This default implementation returns null. + * + * @return A Format for this cell, or null to rely on the the format specified + * by setFormat(). + */ + public Format getFormatForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a foreground color for the renderer. Because + * the table specifies colors for selected cells, these colors will only be used + * when renderering unselected cells.
+ *
+ * This default implementation returns null. + * + * @return A Color for the foreground of the cell, or null to rely on the + * table's default color scheme. + */ + public Color getForegroundForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a background color for the renderer. Because + * the table specifies colors for selected cells, these colors will only be used + * when renderering unselected cells.
+ *
+ * This default implementation returns null. + * + * @return A Color for the background of the cell, or null to rely on the + * table's default color scheme. + */ + public Color getBackgroundForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } + + /** + * Override this method to provide a font for the renderer.
+ *
+ * This default implementation returns null. + * + * @return A Font for the cell, or null to rely on the table's default font. + */ + public Font getFontForContext(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + return null; + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java index 8320d08..2d9531d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/IconCellRenderer.java @@ -54,792 +54,654 @@ import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeCellRenderer; /** -* A cell renderer that displays icons in addition to text, -* and additionally is an editor in case you want to click -* the icon to trigger some kind of action. -* You probably should override both getStringForContext and -* getIconForContext to achieve your desired results. -* To receive mouse clicks, set the same instance of the -* renderer as the editor for the same component.

-* -* One notable addition is that this class is an action event -* broadcaster. ActionEvents are broadcast when the mouse is -* clicked on the button with an action event containing a -* user-configurable string that defaults to CLICKED.

-* -* The renderer itself can be used as a JComponent if -* you need something like a JLabel that allows you to click -* on the icon. You will want to call setIcon and setText -* to configure the component since the renderer method would -* not be called. (If you add an instance of the renderer -* to a container, you cannnot use the same instance as an -* editor in a table, tree, or list.) -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class IconCellRenderer extends JPanel - implements TableCellRenderer, TableCellEditor, - TreeCellRenderer, TreeCellEditor, ListCellRenderer, - Runnable, ActionListener, MouseListener -{ + * A cell renderer that displays icons in addition to text, and additionally is + * an editor in case you want to click the icon to trigger some kind of action. + * You probably should override both getStringForContext and getIconForContext + * to achieve your desired results. To receive mouse clicks, set the same + * instance of the renderer as the editor for the same component.
+ *
+ * + * One notable addition is that this class is an action event broadcaster. + * ActionEvents are broadcast when the mouse is clicked on the button with an + * action event containing a user-configurable string that defaults to CLICKED. + *
+ *
+ * + * The renderer itself can be used as a JComponent if you need something like a + * JLabel that allows you to click on the icon. You will want to call setIcon + * and setText to configure the component since the renderer method would not be + * called. (If you add an instance of the renderer to a container, you cannnot + * use the same instance as an editor in a table, tree, or list.) + * + * @author michael@mpowers.net + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class IconCellRenderer extends JPanel implements TableCellRenderer, TableCellEditor, TreeCellRenderer, + TreeCellEditor, ListCellRenderer, Runnable, ActionListener, MouseListener { public static final String CLICKED = "CLICKED"; - + /** - * The panel that is re-used to render everything. - * This is returned by getRendererForContext. - */ - protected JPanel rendererPanel; + * The panel that is re-used to render everything. This is returned by + * getRendererForContext. + */ + protected JPanel rendererPanel; protected JLabel rendererLabel; - protected JButton rendererButton; + protected JButton rendererButton; /** - * The panel that is used to receive mouse clicks. - * It must be a different component from rendererPanel. - * This is returned by getEditorForContext. - */ - protected JPanel editorPanel; + * The panel that is used to receive mouse clicks. It must be a different + * component from rendererPanel. This is returned by getEditorForContext. + */ + protected JPanel editorPanel; protected JLabel editorLabel; - protected JButton editorButton; - + protected JButton editorButton; + private Object lastKnownValue; - private JComponent lastKnownComponent; - + private JComponent lastKnownComponent; + // do as DefaultTableCellRenderer does private Border noFocusBorder; private Border treeFocusBorder; private Color unselectedForeground; private Color unselectedBackground; - + private Vector actionListeners; private String actionCommand; private Vector cellEditorListeners; - - private boolean editable; - private boolean clickable; - - /** - * Default constructor. - */ - public IconCellRenderer() - { - editable = true; - clickable = true; - - noFocusBorder = new EmptyBorder(1, 1, 1, 1); - treeFocusBorder = new LineBorder( - UIManager.getColor("Tree.selectionBorderColor") ); - setActionCommand( CLICKED ); - - rendererPanel = new JPanel(); - rendererPanel.setLayout( new GridBagLayout() ); - - editorPanel = this; - editorPanel.setLayout( new GridBagLayout() ); - - // set up constraints - GridBagConstraints imageConstraints = new GridBagConstraints(); - imageConstraints.gridx = 0; - GridBagConstraints labelConstraints = new GridBagConstraints(); - labelConstraints.fill = GridBagConstraints.HORIZONTAL; - labelConstraints.gridx = 1; - labelConstraints.weightx = 1.0; - labelConstraints.ipadx = 1; - labelConstraints.insets = new Insets( 0, 1, 0, 0 ); // sweat the pixel - - // make the editor panel go away when not in use - // and pass through all mouse events to container - - //this is not very useful since editorLabel and editorButton - //get all of the events - editorPanel.addMouseListener( this ); - + + private boolean editable; + private boolean clickable; + + /** + * Default constructor. + */ + public IconCellRenderer() { + editable = true; + clickable = true; + + noFocusBorder = new EmptyBorder(1, 1, 1, 1); + treeFocusBorder = new LineBorder(UIManager.getColor("Tree.selectionBorderColor")); + setActionCommand(CLICKED); + + rendererPanel = new JPanel(); + rendererPanel.setLayout(new GridBagLayout()); + + editorPanel = this; + editorPanel.setLayout(new GridBagLayout()); + + // set up constraints + GridBagConstraints imageConstraints = new GridBagConstraints(); + imageConstraints.gridx = 0; + GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.fill = GridBagConstraints.HORIZONTAL; + labelConstraints.gridx = 1; + labelConstraints.weightx = 1.0; + labelConstraints.ipadx = 1; + labelConstraints.insets = new Insets(0, 1, 0, 0); // sweat the pixel + + // make the editor panel go away when not in use + // and pass through all mouse events to container + + // this is not very useful since editorLabel and editorButton + // get all of the events + editorPanel.addMouseListener(this); + rendererLabel = new JLabel(); - rendererLabel.setOpaque( false ); - rendererPanel.add( rendererLabel, labelConstraints ); + rendererLabel.setOpaque(false); + rendererPanel.add(rendererLabel, labelConstraints); editorLabel = new JLabel(); - editorLabel.setText( "" ); // default state - editorLabel.setOpaque( false ); - editorPanel.add( editorLabel, labelConstraints ); + editorLabel.setText(""); // default state + editorLabel.setOpaque(false); + editorPanel.add(editorLabel, labelConstraints); unselectedForeground = rendererLabel.getForeground(); unselectedBackground = rendererLabel.getBackground(); - - rendererButton = new JButton(); - rendererButton.setBorder( null ); - rendererButton.setBorderPainted( false ); - rendererButton.setContentAreaFilled( false ); - rendererButton.setFocusPainted( false ); - rendererButton.setMargin( new Insets( 0, 0, 0, 0 ) ); - rendererPanel.add( rendererButton, imageConstraints ); - - editorButton = new JButton(); - editorButton.setEnabled( clickable ); // default state - editorButton.setIcon( null ); // default state - editorButton.setBorder( null ); - editorButton.setBorderPainted( false ); - editorButton.setContentAreaFilled( false ); - editorButton.setFocusPainted( false ); - editorButton.setMargin( new Insets( 0, 0, 0, 0 ) ); - editorPanel.add( editorButton, imageConstraints ); - - editorButton.addActionListener( this ); - - //add these in order to dispatch the MouseEvents - //to the lastKnownComponent, and proper management of - //DnD operations - editorLabel.addMouseListener( this ); - editorButton.addMouseListener( this ); - } - -/** -* Returns the text string currently displayed in the editor component. -*/ - public String getText() - { - return editorLabel.getText(); - } - -/** -* Sets the text string displayed in the editor component. -* Default is an empty string. -*/ - public void setText( String aString ) - { - editorLabel.setText( aString ); - } -/** -* Returns the icon currently displayed in the editor component. -*/ - public Icon getIcon() - { - return editorButton.getIcon(); - } - -/** -* Sets the icon currently displayed in the editor component. -* Default is null. -*/ - public void setIcon( Icon anIcon ) - { - editorButton.setIcon( anIcon ); - if ( !isClickable() ) - { - editorButton.setDisabledIcon( anIcon ); - } - } + rendererButton = new JButton(); + rendererButton.setBorder(null); + rendererButton.setBorderPainted(false); + rendererButton.setContentAreaFilled(false); + rendererButton.setFocusPainted(false); + rendererButton.setMargin(new Insets(0, 0, 0, 0)); + rendererPanel.add(rendererButton, imageConstraints); + + editorButton = new JButton(); + editorButton.setEnabled(clickable); // default state + editorButton.setIcon(null); // default state + editorButton.setBorder(null); + editorButton.setBorderPainted(false); + editorButton.setContentAreaFilled(false); + editorButton.setFocusPainted(false); + editorButton.setMargin(new Insets(0, 0, 0, 0)); + editorPanel.add(editorButton, imageConstraints); + + editorButton.addActionListener(this); + + // add these in order to dispatch the MouseEvents + // to the lastKnownComponent, and proper management of + // DnD operations + editorLabel.addMouseListener(this); + editorButton.addMouseListener(this); + } -/** -* Returns whether the editor component's label text is editable. -*/ - public boolean isEditable() - { - return editable; - } - -/** -* Sets whether the editor component's label text is editable. -* Default is true. Editable text is not yet implemented. -*/ - public void setEditable( boolean isEditable ) - { - editable = isEditable; - } + /** + * Returns the text string currently displayed in the editor component. + */ + public String getText() { + return editorLabel.getText(); + } -/** -* Returns whether the editor component's icon is clickable. -*/ - public boolean isClickable() - { - return clickable; - } - -/** -* Sets whether the editor component's icon is clickable. -* Default is true. -*/ - public void setClickable( boolean isClickable ) - { - clickable = isClickable; - editorButton.setEnabled( clickable ); - } + /** + * Sets the text string displayed in the editor component. Default is an empty + * string. + */ + public void setText(String aString) { + editorLabel.setText(aString); + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getListCellRendererComponent(JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus) - { - lastKnownComponent = list; - return getRendererForContext( - list, value, index, 0, isSelected, cellHasFocus, false, true ); - } + /** + * Returns the icon currently displayed in the editor component. + */ + public Icon getIcon() { + return editorButton.getIcon(); + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) - { - lastKnownComponent = table; - return getRendererForContext( - table, value, row, column, isSelected, hasFocus, false, true ); - } + /** + * Sets the icon currently displayed in the editor component. Default is null. + */ + public void setIcon(Icon anIcon) { + editorButton.setIcon(anIcon); + if (!isClickable()) { + editorButton.setDisabledIcon(anIcon); + } + } -/** -* Returns the component from getRendererForContext. -*/ - public Component getTreeCellRendererComponent(JTree tree, - Object value, - boolean selected, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus) - { - lastKnownComponent = tree; - return getRendererForContext( - tree, value, row, 0, selected, hasFocus, expanded, leaf ); - } - -/** -* Returns getEditorForContext with the same parameters with hasFocus true. -*/ - public Component getTableCellEditorComponent(JTable table, - Object value, boolean isSelected, int row, int column) - { + /** + * Returns whether the editor component's label text is editable. + */ + public boolean isEditable() { + return editable; + } + + /** + * Sets whether the editor component's label text is editable. Default is true. + * Editable text is not yet implemented. + */ + public void setEditable(boolean isEditable) { + editable = isEditable; + } + + /** + * Returns whether the editor component's icon is clickable. + */ + public boolean isClickable() { + return clickable; + } + + /** + * Sets whether the editor component's icon is clickable. Default is true. + */ + public void setClickable(boolean isClickable) { + clickable = isClickable; + editorButton.setEnabled(clickable); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + lastKnownComponent = list; + return getRendererForContext(list, value, index, 0, isSelected, cellHasFocus, false, true); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + lastKnownComponent = table; + return getRendererForContext(table, value, row, column, isSelected, hasFocus, false, true); + } + + /** + * Returns the component from getRendererForContext. + */ + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + lastKnownComponent = tree; + return getRendererForContext(tree, value, row, 0, selected, hasFocus, expanded, leaf); + } + + /** + * Returns getEditorForContext with the same parameters with hasFocus true. + */ + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { lastKnownValue = value; - lastKnownComponent = table; - return getEditorForContext( - table, value, row, column, isSelected, true, false, true ); + lastKnownComponent = table; + return getEditorForContext(table, value, row, column, isSelected, true, false, true); } -/** -* Returns the component from getEditorForContext with hasFocus true. -*/ - public Component getTreeCellEditorComponent(JTree tree, - Object value, - boolean isSelected, - boolean expanded, - boolean leaf, - int row) - { - - - lastKnownValue = value; - lastKnownComponent = tree; - - return getEditorForContext( - tree, value, row, 0, isSelected, true, expanded, leaf ); - } - -/** -* This default implementation returns a JPanel that is configured by -* calling configureComponentForContext. -* @return An component that is used to render content. -*/ - public Component getRendererForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - - - configureComponentForContext( rendererPanel, rendererButton, rendererLabel, - container, value, row, column, - isSelected, hasFocus, isExpanded, isLeaf ); + /** + * Returns the component from getEditorForContext with hasFocus true. + */ + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, + boolean leaf, int row) { + + lastKnownValue = value; + lastKnownComponent = tree; + + return getEditorForContext(tree, value, row, 0, isSelected, true, expanded, leaf); + } + + /** + * This default implementation returns a JPanel that is configured by calling + * configureComponentForContext. + * + * @return An component that is used to render content. + */ + public Component getRendererForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + + configureComponentForContext(rendererPanel, rendererButton, rendererLabel, container, value, row, column, + isSelected, hasFocus, isExpanded, isLeaf); return rendererPanel; - } + } -/** -* This method returns a separate component that should be visually -* identical to the renderer component. We can't simply reuse the -* renderer component because the renderer is still used to paint -* the table while the editor component is displayed. Clicks are -* received on this component. -* This default implementation returns a JPanel that is configured by -* calling configureComponentForContext. -* @return A component used to receive clicks on the cell. -*/ - public Component getEditorForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - configureComponentForContext( editorPanel, editorButton, editorLabel, - container, value, row, column, - true, hasFocus, isExpanded, isLeaf ); // editor should always be selected + /** + * This method returns a separate component that should be visually identical to + * the renderer component. We can't simply reuse the renderer component because + * the renderer is still used to paint the table while the editor component is + * displayed. Clicks are received on this component. This default implementation + * returns a JPanel that is configured by calling configureComponentForContext. + * + * @return A component used to receive clicks on the cell. + */ + public Component getEditorForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + configureComponentForContext(editorPanel, editorButton, editorLabel, container, value, row, column, true, + hasFocus, isExpanded, isLeaf); // editor should always be selected return editorPanel; - } + } -/** -* Called to configure components -*/ - protected void configureComponentForContext( - JPanel component, JButton iconButton, JLabel label, - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - if (hasFocus) - { - if ( container instanceof JTable ) - { - component.setBorder( - UIManager.getBorder("Table.focusCellHighlightBorder") ); - } - else - { - component.setBorder( noFocusBorder ); - } - - if ( container instanceof JTree ) // was: (false) - { - label.setBorder( treeFocusBorder ); - } - else - { - label.setBorder( noFocusBorder ); - } - } - else - { - label.setBorder(noFocusBorder); - component.setBorder(noFocusBorder); - } - - if (isSelected) - { - if ( container instanceof JTree ) - { - label.setOpaque( true ); - label.setForeground(UIManager.getColor("Tree.selectionForeground")); - label.setBackground(UIManager.getColor("Tree.selectionBackground")); - component.setBackground(container.getBackground()); - } - else if ( container instanceof JTable ) - { - label.setOpaque( false ); - label.setForeground( ((JTable)container).getSelectionForeground() ); - component.setBackground(((JTable)container).getSelectionBackground()); - } - else - { - label.setOpaque( false ); - label.setForeground(UIManager.getColor("Table.selectionForeground")); - component.setBackground(UIManager.getColor("Table.selectionBackground")); - } - } - else - { - label.setOpaque( false ); - label.setForeground(container.getForeground()); - component.setBackground(container.getBackground()); - } - - label.setFont(container.getFont()); - - Icon icon = getIconForContext( - container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf ); - iconButton.setIcon( icon ); - if ( !isClickable() ) - { - iconButton.setDisabledIcon( icon ); - } - - String text = getStringForContext( - container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf ); - - if ( ( text == null ) || ( "".equals( text ) ) ) - { - if ( ! label.getText().equals( "" ) ) - label.setText( "" ); + /** + * Called to configure components + */ + protected void configureComponentForContext(JPanel component, JButton iconButton, JLabel label, + JComponent container, Object value, int row, int column, boolean isSelected, boolean hasFocus, + boolean isExpanded, boolean isLeaf) { + if (hasFocus) { + if (container instanceof JTable) { + component.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); + } else { + component.setBorder(noFocusBorder); + } + + if (container instanceof JTree) // was: (false) + { + label.setBorder(treeFocusBorder); + } else { + label.setBorder(noFocusBorder); + } + } else { + label.setBorder(noFocusBorder); + component.setBorder(noFocusBorder); } - else - { - if ( ! label.getText().equals( text ) ) - label.setText( text ); + + if (isSelected) { + if (container instanceof JTree) { + label.setOpaque(true); + label.setForeground(UIManager.getColor("Tree.selectionForeground")); + label.setBackground(UIManager.getColor("Tree.selectionBackground")); + component.setBackground(container.getBackground()); + } else if (container instanceof JTable) { + label.setOpaque(false); + label.setForeground(((JTable) container).getSelectionForeground()); + component.setBackground(((JTable) container).getSelectionBackground()); + } else { + label.setOpaque(false); + label.setForeground(UIManager.getColor("Table.selectionForeground")); + component.setBackground(UIManager.getColor("Table.selectionBackground")); + } + } else { + label.setOpaque(false); + label.setForeground(container.getForeground()); + component.setBackground(container.getBackground()); + } + + label.setFont(container.getFont()); + + Icon icon = getIconForContext(container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf); + iconButton.setIcon(icon); + if (!isClickable()) { + iconButton.setDisabledIcon(icon); + } + + String text = getStringForContext(container, value, row, column, isSelected, hasFocus, isExpanded, isLeaf); + + if ((text == null) || ("".equals(text))) { + if (!label.getText().equals("")) + label.setText(""); + } else { + if (!label.getText().equals(text)) + label.setText(text); } } - -/** -* Override this method to provide an icon for the renderer. -* This default implementation returns null. -* @return An icon to be displayed in the cell, or null to omit the -* icon from the cell. -*/ - public Icon getIconForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - return null; - } -/** -* Override this method to provide a string for the renderer. -* This default implementation returns toString on the value parameter, -* or null if the value is null. -* @return A string to be displayed in the cell. -*/ - public String getStringForContext( - JComponent container, Object value, - int row, int column, - boolean isSelected, boolean hasFocus, - boolean isExpanded, boolean isLeaf ) - { - if ( value == null ) return null; - return value.toString(); - } - - /** - * Adds the specified listener to the list of listeners - * to be notified when the button receives a click. - */ - public void addActionListener( ActionListener aListener ) - { - if ( actionListeners == null ) - { - actionListeners = new Vector( 2 ); + /** + * Override this method to provide an icon for the renderer. This default + * implementation returns null. + * + * @return An icon to be displayed in the cell, or null to omit the icon from + * the cell. + */ + public Icon getIconForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + return null; + } + + /** + * Override this method to provide a string for the renderer. This default + * implementation returns toString on the value parameter, or null if the value + * is null. + * + * @return A string to be displayed in the cell. + */ + public String getStringForContext(JComponent container, Object value, int row, int column, boolean isSelected, + boolean hasFocus, boolean isExpanded, boolean isLeaf) { + if (value == null) + return null; + return value.toString(); + } + + /** + * Adds the specified listener to the list of listeners to be notified when the + * button receives a click. + */ + public void addActionListener(ActionListener aListener) { + if (actionListeners == null) { + actionListeners = new Vector(2); } - actionListeners.add( aListener ); + actionListeners.add(aListener); } - + /** - * Removes the specified listener from the list of listeners - * to be notified when the button receives a click. - */ - public void removeActionListener( ActionListener aListener ) - { - actionListeners.remove( aListener ); + * Removes the specified listener from the list of listeners to be notified when + * the button receives a click. + */ + public void removeActionListener(ActionListener aListener) { + actionListeners.remove(aListener); } - + /** - * Broadcasts the specified action event to all listeners. - */ - protected void fireActionEvent( ActionEvent anActionEvent ) - { - if ( actionListeners == null ) return; + * Broadcasts the specified action event to all listeners. + */ + protected void fireActionEvent(ActionEvent anActionEvent) { + if (actionListeners == null) + return; // vector's enumeration is not fail-fast Enumeration e = actionListeners.elements(); - while ( e.hasMoreElements() ) - { - ((ActionListener)e.nextElement()).actionPerformed( anActionEvent ); + while (e.hasMoreElements()) { + ((ActionListener) e.nextElement()).actionPerformed(anActionEvent); } } - + /** - * Returns the action command broadcast when this icon - * receives a click. Defaults to CLICKED. - */ - public String getActionCommand() - { + * Returns the action command broadcast when this icon receives a click. + * Defaults to CLICKED. + */ + public String getActionCommand() { return actionCommand; } /** - * Sets the action command broadcast when this table - * receives a double click. - */ - public void setActionCommand( String anActionCommand ) - { - actionCommand = anActionCommand; + * Sets the action command broadcast when this table receives a double click. + */ + public void setActionCommand(String anActionCommand) { + actionCommand = anActionCommand; } - + // interface CellEditor /** - * Returns lastKnownValue, although this should not be called. - */ - public Object getCellEditorValue() - { + * Returns lastKnownValue, although this should not be called. + */ + public Object getCellEditorValue() { return lastKnownValue; } - + /** - * Returns true. - */ - public boolean isCellEditable(EventObject anEvent) - { - return true; + * Returns true. + */ + public boolean isCellEditable(EventObject anEvent) { + return true; } - + /** - * Returns true. - */ - public boolean shouldSelectCell(EventObject anEvent) - { + * Returns true. + */ + public boolean shouldSelectCell(EventObject anEvent) { return true; } - + /** - * Fires an editing stopped event and returns true. - */ - public boolean stopCellEditing() - { - ChangeEvent event = new ChangeEvent( this ); - if ( cellEditorListeners != null ) - { + * Fires an editing stopped event and returns true. + */ + public boolean stopCellEditing() { + ChangeEvent event = new ChangeEvent(this); + if (cellEditorListeners != null) { // vector's enumeration is not fail-fast Enumeration e = cellEditorListeners.elements(); - while ( e.hasMoreElements() ) - { - // broadcast editing cancelled since no value is edited - ((CellEditorListener)e.nextElement()).editingCanceled( event ); + while (e.hasMoreElements()) { + // broadcast editing cancelled since no value is edited + ((CellEditorListener) e.nextElement()).editingCanceled(event); } } - lastKnownComponent = null; + lastKnownComponent = null; return true; } - - /** - * Fires an editing cancelled event and returns true. - */ - public void cancelCellEditing() - { - //HACK: cancelCellEditing() causes for the dragGesture - //to be NOT recognized AT ALL since on the next MOUSE_PRESSED - //the cell editor first needs to startEditing() [if in the tree - //the CellEditorListener is a BasicTreeUI class] - //(before the drag gesture event can be recognized). - //Also the lastKnownComponent should not be set to null, - //none of the mouse events won't dispathced to the lastKnownComponent - //in that case. - - //Not calling it at all does seem to fix it, but what are the - //consequences??? - //Trying to workaround this might solve it, but it introduces having - //an extra listener (a MouseMotionListnener), which might be wasteful - //(i.e. only if a Mouse_dragged event has been initiated, but DragGesture - //hasn't been recognized, postpone calling this till finish the DnD event) - //But what if do DnD and not exited ??? The mouseExited() is not called - //anyway until the DnD event is done. - - ChangeEvent event = new ChangeEvent( this ); - if ( cellEditorListeners == null ) return; + + /** + * Fires an editing cancelled event and returns true. + */ + public void cancelCellEditing() { + // HACK: cancelCellEditing() causes for the dragGesture + // to be NOT recognized AT ALL since on the next MOUSE_PRESSED + // the cell editor first needs to startEditing() [if in the tree + // the CellEditorListener is a BasicTreeUI class] + // (before the drag gesture event can be recognized). + // Also the lastKnownComponent should not be set to null, + // none of the mouse events won't dispathced to the lastKnownComponent + // in that case. + + // Not calling it at all does seem to fix it, but what are the + // consequences??? + // Trying to workaround this might solve it, but it introduces having + // an extra listener (a MouseMotionListnener), which might be wasteful + // (i.e. only if a Mouse_dragged event has been initiated, but DragGesture + // hasn't been recognized, postpone calling this till finish the DnD event) + // But what if do DnD and not exited ??? The mouseExited() is not called + // anyway until the DnD event is done. + + ChangeEvent event = new ChangeEvent(this); + if (cellEditorListeners == null) + return; // vector's enumeration is not fail-fast Enumeration e = cellEditorListeners.elements(); - - while ( e.hasMoreElements() ) - { - ((CellEditorListener)e.nextElement()).editingCanceled( event ); - } - - //DO not nullify this - lastKnownComponent = null; - } - - /** - * Adds the specified listener to the list of listeners - * to be notified when the table receives a double click. - */ - public void addCellEditorListener( CellEditorListener aListener ) - { - if ( cellEditorListeners == null ) - { - cellEditorListeners = new Vector( 2 ); + + while (e.hasMoreElements()) { + ((CellEditorListener) e.nextElement()).editingCanceled(event); + } + + // DO not nullify this + lastKnownComponent = null; + } + + /** + * Adds the specified listener to the list of listeners to be notified when the + * table receives a double click. + */ + public void addCellEditorListener(CellEditorListener aListener) { + if (cellEditorListeners == null) { + cellEditorListeners = new Vector(2); } - cellEditorListeners.add( aListener ); + cellEditorListeners.add(aListener); } - + /** - * Removes the specified listener from the list of listeners - * to be notified when the table receives a double click. - */ - public void removeCellEditorListener( CellEditorListener aListener ) - { - cellEditorListeners.remove( aListener ); + * Removes the specified listener from the list of listeners to be notified when + * the table receives a double click. + */ + public void removeCellEditorListener(CellEditorListener aListener) { + cellEditorListeners.remove(aListener); } - + // interface ActionListener - - /** - * Puts ourself on the end of the event queue for - * firing our action event to all listeners. - */ - public void actionPerformed( ActionEvent evt ) - { - //commented out in order NOT to set lastKnownComponent to null, since - //if this object is inside a table or tree, relying on getCellEditorValue() - //to return the currently edited object - //cancelCellEditing(); - - SwingUtilities.invokeLater( this ); - } - + + /** + * Puts ourself on the end of the event queue for firing our action event to all + * listeners. + */ + public void actionPerformed(ActionEvent evt) { + // commented out in order NOT to set lastKnownComponent to null, since + // if this object is inside a table or tree, relying on getCellEditorValue() + // to return the currently edited object + // cancelCellEditing(); + + SwingUtilities.invokeLater(this); + } + // interface Runnable - + /** - * Fires the action event to all listeners. - * This is triggered by a click on the icon. - */ - public void run() - { - fireActionEvent( new ActionEvent( this, 0, getActionCommand() ) ); + * Fires the action event to all listeners. This is triggered by a click on the + * icon. + */ + public void run() { + fireActionEvent(new ActionEvent(this, 0, getActionCommand())); } // interface MouseListener - - /** - * Passes through editor mouse clicks to last known component. - * (left click only) - */ - public void mouseClicked(MouseEvent e) - { - if(lastKnownComponent != null){ - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - - } - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - - /** - * Passes through editor right-mouse (popup trigger) mouse events to last known component. - * Needed for possible displaying of popup menus on right click - */ - public void mousePressed(MouseEvent e) - { - if ( e.isPopupTrigger() ) - { - if(lastKnownComponent != null) - { - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - } - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - } - - /** - * Does nothing. - */ - public void mouseReleased(MouseEvent e) - { - if ( e.isPopupTrigger() ) - { - if(lastKnownComponent != null){ - - Object source = e.getSource(); - if(source != null) - { - if(source == editorPanel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorPanel, e, lastKnownComponent ) ); - } - - else if(source == editorLabel) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorLabel, e, lastKnownComponent ) ); - } - - else if(source == editorButton) - { - lastKnownComponent.dispatchEvent( - SwingUtilities.convertMouseEvent( - editorButton, e, lastKnownComponent ) ); - } - } - } - } - } - - /** - * Does nothing. - */ - public void mouseEntered(MouseEvent e) - { - } - - /** - * Cancels cell editing. - */ - public void mouseExited(MouseEvent e) - { - Object source = e.getSource(); - if(source != null && source instanceof JComponent){ - //need to convert the Point from the source's coordinate system to editorPanel's coordinate system. - //(note that simple editorPanel.contains(e.getPoint()) fails if source is editorButton) - - Point convertedPoint = SwingUtilities.convertPoint((JComponent) source, e.getPoint(), editorPanel); - - //check if exited from editorButton, but still inside the editorPanel (works for editorLabel as well) - if(!editorPanel.contains(convertedPoint)){ - - //This was getting called before, but it interfers with the DnD operation - cancelCellEditing(); - } - } - } - - /* This might be redundant - public void cleanUp(){ - - //since cancelCellEditing() was never called call it now - cancelCellEditing(); - stopCellEditing(); - - editorButton.removeActionListener( this ); - editorPanel.removeMouseListener( this ); - editorLabel.removeMouseListener( this ); - editorButton.removeMouseListener( this ); - lastKnownComponent = null; - lastKnownValue = null; - } - */ + + /** + * Passes through editor mouse clicks to last known component. (left click only) + */ + public void mouseClicked(MouseEvent e) { + if (lastKnownComponent != null) { + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + + } else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + + /** + * Passes through editor right-mouse (popup trigger) mouse events to last known + * component. Needed for possible displaying of popup menus on right click + */ + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + if (lastKnownComponent != null) { + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + } else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + } + + /** + * Does nothing. + */ + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + if (lastKnownComponent != null) { + + Object source = e.getSource(); + if (source != null) { + if (source == editorPanel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorPanel, e, lastKnownComponent)); + } + + else if (source == editorLabel) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorLabel, e, lastKnownComponent)); + } + + else if (source == editorButton) { + lastKnownComponent + .dispatchEvent(SwingUtilities.convertMouseEvent(editorButton, e, lastKnownComponent)); + } + } + } + } + } + + /** + * Does nothing. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Cancels cell editing. + */ + public void mouseExited(MouseEvent e) { + Object source = e.getSource(); + if (source != null && source instanceof JComponent) { + // need to convert the Point from the source's coordinate system to + // editorPanel's coordinate system. + // (note that simple editorPanel.contains(e.getPoint()) fails if source is + // editorButton) + + Point convertedPoint = SwingUtilities.convertPoint((JComponent) source, e.getPoint(), editorPanel); + + // check if exited from editorButton, but still inside the editorPanel (works + // for editorLabel as well) + if (!editorPanel.contains(convertedPoint)) { + + // This was getting called before, but it interfers with the DnD operation + cancelCellEditing(); + } + } + } + + /* + * This might be redundant public void cleanUp(){ + * + * //since cancelCellEditing() was never called call it now cancelCellEditing(); + * stopCellEditing(); + * + * editorButton.removeActionListener( this ); editorPanel.removeMouseListener( + * this ); editorLabel.removeMouseListener( this ); + * editorButton.removeMouseListener( this ); lastKnownComponent = null; + * lastKnownValue = null; } + */ } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java index cdaa218..4fa8e04 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/ImagePanel.java @@ -26,79 +26,58 @@ import java.awt.image.ImageObserver; import javax.swing.JPanel; /** -* A JPanel that renders an image, tiling as necessary to -* fill the panel. -* The preferred size of the panel is the size of the image -* and will change until the image is fully loaded, so using -* a media tracker is recommended. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ -public class ImagePanel extends JPanel implements ImageObserver -{ + * A JPanel that renders an image, tiling as necessary to fill the panel. The + * preferred size of the panel is the size of the image and will change until + * the image is fully loaded, so using a media tracker is recommended. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ +public class ImagePanel extends JPanel implements ImageObserver { protected Image image; protected int imageWidth, imageHeight; - - public ImagePanel() - { - this( null ); - } - - public ImagePanel( Image anImage ) - { - image = anImage; - if ( anImage != null ) - { - prepareImage( image, this ); + + public ImagePanel() { + this(null); + } + + public ImagePanel(Image anImage) { + image = anImage; + if (anImage != null) { + prepareImage(image, this); // these may return -1 - imageWidth = image.getWidth( this ); - imageHeight = image.getHeight( this ); - } - else - { + imageWidth = image.getWidth(this); + imageHeight = image.getHeight(this); + } else { imageWidth = 0; imageHeight = 0; } } - - protected void paintComponent(Graphics g) - { - if ( ( image != null ) && ( imageWidth > 0 ) && ( imageHeight > 0 ) ) - { + + protected void paintComponent(Graphics g) { + if ((image != null) && (imageWidth > 0) && (imageHeight > 0)) { int width = getWidth(); int height = getHeight(); - - for ( int x = 0; x < width; x += imageWidth ) - { - for ( int y = 0; y < height; y += imageHeight ) - { - g.drawImage( image, x, y, - imageWidth, imageHeight, - getBackground(), this ); + + for (int x = 0; x < width; x += imageWidth) { + for (int y = 0; y < height; y += imageHeight) { + g.drawImage(image, x, y, imageWidth, imageHeight, getBackground(), this); } } } } - - public boolean imageUpdate(Image img, - int infoflags, - int x, - int y, - int width, - int height) - { + + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { imageWidth = width; imageHeight = height; - setPreferredSize( new Dimension( width, height ) ); - revalidate(); + setPreferredSize(new Dimension(width, height)); + revalidate(); repaint(); - - if ( ( infoflags & ImageObserver.ALLBITS ) == ImageObserver.ALLBITS ) - { - return false; + + if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) { + return false; } return true; } - + } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java index 55c1e36..a2c0182 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/InfoPanel.java @@ -49,1645 +49,1521 @@ import javax.swing.JTextField; import javax.swing.SwingConstants; /** -* InfoPanel uses labels and textfields (or any other component - see below) -* to display a list of keys and values in a well-aligned and consistent manner, -* conforming to alignment and pixel spacing in the java look and feel -* design guidelines. -*

-* -* Each key is displayed in a label to the left of the component that contains -* the corresponding value. Each row is displayed starting at the top of the -* component's available area. Each row's height is the maximum preferred -* height of its components and the field itself gets as much of the width as -* it can, dependent on the length of the longest label.

-* -* The values in the fields can be editable, and the -* current value can be retrieved using the key - for this reason, unique keys -* are recommended.

-* -* As a convenience, push buttons may be placed across the -* bottom of the panel in a manner similar to ButtonPanel.

-* -* The panel forwards any ActionEvents generated by the components and -* buttons on it to all registered listeners.

-* -* Optionally, any component can be used instead of a textfield. -* However, get/setValueForKey() and get/setEditable() -* may not work for those components. Use getComponentForKey() to -* access them instead. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class InfoPanel extends JPanel implements ActionListener -{ -/** -* Special label for an empty pair - a label and component -* that take up space but are hidden from view. This might -* be useful for achieving certain layouts. -*/ - public static final String HIDDEN = "(hidden)"; - - /** Cache for the introspectComponent method */ - private static Map _method_cache = - Collections.synchronizedMap( new HashMap(30) ); - - protected Container listContainer = null; - protected int hgap; // set in constructor - protected int vgap; // set in constructor - protected int margin; // set in constructor - protected int columns; // set in constructor - protected List fields = null; - protected List labels = null; - protected List fieldSpacers = null; - protected ButtonPanel buttonPanel = null; - protected boolean isEditable = true; - protected String prefix; - protected String postfix; + * InfoPanel uses labels and textfields (or any other component - see below) to + * display a list of keys and values in a well-aligned and consistent manner, + * conforming to alignment and pixel spacing in the java look and feel + * design + * guidelines.
+ *
+ * + * Each key is displayed in a label to the left of the component that contains + * the corresponding value. Each row is displayed starting at the top of the + * component's available area. Each row's height is the maximum preferred height + * of its components and the field itself gets as much of the width as it can, + * dependent on the length of the longest label.
+ *
+ * + * The values in the fields can be editable, and the current value can be + * retrieved using the key - for this reason, unique keys are recommended.
+ *
+ * + * As a convenience, push buttons may be placed across the bottom of the panel + * in a manner similar to ButtonPanel.
+ *
+ * + * The panel forwards any ActionEvents generated by the components and buttons + * on it to all registered listeners.
+ *
+ * + * Optionally, any component can be used instead of a textfield. However, + * get/setValueForKey() and get/setEditable() may not + * work for those components. Use getComponentForKey() to access + * them instead. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class InfoPanel extends JPanel implements ActionListener { + /** + * Special label for an empty pair - a label and component that take up space + * but are hidden from view. This might be useful for achieving certain layouts. + */ + public static final String HIDDEN = "(hidden)"; + + /** Cache for the introspectComponent method */ + private static Map _method_cache = Collections.synchronizedMap(new HashMap(30)); + + protected Container listContainer = null; + protected int hgap; // set in constructor + protected int vgap; // set in constructor + protected int margin; // set in constructor + protected int columns; // set in constructor + protected List fields = null; + protected List labels = null; + protected List fieldSpacers = null; + protected ButtonPanel buttonPanel = null; + protected boolean isEditable = true; + protected String prefix; + protected String postfix; protected int labelAnchor; protected int labelAlign; // protected Component marginStrut = null; - // for action multicasting - protected ActionListener actionListener = null; - -/** -* Constructs an empty InfoPanel. -*/ - public InfoPanel() - { - hgap = 12; // per java l&f guidelines - vgap = 6; // java l&f says 11 - columns = 1; // default columns - margin = 0; // default margin: none - prefix = ""; // default prefix: none - postfix = ":"; // per java l&f guidelines - fields = new ArrayList(); - labels = new ArrayList(); - labelAnchor = GridBagConstraints.NORTHWEST; - // per java l&f guidelines (CENTER is nicer) - labelAlign = SwingConstants.LEFT; - // per java l&f guidelines - - doInitialLayout(); - } + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Constructs an empty InfoPanel. + */ + public InfoPanel() { + hgap = 12; // per java l&f guidelines + vgap = 6; // java l&f says 11 + columns = 1; // default columns + margin = 0; // default margin: none + prefix = ""; // default prefix: none + postfix = ":"; // per java l&f guidelines + fields = new ArrayList(); + labels = new ArrayList(); + labelAnchor = GridBagConstraints.NORTHWEST; + // per java l&f guidelines (CENTER is nicer) + labelAlign = SwingConstants.LEFT; + // per java l&f guidelines + + doInitialLayout(); + } -/** -* Constructs an InfoPanel with the specified labels -* each paired with a blank textfield. -* @param labelArray An Array containing the labels in the -* order in which they should appear from top to bottom. -* A null value produces an empty panel. -*/ - public InfoPanel( String[] labelArray ) - { - this(); - setLabels( labelArray ); - } + /** + * Constructs an InfoPanel with the specified labels each paired with a blank + * textfield. + * + * @param labelArray An Array containing the labels in the order in which they + * should appear from top to bottom. A null value produces an + * empty panel. + */ + public InfoPanel(String[] labelArray) { + this(); + setLabels(labelArray); + } -/** -* Creates a set of labels and empty textfields after first -* clearing all existing components on the panel. -* @param labelArray An Array containing the labels in the order -* in which they should appear from top to bottom. A null -* value will clear the panel. -*/ - public void setLabels( String[] labelArray ) - { - removeAll(); - if ( labelArray == null ) return; // null clears panel - for ( int i = 0; i < labelArray.length; i++ ) - { - addPair( labelArray[i], new JTextField() ); - } - } + /** + * Creates a set of labels and empty textfields after first clearing all + * existing components on the panel. + * + * @param labelArray An Array containing the labels in the order in which they + * should appear from top to bottom. A null value will clear + * the panel. + */ + public void setLabels(String[] labelArray) { + removeAll(); + if (labelArray == null) + return; // null clears panel + for (int i = 0; i < labelArray.length; i++) { + addPair(labelArray[i], new JTextField()); + } + } -/** -* Retrieves the labls for the components on the panel -* in the order in which they are displayed from top WIDTH bottom. -* These are the keys used to reference values or to reference -* the components directly. -* @return An Array of Strings containing the labels. -*/ - public String[] getLabels() - { - int length = fields.size(); - String[] labelArray = new String[ length ]; - for ( int i = 0; i < length; i++ ) - { - labelArray[i] = ((Component)fields.get(i)).getName(); - } - return labelArray; - } - -/** -* Retrieves the constant used to anchor the labels in place. -* The default value is GridBagConstraints.NORTHWEST. -*/ - public int getLabelAnchor() - { + /** + * Retrieves the labls for the components on the panel in the order in which + * they are displayed from top WIDTH bottom. These are the keys used to + * reference values or to reference the components directly. + * + * @return An Array of Strings containing the labels. + */ + public String[] getLabels() { + int length = fields.size(); + String[] labelArray = new String[length]; + for (int i = 0; i < length; i++) { + labelArray[i] = ((Component) fields.get(i)).getName(); + } + return labelArray; + } + + /** + * Retrieves the constant used to anchor the labels in place. The default value + * is GridBagConstraints.NORTHWEST. + */ + public int getLabelAnchor() { return labelAnchor; } -/** -* Sets the constant used to anchor the labels in place -* and reflows the layout. -* @param anAnchorConstant An anchor constant from -* GridBagConstraints. -*/ - public void setLabelAnchor( int anAnchorConstant ) - { - labelAnchor = anAnchorConstant; + /** + * Sets the constant used to anchor the labels in place and reflows the layout. + * + * @param anAnchorConstant An anchor constant from GridBagConstraints. + */ + public void setLabelAnchor(int anAnchorConstant) { + labelAnchor = anAnchorConstant; updateLabels(); } -/** -* Retrieves the constant used to align the labels in place. -* The default value is GridBagConstraints.CENTER. -*/ - public int getLabelAlignment() - { + /** + * Retrieves the constant used to align the labels in place. The default value + * is GridBagConstraints.CENTER. + */ + public int getLabelAlignment() { return labelAlign; } -/** -* Sets the constant used to align the labels in place -* and reflows the layout. -* @param anAlignmentConstant LEFT, CENTER, or RIGHT constants -* from SwingUtilities. -*/ - public void setLabelAlignment( int anAlignmentConstant ) - { - labelAlign = anAlignmentConstant; + /** + * Sets the constant used to align the labels in place and reflows the layout. + * + * @param anAlignmentConstant LEFT, CENTER, or RIGHT constants from + * SwingUtilities. + */ + public void setLabelAlignment(int anAlignmentConstant) { + labelAlign = anAlignmentConstant; updateLabels(); } - -/** -* Factory method for creating panel spacers. -* This implementation returns a JPanel with -* opaque set to false. Override to customize. -*/ - public JPanel createPanel() - { - JPanel result = new JPanel(); - result.setOpaque( false ); - return result; - } -/** -* This method is responsible for the initial layout of the panel. -* All labels and textfields will later added to listContainer. -* This method is responsible for initializing listContainer. -*/ - protected void doInitialLayout() - { - listContainer = createPanel(); - listContainer.setLayout( new BetterGridBagLayout() ); - this.setLayout( new BorderLayout() ); - this.add( listContainer, BorderLayout.NORTH ); + /** + * Factory method for creating panel spacers. This implementation returns a + * JPanel with opaque set to false. Override to customize. + */ + public JPanel createPanel() { + JPanel result = new JPanel(); + result.setOpaque(false); + return result; + } - //listContainer.setBackground( Color.blue ); // useful for testing - //this.setBackground( Color.red ); - } + /** + * This method is responsible for the initial layout of the panel. All labels + * and textfields will later added to listContainer. This method is responsible + * for initializing listContainer. + */ + protected void doInitialLayout() { + listContainer = createPanel(); + listContainer.setLayout(new BetterGridBagLayout()); + this.setLayout(new BorderLayout()); + this.add(listContainer, BorderLayout.NORTH); + + // listContainer.setBackground( Color.blue ); // useful for testing + // this.setBackground( Color.red ); + } -/** -* Changes the horizontal spacing between the label and the components in the panel. -* Note: Assumes listContainer uses a GridBagLayout. -* @param newHgap the new spacing, in pixels. May not be negative. -*/ - public void setHgap( int newHgap ) - { - if ( newHgap < 0 ) return; // may not be negative - this.hgap = newHgap; - updateGaps(); - this.revalidate(); - this.repaint(); + /** + * Changes the horizontal spacing between the label and the components in the + * panel. Note: Assumes listContainer uses a GridBagLayout. + * + * @param newHgap the new spacing, in pixels. May not be negative. + */ + public void setHgap(int newHgap) { + if (newHgap < 0) + return; // may not be negative + this.hgap = newHgap; + updateGaps(); + this.revalidate(); + this.repaint(); - } + } -/** -* Gets the current horizontal spacing between components. -* @return the current horizontal spacing, in pixels. -*/ - public int getHgap() - { - return this.hgap; - } + /** + * Gets the current horizontal spacing between components. + * + * @return the current horizontal spacing, in pixels. + */ + public int getHgap() { + return this.hgap; + } -/** -* Changes the vertical spacing between components in the panel. -* Note: Assumes listContainer uses a GridBagLayout. -* @param newVgap the new spacing, in pixels. May not be negative. -*/ - public void setVgap( int newVgap ) - { - if ( newVgap < 0 ) return; // may not be negative - this.vgap = newVgap; - updateGaps(); - this.revalidate(); - this.repaint(); + /** + * Changes the vertical spacing between components in the panel. Note: Assumes + * listContainer uses a GridBagLayout. + * + * @param newVgap the new spacing, in pixels. May not be negative. + */ + public void setVgap(int newVgap) { + if (newVgap < 0) + return; // may not be negative + this.vgap = newVgap; + updateGaps(); + this.revalidate(); + this.repaint(); - } + } -/** -* Gets the current vertical spacing between components. -* @return the current vertical spacing, in pixels. -*/ - public int getVgap() - { - return this.vgap; - } + /** + * Gets the current vertical spacing between components. + * + * @return the current vertical spacing, in pixels. + */ + public int getVgap() { + return this.vgap; + } -/** -* Sets the minimum width for the labels column. -* This left margin will grow if one of the labels -* is wider than this value. -* Note: assumes GridBagLayout. -* @param newMargin the new minimum margin in pixels. May not be negative. -*/ - public void setMargin( int newMargin ) - { - if ( newMargin < 0 ) return; // may not be negative - this.margin = newMargin; - - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints constraints = null; - Component c = null; - int count = listContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = listContainer.getComponent( i ); - constraints = gridBag.getConstraints( c ); - if ( constraints.gridy == 0 && constraints.gridx % 2 == 0 ) - { // if this is a label spacer - // replace it with an appropriately sized box - listContainer.remove( c ); - listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); - } - } - } - - this.revalidate(); - this.repaint(); - - } + /** + * Sets the minimum width for the labels column. This left margin will grow if + * one of the labels is wider than this value. Note: assumes GridBagLayout. + * + * @param newMargin the new minimum margin in pixels. May not be negative. + */ + public void setMargin(int newMargin) { + if (newMargin < 0) + return; // may not be negative + this.margin = newMargin; + + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints constraints = null; + Component c = null; + int count = listContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = listContainer.getComponent(i); + constraints = gridBag.getConstraints(c); + if (constraints.gridy == 0 && constraints.gridx % 2 == 0) { // if this is a label spacer + // replace it with an appropriately sized + // box + listContainer.remove(c); + listContainer.add(Box.createHorizontalStrut(this.margin), constraints); + } + } + } -/** -* Gets the current minimum margin for the labels column. -* @return the current minimum margin in pixels. -*/ - public int getMargin() - { - return this.margin; - } + this.revalidate(); + this.repaint(); -/** -* Sets the number of columns for the panel. -* Label/Component pairs will start from the top left -* and fill in to the right before wrapping to the -* next row. The default number of columns is one. -* Note: assumes GridBagLayout. -* @param newColumns the new number of columns. May not be less than one. -*/ - public void setColumns( int newColumns ) - { - if ( newColumns < 1 ) return; // may not be less than one. - int oldColumns = this.columns; - this.columns = newColumns; - - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - int count = listContainer.getComponentCount(); - Component[] components = listContainer.getComponents(); - GridBagConstraints[] constraints = new GridBagConstraints[ components.length ]; - for ( int i = 0; i < components.length; i++ ) - { - constraints[i] = gridBag.getConstraints( components[i] ); - } - listContainer.removeAll(); - for ( int i = 0; i < components.length; i++ ) - { - if ( constraints[i].gridy != 0 ) - { // ignore first row which is reserved for spacers. - - // translate component to new position - // (columns*2 accounts for two grid columns for one "actual" column) - int index = ( constraints[i].gridy - 1 ) * oldColumns*2 + constraints[i].gridx; - constraints[i].gridy = ( index / (newColumns*2) ) + 1; - constraints[i].gridx = index % (newColumns*2) ; - listContainer.add( components[i], constraints[i] ); - } - } - createSpacers(); // replace the spacers - updateGaps(); - } - - this.revalidate(); - this.repaint(); - - } + } -/** -* Sets the vertical weight used for determining how to distribute additional -* vertical space in the component. -* @param aComponent Key that exists in the layout. -* @return weighty The weight of the component, or -1.0 if not found. -*/ - public double getVerticalWeightForKey( String key ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return -1.0; - if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return -1.0; - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints gbc = layout.getConstraints( c ); - return gbc.weighty; - } - -/** -* Sets the vertical weight used for determining how to distribute additional -* vertical space in the component. By default, all weights are zero, so each -* component gets its preferred height. If any weights are specified, then -* additional space is allocated to those components proportionately. -* @param aComponent Key that exists in the layout. -* @param weighty The new weight. -*/ - public void setVerticalWeightForKey( String key, double weighty ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return; - if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return; - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints gbc = layout.getConstraints( c ); - gbc.weighty = weighty; - layout.setConstraints( c, gbc ); - // handle adding on-the-fly - updateGaps(); - this.revalidate(); - this.repaint(); - } - -/** -* Gets the current number of columns. -* @return the current number of columns. -*/ - public int getColumns() - { - return this.columns; - } + /** + * Gets the current minimum margin for the labels column. + * + * @return the current minimum margin in pixels. + */ + public int getMargin() { + return this.margin; + } + + /** + * Sets the number of columns for the panel. Label/Component pairs will start + * from the top left and fill in to the right before wrapping to the next row. + * The default number of columns is one. Note: assumes GridBagLayout. + * + * @param newColumns the new number of columns. May not be less than one. + */ + public void setColumns(int newColumns) { + if (newColumns < 1) + return; // may not be less than one. + int oldColumns = this.columns; + this.columns = newColumns; + + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + int count = listContainer.getComponentCount(); + Component[] components = listContainer.getComponents(); + GridBagConstraints[] constraints = new GridBagConstraints[components.length]; + for (int i = 0; i < components.length; i++) { + constraints[i] = gridBag.getConstraints(components[i]); + } + listContainer.removeAll(); + for (int i = 0; i < components.length; i++) { + if (constraints[i].gridy != 0) { // ignore first row which is reserved for spacers. + + // translate component to new position + // (columns*2 accounts for two grid columns for one "actual" column) + int index = (constraints[i].gridy - 1) * oldColumns * 2 + constraints[i].gridx; + constraints[i].gridy = (index / (newColumns * 2)) + 1; + constraints[i].gridx = index % (newColumns * 2); + listContainer.add(components[i], constraints[i]); + } + } + createSpacers(); // replace the spacers + updateGaps(); + } + + this.revalidate(); + this.repaint(); -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a blank JPanel will be used. -*/ - public void addPair( String key, Component component ) - { - addRow( key, new Component[] { component } ); } - -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a blank JPanel will appear. -*/ - public void addRow( String key, Component component ) - { - addRow( key, new Component[] { component } ); + + /** + * Sets the vertical weight used for determining how to distribute additional + * vertical space in the component. + * + * @param aComponent Key that exists in the layout. + * @return weighty The weight of the component, or -1.0 if not found. + */ + public double getVerticalWeightForKey(String key) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return -1.0; + if (!(listContainer.getLayout() instanceof GridBagLayout)) + return -1.0; + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints gbc = layout.getConstraints(c); + return gbc.weighty; } - -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param components An array of components that will be placed next to the label. -* Any nulls in the list will be replaced with blank JPanels. -*/ - public void addRow( - String key, Component[] components ) - { - addCompositeComponent( key, makeCompositeComponent( key, components ) ); + + /** + * Sets the vertical weight used for determining how to distribute additional + * vertical space in the component. By default, all weights are zero, so each + * component gets its preferred height. If any weights are specified, then + * additional space is allocated to those components proportionately. + * + * @param aComponent Key that exists in the layout. + * @param weighty The new weight. + */ + public void setVerticalWeightForKey(String key, double weighty) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return; + if (!(listContainer.getLayout() instanceof GridBagLayout)) + return; + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints gbc = layout.getConstraints(c); + gbc.weighty = weighty; + layout.setConstraints(c, gbc); + // handle adding on-the-fly + updateGaps(); + this.revalidate(); + this.repaint(); } -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the components - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param west A component that will appear to the left of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -* @param center A component that will appear between the other components, -* taking up available space. -* A null will be replaced with a blank JPanel. -* @param east A component that will appear to the right of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -*/ - public void addRow( - String key, Component west, Component center, Component east ) - { - addCompositeComponent( key, - makeCompositeComponent( key, - west, center, east ) ); + /** + * Gets the current number of columns. + * + * @return the current number of columns. + */ + public int getColumns() { + return this.columns; } -/** -* Appends a label containing a key and the specified components -* to the bottom of the panel. Any registered action listeners -* will receive action events from the components - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param west A component that will appear to the left of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -* @param north A component that will appear above all the other components, -* as tall as its preferred height and as wide as the info panel itself. -* @param center A component that will appear between the other components, -* taking up available space. A null will be replaced with a blank JPanel. -* @param south A component that will appear below all the other components, -* as tall as its preferred height and as wide as the info panel itself. -* @param east A component that will appear to the right of the other components, -* as wide as its preferred width and as tall as the tallest of the other components. -* A null will be replaced with a blank JPanel. -*/ - public void addRow( - String key, Component west, Component north, - Component center, Component south, Component east ) - { - addCompositeComponent( key, - makeCompositeComponent( key, - west, north, center, south, east ) ); + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a blank JPanel will be used. + */ + public void addPair(String key, Component component) { + addRow(key, new Component[] { component }); } -/** -* Produces a container that contains the specified components, -* using GridLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component[] components ) - { + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a blank JPanel will appear. + */ + public void addRow(String key, Component component) { + addRow(key, new Component[] { component }); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param components An array of components that will be placed next to the + * label. Any nulls in the list will be replaced with blank + * JPanels. + */ + public void addRow(String key, Component[] components) { + addCompositeComponent(key, makeCompositeComponent(key, components)); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the components - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param west A component that will appear to the left of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + * @param center A component that will appear between the other components, + * taking up available space. A null will be replaced with a blank + * JPanel. + * @param east A component that will appear to the right of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + */ + public void addRow(String key, Component west, Component center, Component east) { + addCompositeComponent(key, makeCompositeComponent(key, west, center, east)); + } + + /** + * Appends a label containing a key and the specified components to the bottom + * of the panel. Any registered action listeners will receive action events from + * the components - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param west A component that will appear to the left of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + * @param north A component that will appear above all the other components, as + * tall as its preferred height and as wide as the info panel + * itself. + * @param center A component that will appear between the other components, + * taking up available space. A null will be replaced with a blank + * JPanel. + * @param south A component that will appear below all the other components, as + * tall as its preferred height and as wide as the info panel + * itself. + * @param east A component that will appear to the right of the other + * components, as wide as its preferred width and as tall as the + * tallest of the other components. A null will be replaced with a + * blank JPanel. + */ + public void addRow(String key, Component west, Component north, Component center, Component south, Component east) { + addCompositeComponent(key, makeCompositeComponent(key, west, north, center, south, east)); + } + + /** + * Produces a container that contains the specified components, using + * GridLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component[] components) { JPanel panel = createPanel(); - if ( components.length != 0 ) - { - panel.setLayout( new GridLayout( 1, components.length, hgap, vgap ) ); + if (components.length != 0) { + panel.setLayout(new GridLayout(1, components.length, hgap, vgap)); Component c; - for ( int i = 0; i < components.length; i++ ) - { + for (int i = 0; i < components.length; i++) { c = components[i]; - if ( c != null ) - { - introspectComponent( c, key ); - panel.add( c ); + if (c != null) { + introspectComponent(c, key); + panel.add(c); } } } return panel; } -/** -* Produces a container that contains the specified components, -* using BorderLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component west, Component center, Component east ) - { + /** + * Produces a container that contains the specified components, using + * BorderLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component west, Component center, Component east) { JPanel panel = createPanel(); - panel.setLayout( new BorderLayout( hgap, vgap ) ); + panel.setLayout(new BorderLayout(hgap, vgap)); - if ( west != null ) - { - introspectComponent( west, key ); - panel.add( west, BorderLayout.WEST ); + if (west != null) { + introspectComponent(west, key); + panel.add(west, BorderLayout.WEST); } - - if ( center != null ) - { - introspectComponent( center, key ); - panel.add( center, BorderLayout.CENTER ); + + if (center != null) { + introspectComponent(center, key); + panel.add(center, BorderLayout.CENTER); } - - if ( east != null ) - { - introspectComponent( east, key ); - panel.add( east, BorderLayout.EAST ); + + if (east != null) { + introspectComponent(east, key); + panel.add(east, BorderLayout.EAST); } - + return panel; } -/** -* Produces a container that contains the specified components, -* using BorderLayout. Nulls are ignored. -* This implementation returns a JPanel. -*/ - protected Container makeCompositeComponent( - String key, Component west, Component north, - Component center, Component south, Component east ) - { + /** + * Produces a container that contains the specified components, using + * BorderLayout. Nulls are ignored. This implementation returns a JPanel. + */ + protected Container makeCompositeComponent(String key, Component west, Component north, Component center, + Component south, Component east) { JPanel panel = createPanel(); - panel.setLayout( new BorderLayout( hgap, vgap ) ); + panel.setLayout(new BorderLayout(hgap, vgap)); - if ( west != null ) - { - introspectComponent( west, key ); - panel.add( west, BorderLayout.WEST ); + if (west != null) { + introspectComponent(west, key); + panel.add(west, BorderLayout.WEST); } - - if ( north != null ) - { - introspectComponent( north, key ); - panel.add( north, BorderLayout.WEST ); + + if (north != null) { + introspectComponent(north, key); + panel.add(north, BorderLayout.WEST); } - - if ( center != null ) - { - introspectComponent( center, key ); - panel.add( center, BorderLayout.CENTER ); + + if (center != null) { + introspectComponent(center, key); + panel.add(center, BorderLayout.CENTER); } - - if ( south != null ) - { - introspectComponent( south, key ); - panel.add( south, BorderLayout.CENTER ); + + if (south != null) { + introspectComponent(south, key); + panel.add(south, BorderLayout.CENTER); } - - if ( east != null ) - { - introspectComponent( east, key ); - panel.add( east, BorderLayout.EAST ); + + if (east != null) { + introspectComponent(east, key); + panel.add(east, BorderLayout.EAST); } - + return panel; } -/** -* Override to return a specific component to be used -* as a label. This implementation calls createLabel(). -*/ - protected Component createLabelForKey( String aKey ) - { - return createLabel(); + /** + * Override to return a specific component to be used as a label. This + * implementation calls createLabel(). + */ + protected Component createLabelForKey(String aKey) { + return createLabel(); } -/** -* Provided for backwards compatibility, and called by -* the default implementation of createLabelForKey. -* This implementation returns a JLabel. -*/ - protected JLabel createLabel() - { - return new JLabel(); + /** + * Provided for backwards compatibility, and called by the default + * implementation of createLabelForKey. This implementation returns a JLabel. + */ + protected JLabel createLabel() { + return new JLabel(); } -/** -* Appends a label containing a key and the specified component -* to the bottom of the panel. Any registered action listeners -* will receive action events from the component - the key corresponding -* to the component will be used as the action command. -* @param key A string that will be displayed in a label, preferrably unique. -* @param component A component that will be placed next to the label. -* If null, a stock JTextField will be used. -*/ - protected void addCompositeComponent( String key, Component component ) - { - if ( key == null ) - { - key = ""; - } - Component label = createLabelForKey( key ); - Component field = component; - if ( field == null ) - { - field = new JTextField( 15 ); // default to 15 columns - } - field.setName( key ); // for association and reference - label.setName( key ); // ditto - if ( label instanceof JLabel ) - { - ((JLabel)label).setHorizontalAlignment( labelAlign ); - ((JLabel)label).setLabelFor( field ); // for accessibility - } - if ( "".equals( key ) ) - { - setText( label, "" ); - } - else - { - setText( label, prefix + key + postfix ); - } - field.setEnabled( this.isEditable ); // was: setEditable - - GridBagConstraints gbc = new GridBagConstraints(); - - if ( listContainer.getComponentCount() == 0 ) - { // we've just initialized or called removeAll - createSpacers(); - } - - gbc.gridx = ( fields.size() % this.columns ) * 2; - gbc.gridy = ( fields.size() / this.columns ) + 1; // spacer is at index zero - gbc.weightx = 0.0; - gbc.weighty = 0.0; - gbc.anchor = this.labelAnchor; - gbc.fill = GridBagConstraints.HORIZONTAL; - listContainer.add( label, gbc ); + /** + * Appends a label containing a key and the specified component to the bottom of + * the panel. Any registered action listeners will receive action events from + * the component - the key corresponding to the component will be used as the + * action command. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param component A component that will be placed next to the label. If null, + * a stock JTextField will be used. + */ + protected void addCompositeComponent(String key, Component component) { + if (key == null) { + key = ""; + } + Component label = createLabelForKey(key); + Component field = component; + if (field == null) { + field = new JTextField(15); // default to 15 columns + } + field.setName(key); // for association and reference + label.setName(key); // ditto + if (label instanceof JLabel) { + ((JLabel) label).setHorizontalAlignment(labelAlign); + ((JLabel) label).setLabelFor(field); // for accessibility + } + if ("".equals(key)) { + setText(label, ""); + } else { + setText(label, prefix + key + postfix); + } + field.setEnabled(this.isEditable); // was: setEditable + + GridBagConstraints gbc = new GridBagConstraints(); + + if (listContainer.getComponentCount() == 0) { // we've just initialized or called removeAll + createSpacers(); + } + + gbc.gridx = (fields.size() % this.columns) * 2; + gbc.gridy = (fields.size() / this.columns) + 1; // spacer is at index zero + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = this.labelAnchor; + gbc.fill = GridBagConstraints.HORIZONTAL; + listContainer.add(label, gbc); gbc.fill = GridBagConstraints.BOTH; - gbc.gridx = gbc.gridx + 1; - //FIXME: components default to the labelAnchor - should be different? - gbc.weightx = 1.0; - gbc.weighty = 0.0; - - listContainer.add( field, gbc ); - - if ( key.equals( HIDDEN ) ) - { // these components are not to be shown - setText( label, " " ); - field.setVisible( false ); - } - - fields.add( field ); // using list not map to allow for duplicate keys - labels.add( label ); // ditto - - // handle adding on-the-fly - updateGaps(); - this.revalidate(); - this.repaint(); - } + gbc.gridx = gbc.gridx + 1; + // FIXME: components default to the labelAnchor - should be different? + gbc.weightx = 1.0; + gbc.weighty = 0.0; -/** -* Introspects a component to set the action command and to add the -* InfoPanel to its list of ActionListeners. -* @param aComponent The Component to be introspected. -* @param aKey The action command to be set. -*/ - protected void introspectComponent( Component aComponent, String aKey ) - { - // try to set properties of whatever component this might be - try { - Method [] methods = - (Method []) _method_cache.get( aComponent.getClass() ); - if (methods == null) { - Class componentClass = aComponent.getClass(); - BeanInfo info = - Introspector.getBeanInfo( componentClass ); - - MethodDescriptor[] descriptors = - info.getMethodDescriptors(); - Method setMethod = null; - Method addMethod = null; - for ( int i = 0; - ((setMethod == null || addMethod == null) && - i < descriptors.length); - i++ ) - { - Method m = descriptors[i].getMethod(); - String name = m.getName (); - if ( setMethod == null && - name.equals( "setActionCommand" ) ) - { - setMethod = m; - } - else if ( addMethod == null && - name.equals( "addActionListener" ) ) - { - addMethod = m; - } - } - - methods = new Method [] {setMethod, addMethod}; - _method_cache.put (componentClass, methods); - } - if (methods [0] != null) { - methods [0].invoke( aComponent, new Object[] { aKey } ); - } - if (methods [1] != null) { - methods [1].invoke( aComponent, new Object[] { this } ); - listenedToComponents.add( aComponent ); - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - System.out.println( "InfoPanel.introspectComponent: " + exc ); - } - } - -/** -* Called to populate a label component with the specified text. -* This implementation attempts to call setText(String) on the component. -* Override to customize. -*/ - protected void setText( Component c, String text ) - { - try - { - Method m = c.getClass().getMethod( "setText", new Class[] { String.class } ); - if ( m != null ) - { - m.invoke( c, new Object[] { text } ); - } - } - catch ( Exception exc ) - { - // no such method: ignore - } - } + listContainer.add(field, gbc); -/** -* Creates spacer components on the reserved first grid row -* for each column of labels and fields. -* This allows us to set the margin for those label columns, -* and set the preferred width of the field columns. -* A list containing the field spacers should be assigned to -* the fieldSpacers instance variable. -*/ - private void createSpacers() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - // insert spacers for labels column - GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridy = 0; - constraints.fill = GridBagConstraints.HORIZONTAL; - - fieldSpacers = new LinkedList(); - Component fieldSpacer; - for ( int i = 0; i < this.columns; i++ ) - { - constraints.gridx = i * 2; - listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); - - constraints.gridx = i * 2 + 1; - fieldSpacer = Box.createHorizontalStrut( 0 ); - fieldSpacers.add( fieldSpacer ); - listContainer.add( fieldSpacer, constraints ); - } - } - } + if (key.equals(HIDDEN)) { // these components are not to be shown + setText(label, " "); + field.setVisible(false); + } -/** -* Updates the insets for all components. -*/ - protected void updateGaps() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - Component c = null; - GridBagConstraints gbc = null; - double totalWeightY = 0.0; - int count = listContainer.getComponentCount(); - int i; - for ( i = 0; i < count; i++ ) - { - c = listContainer.getComponent( i ); - gbc = layout.getConstraints( c ); - totalWeightY += gbc.weighty; - if ( (gbc.gridx + 1) % ( this.columns * 2 ) == 0 ) - { // if last component in row - gbc.insets = new Insets( 0, 0, this.vgap, 0 ); - } - else - { - if ( gbc.gridx % 2 == 0 ) - { // is a label column - NOTE: uses eleven pixels before component, per l&f guide - gbc.insets = new Insets( 0, 0, this.vgap, 11 ); - } - else - { // is a component column - if ( gbc.gridy != 0 ) - { - if ( c instanceof JPanel ) ((JPanel)c).setPreferredSize( null ); - gbc.insets = new Insets( 0, 0, this.vgap, this.hgap ); - } - } - } - layout.setConstraints( c, gbc ); - } - - //hack: gridbag clumps components in center if weighty is zero - // if sum of weighty is zero, top-justify the list container - this.remove( listContainer ); - if ( totalWeightY == 0.0 ) - { - this.add( listContainer, BorderLayout.NORTH ); - } - else // put list container in center so it will grow - { - this.add( listContainer, BorderLayout.CENTER ); - } - } - } + fields.add(field); // using list not map to allow for duplicate keys + labels.add(label); // ditto -/** -* Updates the label alignment. -*/ - protected void updateLabels() - { - if ( listContainer.getLayout() instanceof GridBagLayout ) - { - GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); - Component c = null; - GridBagConstraints gbc = null; + // handle adding on-the-fly + updateGaps(); + this.revalidate(); + this.repaint(); + } + + /** + * Introspects a component to set the action command and to add the InfoPanel to + * its list of ActionListeners. + * + * @param aComponent The Component to be introspected. + * @param aKey The action command to be set. + */ + protected void introspectComponent(Component aComponent, String aKey) { + // try to set properties of whatever component this might be + try { + Method[] methods = (Method[]) _method_cache.get(aComponent.getClass()); + if (methods == null) { + Class componentClass = aComponent.getClass(); + BeanInfo info = Introspector.getBeanInfo(componentClass); + + MethodDescriptor[] descriptors = info.getMethodDescriptors(); + Method setMethod = null; + Method addMethod = null; + for (int i = 0; ((setMethod == null || addMethod == null) && i < descriptors.length); i++) { + Method m = descriptors[i].getMethod(); + String name = m.getName(); + if (setMethod == null && name.equals("setActionCommand")) { + setMethod = m; + } else if (addMethod == null && name.equals("addActionListener")) { + addMethod = m; + } + } + + methods = new Method[] { setMethod, addMethod }; + _method_cache.put(componentClass, methods); + } + if (methods[0] != null) { + methods[0].invoke(aComponent, new Object[] { aKey }); + } + if (methods[1] != null) { + methods[1].invoke(aComponent, new Object[] { this }); + listenedToComponents.add(aComponent); + } + } catch (Exception exc) { // error occured while introspecting... move along. + System.out.println("InfoPanel.introspectComponent: " + exc); + } + } + + /** + * Called to populate a label component with the specified text. This + * implementation attempts to call setText(String) on the component. Override to + * customize. + */ + protected void setText(Component c, String text) { + try { + Method m = c.getClass().getMethod("setText", new Class[] { String.class }); + if (m != null) { + m.invoke(c, new Object[] { text }); + } + } catch (Exception exc) { + // no such method: ignore + } + } + + /** + * Creates spacer components on the reserved first grid row for each column of + * labels and fields. This allows us to set the margin for those label columns, + * and set the preferred width of the field columns. A list containing the field + * spacers should be assigned to the fieldSpacers instance variable. + */ + private void createSpacers() { + if (listContainer.getLayout() instanceof GridBagLayout) { + // insert spacers for labels column + GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridy = 0; + constraints.fill = GridBagConstraints.HORIZONTAL; + + fieldSpacers = new LinkedList(); + Component fieldSpacer; + for (int i = 0; i < this.columns; i++) { + constraints.gridx = i * 2; + listContainer.add(Box.createHorizontalStrut(this.margin), constraints); + + constraints.gridx = i * 2 + 1; + fieldSpacer = Box.createHorizontalStrut(0); + fieldSpacers.add(fieldSpacer); + listContainer.add(fieldSpacer, constraints); + } + } + } + + /** + * Updates the insets for all components. + */ + protected void updateGaps() { + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + Component c = null; + GridBagConstraints gbc = null; + double totalWeightY = 0.0; + int count = listContainer.getComponentCount(); + int i; + for (i = 0; i < count; i++) { + c = listContainer.getComponent(i); + gbc = layout.getConstraints(c); + totalWeightY += gbc.weighty; + if ((gbc.gridx + 1) % (this.columns * 2) == 0) { // if last component in row + gbc.insets = new Insets(0, 0, this.vgap, 0); + } else { + if (gbc.gridx % 2 == 0) { // is a label column - NOTE: uses eleven pixels before component, per l&f + // guide + gbc.insets = new Insets(0, 0, this.vgap, 11); + } else { // is a component column + if (gbc.gridy != 0) { + if (c instanceof JPanel) + ((JPanel) c).setPreferredSize(null); + gbc.insets = new Insets(0, 0, this.vgap, this.hgap); + } + } + } + layout.setConstraints(c, gbc); + } + + // hack: gridbag clumps components in center if weighty is zero + // if sum of weighty is zero, top-justify the list container + this.remove(listContainer); + if (totalWeightY == 0.0) { + this.add(listContainer, BorderLayout.NORTH); + } else // put list container in center so it will grow + { + this.add(listContainer, BorderLayout.CENTER); + } + } + } + + /** + * Updates the label alignment. + */ + protected void updateLabels() { + if (listContainer.getLayout() instanceof GridBagLayout) { + GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); + Component c = null; + GridBagConstraints gbc = null; Iterator it = labels.iterator(); - while ( it.hasNext() ) - { - c = (Component) it.next(); - if ( c instanceof JLabel ) - { - ((JLabel)c).setHorizontalAlignment( labelAlign ); - } - gbc = layout.getConstraints( c ); - gbc.anchor = this.labelAnchor; - layout.setConstraints( c, gbc ); - } - } - } + while (it.hasNext()) { + c = (Component) it.next(); + if (c instanceof JLabel) { + ((JLabel) c).setHorizontalAlignment(labelAlign); + } + gbc = layout.getConstraints(c); + gbc.anchor = this.labelAnchor; + layout.setConstraints(c, gbc); + } + } + } -/** -* Convenience method that uses a stock JTextField. -* @param key A string that will be displayed in a label, preferrably unique. -* @param value A string that will be displayed in a textfield. -*/ - public void addPair( String key, String value ) - { - addPair( key, value, null ); - } + /** + * Convenience method that uses a stock JTextField. + * + * @param key A string that will be displayed in a label, preferrably unique. + * @param value A string that will be displayed in a textfield. + */ + public void addPair(String key, String value) { + addPair(key, value, null); + } -/** -* Convenience method that uses the specified JTextField or subclass -* and sets it to the specified value. -* @param key A string that will be displayed in a label, preferrably unique. -* @param value A string that will be displayed in a textfield. -* @param textField A JTextField or subclass that will be used to display the value. -* If null, a stock JTextField will be used. -*/ - public void addPair( String key, String value, JTextField textField ) - { - if ( value == null ) - { - value = ""; - } - JTextField field = textField; - if ( field == null ) - { - field = new JTextField( 15 ); // default to 15 columns - } - else - { - field = textField; - } - field.setText( value ); - - addPair( key, (Component) field ); - } + /** + * Convenience method that uses the specified JTextField or subclass and sets it + * to the specified value. + * + * @param key A string that will be displayed in a label, preferrably + * unique. + * @param value A string that will be displayed in a textfield. + * @param textField A JTextField or subclass that will be used to display the + * value. If null, a stock JTextField will be used. + */ + public void addPair(String key, String value, JTextField textField) { + if (value == null) { + value = ""; + } + JTextField field = textField; + if (field == null) { + field = new JTextField(15); // default to 15 columns + } else { + field = textField; + } + field.setText(value); -/** -* Removes all components from the list. Buttons, if any, -* will remain unchanged - use setButtons( null ) to remove -* them. NOTE: does not call super.removeAll(). -*/ - public void removeAll() - { - Object component; - Method method; - Class[] paramClasses = new Class[] { ActionListener.class }; - Object[] paramObjects = new Object[] { this }; - - Iterator iterator = listenedToComponents.iterator(); - while ( iterator.hasNext() ) - { - component = iterator.next(); - try - { - method = component.getClass().getMethod( "removeActionListener", paramClasses ); - if ( method != null ) - { - method.invoke( component, paramObjects ); - } - } - catch ( Exception exception ) - { - // No removeActionListener() method, move along. - } - } - - listenedToComponents.clear(); - - listContainer.removeAll(); - fields.clear(); - labels.clear(); - this.revalidate(); - this.repaint(); - - //FIXME: It is very confusing that this - // implementation does not call super.removeAll(). - } + addPair(key, (Component) field); + } -/** -* Adds one or buttons to the bottom of the panel with the specified labels -* from left to right. Any action listeners will receive action events -* from clicks on these buttons - the supplied label will be used as the action command. -* @param buttons A string array containing the strings to be used for the button labels -* and action commands. A null value will remove the button panel. -* @see ButtonPanel -*/ - public void setButtons( String[] buttons ) - { - if ( buttonPanel == null ) - { - buttonPanel = new ButtonPanel(); - buttonPanel.setInsets( new Insets( 6, 0, 0, 0 ) ); - // button panel has a 11-pixel top inset - // and java l&f guide says 17-pixels before command buttons - buttonPanel.addActionListener( this ); - this.add( buttonPanel, BorderLayout.SOUTH ); - } - if ( buttons == null ) - { - this.remove( buttonPanel ); - buttonPanel = null; - } - else - { - buttonPanel.setLabels( buttons ); - } - - this.revalidate(); - this.repaint(); - } - protected Collection listenedToComponents = new LinkedList(); + /** + * Removes all components from the list. Buttons, if any, will remain unchanged + * - use setButtons( null ) to remove them. NOTE: does not call + * super.removeAll(). + */ + public void removeAll() { + Object component; + Method method; + Class[] paramClasses = new Class[] { ActionListener.class }; + Object[] paramObjects = new Object[] { this }; + + Iterator iterator = listenedToComponents.iterator(); + while (iterator.hasNext()) { + component = iterator.next(); + try { + method = component.getClass().getMethod("removeActionListener", paramClasses); + if (method != null) { + method.invoke(component, paramObjects); + } + } catch (Exception exception) { + // No removeActionListener() method, move along. + } + } -/** -* Retrieves the names of the buttons that are displayed, if any. -* @return A string array containing the strings used for the button labels -* and action commands, or null if no buttons have been created. -* @see ButtonPanel -*/ - public String[] getButtons() - { - if ( buttonPanel == null ) - { - return null; // none created - } + listenedToComponents.clear(); - return buttonPanel.getLabels(); - } + listContainer.removeAll(); + fields.clear(); + labels.clear(); + this.revalidate(); + this.repaint(); -/** -* Retrieves the actual button panel, if any. -* @return A button panel, or null if none has been created. -* @see ButtonPanel -*/ - public ButtonPanel getButtonPanel() - { - return buttonPanel; - } + // FIXME: It is very confusing that this + // implementation does not call super.removeAll(). + } + /** + * Adds one or buttons to the bottom of the panel with the specified labels from + * left to right. Any action listeners will receive action events from clicks on + * these buttons - the supplied label will be used as the action command. + * + * @param buttons A string array containing the strings to be used for the + * button labels and action commands. A null value will remove + * the button panel. + * @see ButtonPanel + */ + public void setButtons(String[] buttons) { + if (buttonPanel == null) { + buttonPanel = new ButtonPanel(); + buttonPanel.setInsets(new Insets(6, 0, 0, 0)); + // button panel has a 11-pixel top inset + // and java l&f guide says 17-pixels before command buttons + buttonPanel.addActionListener(this); + this.add(buttonPanel, BorderLayout.SOUTH); + } + if (buttons == null) { + this.remove(buttonPanel); + buttonPanel = null; + } else { + buttonPanel.setLabels(buttons); + } -/** -* Sets whether the values displayed in the panel should be editable. Defaults to true. -* @param isEditable Whether the values should be editable. -*/ - public void setEditable( boolean isEditable ) - { - this.isEditable = isEditable; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { - ( (Component) enumeration.next() ).setEnabled( isEditable ); - } - } + this.revalidate(); + this.repaint(); + } -/** -* Gets whether the values displayed in the panel are editable. -* @return Whether the values should be editable. -*/ - public boolean isEditable() - { - return this.isEditable; - } + protected Collection listenedToComponents = new LinkedList(); + + /** + * Retrieves the names of the buttons that are displayed, if any. + * + * @return A string array containing the strings used for the button labels and + * action commands, or null if no buttons have been created. + * @see ButtonPanel + */ + public String[] getButtons() { + if (buttonPanel == null) { + return null; // none created + } -/** -* Sets the field associated with the key to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param key A string representing the key associated with the field. Nulls are converted to an empty string. -* @param value A object to be displayed in the specified field. Nulls are converted to an empty string. -*/ - public void setValueForKey( String key, Object value ) - { - setValueForKey( key, value, 0 ); + return buttonPanel.getLabels(); } - -/** -* Sets the field associated with the key to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param key A string representing the key associated with the field. Nulls are converted to an empty string. -* @param value A object to be displayed in the specified field. Nulls are converted to an empty string. -*/ - public void setValueForKey( String key, Object value, int index ) - { - if ( key == null ) - { - key = ""; - } - - Container field = null; - for ( int i = 0; i < fields.size(); i++ ) - { - field = (Container) fields.get(i); - if ( key.equals( field.getName() ) ) - { - setValueForIndex( index, i, value ); + + /** + * Retrieves the actual button panel, if any. + * + * @return A button panel, or null if none has been created. + * @see ButtonPanel + */ + public ButtonPanel getButtonPanel() { + return buttonPanel; + } + + /** + * Sets whether the values displayed in the panel should be editable. Defaults + * to true. + * + * @param isEditable Whether the values should be editable. + */ + public void setEditable(boolean isEditable) { + this.isEditable = isEditable; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { + ((Component) enumeration.next()).setEnabled(isEditable); + } + } + + /** + * Gets whether the values displayed in the panel are editable. + * + * @return Whether the values should be editable. + */ + public boolean isEditable() { + return this.isEditable; + } + + /** + * Sets the field associated with the key to the specified value. Note: If the + * component does not respond to setText() or setString() or setValue() the + * value will not be set. JTextFields and the like will work. + * + * @param key A string representing the key associated with the field. Nulls + * are converted to an empty string. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForKey(String key, Object value) { + setValueForKey(key, value, 0); + } + + /** + * Sets the field associated with the key to the specified value. Note: If the + * component does not respond to setText() or setString() or setValue() the + * value will not be set. JTextFields and the like will work. + * + * @param key A string representing the key associated with the field. Nulls + * are converted to an empty string. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForKey(String key, Object value, int index) { + if (key == null) { + key = ""; + } + + Container field = null; + for (int i = 0; i < fields.size(); i++) { + field = (Container) fields.get(i); + if (key.equals(field.getName())) { + setValueForIndex(index, i, value); return; - } + } } - // else not found - ignore - } - -/** -* Sets the first field at the specified row index to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param row The row index of the component. -* @param value A object to be displayed in the specified field. -* Nulls are converted to an empty string. -*/ - public void setValueForIndex( int row, Object value ) - { - setValueForIndex( row, 0, value ); + // else not found - ignore } - -/** -* Sets the field at the specified row index and column index to the specified value. -* Note: If the component does not respond to setText() or setString() -* or setValue() the value will not be set. JTextFields and the like will work. -* @param row The row index of the component. -* @param index The column index of the component. -* @param value A object to be displayed in the specified field. -* Nulls are converted to an empty string. -*/ - public void setValueForIndex( int row, int col, Object value ) - { - Container field = (Container) fields.get( row ); - Component c = field.getComponent( col ); - setValueForComponent( c, value ); + + /** + * Sets the first field at the specified row index to the specified value. Note: + * If the component does not respond to setText() or setString() or setValue() + * the value will not be set. JTextFields and the like will work. + * + * @param row The row index of the component. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForIndex(int row, Object value) { + setValueForIndex(row, 0, value); } - + /** + * Sets the field at the specified row index and column index to the specified + * value. Note: If the component does not respond to setText() or setString() or + * setValue() the value will not be set. JTextFields and the like will work. + * + * @param row The row index of the component. + * @param index The column index of the component. + * @param value A object to be displayed in the specified field. Nulls are + * converted to an empty string. + */ + public void setValueForIndex(int row, int col, Object value) { + Container field = (Container) fields.get(row); + Component c = field.getComponent(col); + setValueForComponent(c, value); + } -/** -* Sets the value in the field at the specified index. -* Note: If the component does not respond to setText() or setString() -* or setValue() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @param value A object to be displayed in the specified field. -*/ - protected void setValueForComponent( Component aComponent, Object value ) - { - // try to set a text or string property - try { - BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); - MethodDescriptor[] methods = info.getMethodDescriptors(); - for ( int i = 0; i < methods.length; i++ ) - { - Method m = methods[i].getMethod(); - Class[] paramTypes = m.getParameterTypes(); - if ( paramTypes.length == 1 ) - { - if ( m.getName().equals( "setText" ) ) - { - if ( paramTypes[0].getName().equals( String.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - if ( m.getName().equals( "setString" ) ) - { - if ( paramTypes[0].getName().equals( String.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - if ( m.getName().equals( "setValue" ) ) - { - if ( paramTypes[0].getName().equals( Object.class.getName() ) ) - { - m.invoke( aComponent, new Object[] { value } ); - } - } - } - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - // FIXME: should log error in ErrorManager - System.out.println( "InfoPanel.setValueForComponent: " + exc ); - } - } + /** + * Sets the value in the field at the specified index. Note: If the component + * does not respond to setText() or setString() or setValue() this method will + * return null. JTextFields and the like will work. + * + * @param A valid index. + * @param value A object to be displayed in the specified field. + */ + protected void setValueForComponent(Component aComponent, Object value) { + // try to set a text or string property + try { + BeanInfo info = Introspector.getBeanInfo(aComponent.getClass()); + MethodDescriptor[] methods = info.getMethodDescriptors(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i].getMethod(); + Class[] paramTypes = m.getParameterTypes(); + if (paramTypes.length == 1) { + if (m.getName().equals("setText")) { + if (paramTypes[0].getName().equals(String.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + if (m.getName().equals("setString")) { + if (paramTypes[0].getName().equals(String.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + if (m.getName().equals("setValue")) { + if (paramTypes[0].getName().equals(Object.class.getName())) { + m.invoke(aComponent, new Object[] { value }); + } + } + } + } + } catch (Exception exc) { // error occured while introspecting... move along. + // FIXME: should log error in ErrorManager + System.out.println("InfoPanel.setValueForComponent: " + exc); + } + } -/** -* Gets the value in the field at the specified index. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @return An object representing the value in the field at the specified index, -* or null if the component does not have a text property or if the index is out of bounds. -*/ - public Object getValueForIndex( int anIndex ) - { - return getValueForIndex( anIndex, 0 ); - } + /** + * Gets the value in the field at the specified index. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param A valid index. + * @return An object representing the value in the field at the specified index, + * or null if the component does not have a text property or if the + * index is out of bounds. + */ + public Object getValueForIndex(int anIndex) { + return getValueForIndex(anIndex, 0); + } -/** -* Gets the value in the field at the specified row and column. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param A valid index. -* @return An object representing the value in the field at the specified index, -* or null if the component does not have a text property or if the index is out of bounds. -*/ - public Object getValueForIndex( int row, int col ) - { - if ( ( row >= fields.size() ) || ( row < 0 ) ) - { // out of bounds - return null; - } - - Container field = (Container) fields.get( row ); - Component c = field.getComponent( col ); - return getValueForComponent( c ); - } + /** + * Gets the value in the field at the specified row and column. Note: If the + * component does not respond to getText() or getString() or getSelectedItem() + * this method will return null. JTextFields and the like will work. + * + * @param A valid index. + * @return An object representing the value in the field at the specified index, + * or null if the component does not have a text property or if the + * index is out of bounds. + */ + public Object getValueForIndex(int row, int col) { + if ((row >= fields.size()) || (row < 0)) { // out of bounds + return null; + } -/** -* Gets the value in the field associated with the key. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param key An string representing the key associated with the field. Nulls are converted to an empty string. -* @return An object representing the value in the field associated with the key, -* or null if the key does not exist or if the component does not have a text property. -*/ - public Object getValueForKey( String key ) - { - return getValueForKey( key, 0 ); + Container field = (Container) fields.get(row); + Component c = field.getComponent(col); + return getValueForComponent(c); } -/** -* Gets the value in the field associated with the key. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param key An string representing the key associated with the field. Nulls are converted to an empty string. -* @return An object representing the value in the field associated with the key, -* or null if the key does not exist or if the component does not have a text property. -*/ - public Object getValueForKey( String key, int index ) - { - if ( key == null ) - { - key = ""; - } - - Container field = null; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - field = (Container) enumeration.next(); - if ( key.equals( field.getName() ) ) - { - Component c = field.getComponent( index ); - if ( c != null ) - { - return getValueForComponent( c ); + /** + * Gets the value in the field associated with the key. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param key An string representing the key associated with the field. Nulls + * are converted to an empty string. + * @return An object representing the value in the field associated with the + * key, or null if the key does not exist or if the component does not + * have a text property. + */ + public Object getValueForKey(String key) { + return getValueForKey(key, 0); + } + + /** + * Gets the value in the field associated with the key. Note: If the component + * does not respond to getText() or getString() or getSelectedItem() this method + * will return null. JTextFields and the like will work. + * + * @param key An string representing the key associated with the field. Nulls + * are converted to an empty string. + * @return An object representing the value in the field associated with the + * key, or null if the key does not exist or if the component does not + * have a text property. + */ + public Object getValueForKey(String key, int index) { + if (key == null) { + key = ""; + } + + Container field = null; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + field = (Container) enumeration.next(); + if (key.equals(field.getName())) { + Component c = field.getComponent(index); + if (c != null) { + return getValueForComponent(c); } - } - } - // else not found - return null; - } + } + } + // else not found + return null; + } -/** -* Gets the value in the specified component. -* Note: If the component does not respond to getText() or getString() -* or getSelectedItem() this method will return null. JTextFields and the like will work. -* @param aComponent The specified component. -* @return An object representing the value in the component. -* or null if the component does not have a text property. -*/ - protected Object getValueForComponent( Component aComponent ) - { - // try to get a text or string property - try - { - BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); - MethodDescriptor[] methods = info.getMethodDescriptors(); - for ( int i = 0; i < methods.length; i++ ) - { - Method m = methods[i].getMethod(); - Class[] paramTypes = m.getParameterTypes(); - if ( m.getName().equals( "getText" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - if ( m.getName().equals( "getString" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - if ( m.getName().equals( "getSelectedItem" ) ) - { - if ( paramTypes.length == 0 ) - { - return m.invoke( aComponent, new Object[] {} ); - } - } - // TODO: should also handle variants of setValue() - } - } - catch ( Exception exc ) - { // error occured while introspecting... move along. - System.out.println( "InfoPanel.getValueFromComponent: " + exc ); - } - - // not found - return null; - } + /** + * Gets the value in the specified component. Note: If the component does not + * respond to getText() or getString() or getSelectedItem() this method will + * return null. JTextFields and the like will work. + * + * @param aComponent The specified component. + * @return An object representing the value in the component. or null if the + * component does not have a text property. + */ + protected Object getValueForComponent(Component aComponent) { + // try to get a text or string property + try { + BeanInfo info = Introspector.getBeanInfo(aComponent.getClass()); + MethodDescriptor[] methods = info.getMethodDescriptors(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i].getMethod(); + Class[] paramTypes = m.getParameterTypes(); + if (m.getName().equals("getText")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + if (m.getName().equals("getString")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + if (m.getName().equals("getSelectedItem")) { + if (paramTypes.length == 0) { + return m.invoke(aComponent, new Object[] {}); + } + } + // TODO: should also handle variants of setValue() + } + } catch (Exception exc) { // error occured while introspecting... move along. + System.out.println("InfoPanel.getValueFromComponent: " + exc); + } -/** -* Gets the component associated with the key as a JTextField, for backwards compatibility. -* @param key A string representing the key associated with the component. Nulls are converted to an empty string. -* @return A JTextField that contains the value associated with the key, -* or null if the key does not exist or if the component is not a JTextField. -*/ - public JTextField getFieldForKey( String key ) - { - Component c = getComponentForKey( key ); - if ( c instanceof JTextField ) - { - return (JTextField) c; - } - return null; - } + // not found + return null; + } -/** -* Gets the component associated with the key. If more than one component is associated -* with the key, returns the first such component. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - public Component getComponentForKey( String key ) - { - return getComponentForKey( key, 0 ); + /** + * Gets the component associated with the key as a JTextField, for backwards + * compatibility. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A JTextField that contains the value associated with the key, or null + * if the key does not exist or if the component is not a JTextField. + */ + public JTextField getFieldForKey(String key) { + Component c = getComponentForKey(key); + if (c instanceof JTextField) { + return (JTextField) c; + } + return null; } -/** -* Gets the component associated with the key and index. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - public Component getComponentForKey( String key, int index ) - { - Container c = getCompositeComponentForKey( key ); - if ( c == null ) return null; - return c.getComponent( index ); - } + /** + * Gets the component associated with the key. If more than one component is + * associated with the key, returns the first such component. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + public Component getComponentForKey(String key) { + return getComponentForKey(key, 0); + } -/** -* Gets the component at the specified row. If more than one component exists -* on that row, returns the first such component. -* @return A component or null if the row does not exist. -*/ - public Object getComponentForIndex( int row ) - { - return getComponentForIndex( row, 0 ); - } + /** + * Gets the component associated with the key and index. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + public Component getComponentForKey(String key, int index) { + Container c = getCompositeComponentForKey(key); + if (c == null) + return null; + return c.getComponent(index); + } -/** -* Gets the component at the specified row and column. -* @return A component or null if the index is out of bounds. -*/ - public Object getComponentForIndex( int row, int col ) - { - if ( ( row > fields.size() ) || ( row < 0 ) ) - { // out of bounds - return null; - } - - Container field = (Container) fields.get( row ); - return field.getComponent( col ); - } + /** + * Gets the component at the specified row. If more than one component exists on + * that row, returns the first such component. + * + * @return A component or null if the row does not exist. + */ + public Object getComponentForIndex(int row) { + return getComponentForIndex(row, 0); + } -/** -* Gets the container associated with the key. -* @param key A string representing the key associated with the component. -* Nulls are converted to an empty string. -* @return A component that contains the value associated with the key, -* or null if the key does not exist. -*/ - protected Container getCompositeComponentForKey( String key ) - { - if ( key == null ) - { - key = ""; - } - - JPanel field = null; - Iterator enumeration = fields.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - field = (JPanel) enumeration.next(); - if ( key.equals( field.getName() ) ) - { - return field; - } - } - - // else not found - return null; - } + /** + * Gets the component at the specified row and column. + * + * @return A component or null if the index is out of bounds. + */ + public Object getComponentForIndex(int row, int col) { + if ((row > fields.size()) || (row < 0)) { // out of bounds + return null; + } -/** -* Provided for backwards compatibility: calls getLabelComponentForKey. -* @param key A string representing the key associated with the compoent. -* Nulls are converted to an empty string. -* @return Component label object associated with the key, or null if the key does not exist -* or if the label component is not an instance of JLabel. -*/ - public JLabel getLabelForKey( String key ) - { - Component result = getLabelComponentForKey( key ); - if ( result instanceof JLabel ) return (JLabel) result; - return null; - } - -/** -* Get the label component associated with the key. -* @param key A string representing the key associated with the compoent. -* Nulls are converted to an empty string. -* @return Component label object associated with the key, or null if the key does not exist. -*/ - public Component getLabelComponentForKey( String key ) - { - if ( key == null ) - { - key = ""; - } - - Component label = null; - Iterator enumeration = labels.iterator(); - while ( enumeration.hasNext() ) - { // finds first value in list with specified key - label = (Component) enumeration.next(); - if ( key.equals( label.getName() ) ) - { - return label; - } - } - - // else not found - return null; - } + Container field = (Container) fields.get(row); + return field.getComponent(col); + } -/** -* Replaces the first component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param key A string representing the key to be associated with the component. -* Nulls are converted to an empty string. -* @param c A component to be placed next to the label corresponding to the key. -* Nulls are converted to a JTextField. -*/ - public void setComponentForKey( String key, Component c ) - { - setComponentForKey( key, c, 0 ); + /** + * Gets the container associated with the key. + * + * @param key A string representing the key associated with the component. Nulls + * are converted to an empty string. + * @return A component that contains the value associated with the key, or null + * if the key does not exist. + */ + protected Container getCompositeComponentForKey(String key) { + if (key == null) { + key = ""; + } + + JPanel field = null; + Iterator enumeration = fields.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + field = (JPanel) enumeration.next(); + if (key.equals(field.getName())) { + return field; + } + } + + // else not found + return null; } - -/** -* Replaces the component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param key A string representing the key to be associated with the component. -* Nulls are converted to an empty string. -* @param c A component to be placed next to the label corresponding to the key. -* Nulls are converted to a JTextField. -*/ - public void setComponentForKey( String key, Component c, int index ) - { - if ( c == null ) - { - c = new JTextField( 15 ); - } - if ( key == null ) - { - key = ""; - } - - Container container = this.getCompositeComponentForKey( key ); - Component field = container.getComponent( index ); - Object value = this.getValueForKey( key, index ); - if ( field != null ) - { - container.remove( index ); - container.add( c, index ); - c.setEnabled( this.isEditable ); - introspectComponent( c, key ); - setValueForComponent( c, value ); - } - } -/** -* Replaces the first component in the specified row. Any value in the existing -* component will be copied to the new component. -* @param row A valid index. -* @param c A component to be placed next to the label corresponding to the key. -*/ - public void setComponentForIndex( int row, Component c ) - { - setComponentForIndex( row, 0, c ); + /** + * Provided for backwards compatibility: calls getLabelComponentForKey. + * + * @param key A string representing the key associated with the compoent. Nulls + * are converted to an empty string. + * @return Component label object associated with the key, or null if the key + * does not exist or if the label component is not an instance of + * JLabel. + */ + public JLabel getLabelForKey(String key) { + Component result = getLabelComponentForKey(key); + if (result instanceof JLabel) + return (JLabel) result; + return null; } - -/** -* Replaces the component associated with the key. Any value in the existing -* component will be copied to the new component. -* @param row A valid index. -* @param c A component to be placed next to the label corresponding to the key. -*/ - public void setComponentForIndex( int row, int col, Component c ) - { - setComponentForKey( getLabels()[row], c, col ); - } -/** -* Sets the string that appears before each label's text on the panel. -* @param aString A String to be used as the label prefix. -*/ - public void setLabelPrefix( String aString ) - { - prefix = aString; - setLabels( getLabels() ); // force refresh - } + /** + * Get the label component associated with the key. + * + * @param key A string representing the key associated with the compoent. Nulls + * are converted to an empty string. + * @return Component label object associated with the key, or null if the key + * does not exist. + */ + public Component getLabelComponentForKey(String key) { + if (key == null) { + key = ""; + } -/** -* Gets the string that appears before each label's text on the panel. -* Defaults to "", an empty string. -* @return A String that is currently used as the label prefix. -*/ - public String getLabelPrefix() - { - return prefix; - } + Component label = null; + Iterator enumeration = labels.iterator(); + while (enumeration.hasNext()) { // finds first value in list with specified key + label = (Component) enumeration.next(); + if (key.equals(label.getName())) { + return label; + } + } -/** -* Sets the string that appears after each label's text on the panel. -* Defaults to ": ", a colon followed by a space. -* @param aString A String to be used as the label postfix. -*/ - public void setLabelPostfix( String aString ) - { - postfix = aString; - setLabels( getLabels() ); // force refresh - } + // else not found + return null; + } -/** -* Gets the string that appears after each label's text on the panel. -* @return A String that is currently used as the label postfix. -*/ - public String getLabelPostfix() - { - return postfix; - } + /** + * Replaces the first component associated with the key. Any value in the + * existing component will be copied to the new component. + * + * @param key A string representing the key to be associated with the component. + * Nulls are converted to an empty string. + * @param c A component to be placed next to the label corresponding to the + * key. Nulls are converted to a JTextField. + */ + public void setComponentForKey(String key, Component c) { + setComponentForKey(key, c, 0); + } -/** -* Adds an action listener to the list that will be -* notified by events occurring in the panel. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by events occurring in the panel. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } + /** + * Replaces the component associated with the key. Any value in the existing + * component will be copied to the new component. + * + * @param key A string representing the key to be associated with the component. + * Nulls are converted to an empty string. + * @param c A component to be placed next to the label corresponding to the + * key. Nulls are converted to a JTextField. + */ + public void setComponentForKey(String key, Component c, int index) { + if (c == null) { + c = new JTextField(15); + } + if (key == null) { + key = ""; + } - // interface ActionListener + Container container = this.getCompositeComponentForKey(key); + Component field = container.getComponent(index); + Object value = this.getValueForKey(key, index); + if (field != null) { + container.remove(index); + container.add(c, index); + c.setEnabled(this.isEditable); + introspectComponent(c, key); + setValueForComponent(c, value); + } + } -/** -* Called by buttons on panel and by other components that -* might be set to broadcast events to this listener. -* Simply forwards the action event unchanged. -* @param e An action event to be received. -*/ - public void actionPerformed(ActionEvent e) - { + /** + * Replaces the first component in the specified row. Any value in the existing + * component will be copied to the new component. + * + * @param row A valid index. + * @param c A component to be placed next to the label corresponding to the + * key. + */ + public void setComponentForIndex(int row, Component c) { + setComponentForIndex(row, 0, c); + } + + /** + * Replaces the component associated with the key. Any value in the existing + * component will be copied to the new component. + * + * @param row A valid index. + * @param c A component to be placed next to the label corresponding to the + * key. + */ + public void setComponentForIndex(int row, int col, Component c) { + setComponentForKey(getLabels()[row], c, col); + } + + /** + * Sets the string that appears before each label's text on the panel. + * + * @param aString A String to be used as the label prefix. + */ + public void setLabelPrefix(String aString) { + prefix = aString; + setLabels(getLabels()); // force refresh + } + + /** + * Gets the string that appears before each label's text on the panel. Defaults + * to "", an empty string. + * + * @return A String that is currently used as the label prefix. + */ + public String getLabelPrefix() { + return prefix; + } + + /** + * Sets the string that appears after each label's text on the panel. Defaults + * to ": ", a colon followed by a space. + * + * @param aString A String to be used as the label postfix. + */ + public void setLabelPostfix(String aString) { + postfix = aString; + setLabels(getLabels()); // force refresh + } + + /** + * Gets the string that appears after each label's text on the panel. + * + * @return A String that is currently used as the label postfix. + */ + public String getLabelPostfix() { + return postfix; + } + + /** + * Adds an action listener to the list that will be notified by events occurring + * in the panel. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by events + * occurring in the panel. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } + + // interface ActionListener + + /** + * Called by buttons on panel and by other components that might be set to + * broadcast events to this listener. Simply forwards the action event + * unchanged. + * + * @param e An action event to be received. + */ + public void actionPerformed(ActionEvent e) { // if ( e.getSource() instanceof AbstractButton ) // { - broadcastEvent(e); + broadcastEvent(e); // } - } - - /** - * GridBagLayout allocates weightx only after considering - * the preferred width of the components in a column. - * We'd prefer that preferred width wasn't considered, - * so that the layout worked more like a html-table. - * GridBagLayout is poorly factored for subclassing, - * so this code is going to get a little bit ugly. - * Really, what good is a protected method that returns - * a private class? Would have liked to just override - * getLayoutInfo and be done with it. - */ - private class BetterGridBagLayout extends GridBagLayout - { - public Dimension preferredLayoutSize(Container parent) - { - preprocess(); - return super.preferredLayoutSize( parent ); - } - - public Dimension minimumLayoutSize(Container parent) - { - preprocess(); - return super.minimumLayoutSize( parent ); - } - - - public void layoutContainer(Container parent) - { - preprocess(); - super.layoutContainer( parent ); - } - - protected void preprocess() - { - if ( fieldSpacers == null ) return; - Iterator i; - - // find the field with the widest preferred size - Component c; - int maxWidth = 0; - i = fields.iterator(); - while ( i.hasNext() ) - { - c = (Component) i.next(); - maxWidth = Math.max( maxWidth, - Math.max( c.getPreferredSize().width, c.getMinimumSize().width ) ); - } - - // set each column's spacers to that preferred size - Dimension min = new Dimension( 0, 0 ); - Dimension pref = new Dimension( maxWidth, 0 ); - i = fieldSpacers.iterator(); - while ( i.hasNext() ) - { - ((Box.Filler)i.next()).changeShape( min, pref, pref ); - } - } - } -} + } + + /** + * GridBagLayout allocates weightx only after considering the preferred width of + * the components in a column. We'd prefer that preferred width wasn't + * considered, so that the layout worked more like a html-table. GridBagLayout + * is poorly factored for subclassing, so this code is going to get a little bit + * ugly. Really, what good is a protected method that returns a private class? + * Would have liked to just override getLayoutInfo and be done with it. + */ + private class BetterGridBagLayout extends GridBagLayout { + public Dimension preferredLayoutSize(Container parent) { + preprocess(); + return super.preferredLayoutSize(parent); + } + + public Dimension minimumLayoutSize(Container parent) { + preprocess(); + return super.minimumLayoutSize(parent); + } + + public void layoutContainer(Container parent) { + preprocess(); + super.layoutContainer(parent); + } + + protected void preprocess() { + if (fieldSpacers == null) + return; + Iterator i; + + // find the field with the widest preferred size + Component c; + int maxWidth = 0; + i = fields.iterator(); + while (i.hasNext()) { + c = (Component) i.next(); + maxWidth = Math.max(maxWidth, Math.max(c.getPreferredSize().width, c.getMinimumSize().width)); + } + // set each column's spacers to that preferred size + Dimension min = new Dimension(0, 0); + Dimension pref = new Dimension(maxWidth, 0); + i = fieldSpacers.iterator(); + while (i.hasNext()) { + ((Box.Filler) i.next()).changeShape(min, pref, pref); + } + } + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java index b73c74d..31bdb70 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyDelayTimer.java @@ -28,161 +28,150 @@ import java.awt.event.KeyListener; import javax.swing.Timer; /** -* KeyDelayTimer is a utility that listens for KeyEvents from one -* or more components. After receiving a KeyEvents the timer will -* broadcast an action event if a specified time interval passes without -* a subsequent KeyEvent.

-* -* This utility is useful for implementing any kind of auto-complete -* feature in a user interface. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class KeyDelayTimer implements ActionListener, KeyListener -{ - // delay timer for keypress-sensitve events - protected Timer keyTimer = null; - protected Component lastFieldTouched = null; - protected long timeLastFieldTouched = 0; - protected int interval = 400; // adjust as needed - - // for action multicasting - protected ActionListener actionListener = null; - -/** -* Default constructor. -*/ - public KeyDelayTimer() - { - keyTimer = new Timer( interval, this ); - } - -/** -* Convenience constructor. -* @param listener An action listener to be notified of delay events. -*/ - public KeyDelayTimer( ActionListener listener ) - { - this(); - addActionListener( listener ); - } - -/** -* Returns the last component that generated a KeyEvent. -* @return The component that sent the most recent KeyEvent. -*/ - public Component getComponent() - { - return lastFieldTouched; - } - -/** -* Returns the number of milliseconds before an ActionEvent is generated. -* The default is 400. -* @return The current delay interval in milliseconds. -*/ - public int getInterval() - { - return interval; - } - -/** -* Sets the number of milliseconds before an ActionEvent will be generated -* after a KeyEvent is received. -* @param millis The new delay interval in milliseconds. -*/ - public void setInterval( int millis ) - { - interval = millis; - keyTimer.setDelay( interval / 2 ); - } - - // interface KeyListener - - public void keyTyped(KeyEvent e) - { - } - public void keyPressed(KeyEvent e) - { - } - -/** -* Receives key events from one or more components. -* Records the component and the time this event was received, -* then starts the timer. -* @param e The key event in question. -*/ - public void keyReleased(KeyEvent e) - { // handles keystrokes in the textfields (except ENTER and ESCAPE) - if ( ( Character.isLetterOrDigit( e.getKeyChar() ) ) - || ( e.getKeyCode() == KeyEvent.VK_SPACE ) - || ( e.getKeyCode() == KeyEvent.VK_DELETE ) - || ( e.getKeyCode() == KeyEvent.VK_BACK_SPACE ) ) - { - this.lastFieldTouched = e.getComponent(); - this.timeLastFieldTouched = System.currentTimeMillis(); - this.keyTimer.start(); - return; - } - } - - // interface ActionListener - -/** -* Receives ActionEvents from the internal timer. -* If the interval has passed without another KeyEvent, -* an ActionEvent is broadcast, with the name of this class -* as the ActionCommand, and the internal timer is stopped. -* @param e The action event in question. -*/ - public void actionPerformed(ActionEvent e) - { - if ( e.getSource() == keyTimer ) - { - if ( System.currentTimeMillis() - this.timeLastFieldTouched > interval ) - { - this.keyTimer.stop(); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, this.getClass().getName() ) ); - } - return; - } - } - - // Action Multicast methods - -/** -* Adds an action listener to the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be notified. -*/ - public void addActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.add(actionListener, l); - } -/** -* Removes an action listener from the list that will be -* notified by button events and changes in button state. -* @param l An action listener to be removed. -*/ - public void removeActionListener(ActionListener l) - { - actionListener = AWTEventMulticaster.remove(actionListener, l); - } -/** -* Notifies all registered action listeners of a pending Action Event. -* @param e An action event to be broadcast. -*/ - protected void broadcastEvent(ActionEvent e) - { - if (actionListener != null) - { - actionListener.actionPerformed(e); - } - } + * KeyDelayTimer is a utility that listens for KeyEvents from one or more + * components. After receiving a KeyEvents the timer will broadcast an action + * event if a specified time interval passes without a subsequent KeyEvent.
+ *
+ * + * This utility is useful for implementing any kind of auto-complete feature in + * a user interface. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class KeyDelayTimer implements ActionListener, KeyListener { + // delay timer for keypress-sensitve events + protected Timer keyTimer = null; + protected Component lastFieldTouched = null; + protected long timeLastFieldTouched = 0; + protected int interval = 400; // adjust as needed + + // for action multicasting + protected ActionListener actionListener = null; + + /** + * Default constructor. + */ + public KeyDelayTimer() { + keyTimer = new Timer(interval, this); + } + + /** + * Convenience constructor. + * + * @param listener An action listener to be notified of delay events. + */ + public KeyDelayTimer(ActionListener listener) { + this(); + addActionListener(listener); + } + + /** + * Returns the last component that generated a KeyEvent. + * + * @return The component that sent the most recent KeyEvent. + */ + public Component getComponent() { + return lastFieldTouched; + } + + /** + * Returns the number of milliseconds before an ActionEvent is generated. The + * default is 400. + * + * @return The current delay interval in milliseconds. + */ + public int getInterval() { + return interval; + } + + /** + * Sets the number of milliseconds before an ActionEvent will be generated after + * a KeyEvent is received. + * + * @param millis The new delay interval in milliseconds. + */ + public void setInterval(int millis) { + interval = millis; + keyTimer.setDelay(interval / 2); + } + + // interface KeyListener + + public void keyTyped(KeyEvent e) { + } + + public void keyPressed(KeyEvent e) { + } + + /** + * Receives key events from one or more components. Records the component and + * the time this event was received, then starts the timer. + * + * @param e The key event in question. + */ + public void keyReleased(KeyEvent e) { // handles keystrokes in the textfields (except ENTER and ESCAPE) + if ((Character.isLetterOrDigit(e.getKeyChar())) || (e.getKeyCode() == KeyEvent.VK_SPACE) + || (e.getKeyCode() == KeyEvent.VK_DELETE) || (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)) { + this.lastFieldTouched = e.getComponent(); + this.timeLastFieldTouched = System.currentTimeMillis(); + this.keyTimer.start(); + return; + } + } + + // interface ActionListener + + /** + * Receives ActionEvents from the internal timer. If the interval has passed + * without another KeyEvent, an ActionEvent is broadcast, with the name of this + * class as the ActionCommand, and the internal timer is stopped. + * + * @param e The action event in question. + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == keyTimer) { + if (System.currentTimeMillis() - this.timeLastFieldTouched > interval) { + this.keyTimer.stop(); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, this.getClass().getName())); + } + return; + } + } + + // Action Multicast methods + + /** + * Adds an action listener to the list that will be notified by button events + * and changes in button state. + * + * @param l An action listener to be notified. + */ + public void addActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.add(actionListener, l); + } + + /** + * Removes an action listener from the list that will be notified by button + * events and changes in button state. + * + * @param l An action listener to be removed. + */ + public void removeActionListener(ActionListener l) { + actionListener = AWTEventMulticaster.remove(actionListener, l); + } + + /** + * Notifies all registered action listeners of a pending Action Event. + * + * @param e An action event to be broadcast. + */ + protected void broadcastEvent(ActionEvent e) { + if (actionListener != null) { + actionListener.actionPerformed(e); + } + } } - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java index 95b8a19..f64e607 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/KeyableCellEditor.java @@ -40,311 +40,265 @@ import javax.swing.event.ChangeEvent; import javax.swing.table.TableCellEditor; /** -* A table cell editor customized for keyboard navigation, much like -* working with a spreadsheet. The default cell editor unfortunately -* does none of these things: -*

    -*
  • Selects text on start of editing. -*
  • Up and down keys move edit cell up and down. -*
  • Right and left keys move cell when selection caret is at end of text. -*
  • Escape cancels editing. -*
  • Enter commits edit. -*
  • Edits are properly committed on lost focus. -*
  • Tab and shift-tab work as expected. -*
  • Cell selection moves with the edit cell. -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class KeyableCellEditor implements TableCellEditor, FocusListener, - KeyListener, Serializable -{ - List listeners; - JTextField textField; - Object lastValue; - Format currentFormat; - - JTable table; - -/** -* Default constructor - a standard JTextField will be used for editing. -*/ - public KeyableCellEditor() - { - this( (JTextField) null ); - } - -/** -* Constructor specifying a type of JTextField to be used for editing. -* The JTextField will have its border replaced with a black line border. -* @param aTextField A JTextField or subclass for editing values. -*/ - public KeyableCellEditor( JTextField aTextField ) - { - listeners = new Vector(); - lastValue = null; - - // default to stock JTextField - textField = aTextField; - if ( textField == null ) - { - textField = new JTextField(); - } - - textField.setBorder(new LineBorder(Color.black)); - - // handle arrow keys while caret is showing - textField.addKeyListener( this ); - - // handle lost focus - textField.addFocusListener( this ); - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) - { - this.table = table; - table.removeKeyListener( this ); // if any - table.addKeyListener( this ); - return getEditorComponent( value ); - } - - protected Component getEditorComponent( Object value ) - { - if ( value != null ) - { - textField.setText( value.toString() ); - } - else - { - textField.setText( "" ); - } - - if ( value instanceof Number ) - { - textField.setHorizontalAlignment(JTextField.RIGHT); - } - else - { - textField.setHorizontalAlignment(JTextField.LEFT); - } - - // remember original value - lastValue = value; - - // select all text and get focus - textField.selectAll(); - textField.requestFocus(); - - return textField; - } - - public Object getCellEditorValue() - { - return lastValue; - } - - public boolean isCellEditable(EventObject anEvent) - { - // key events should replace the selection - // NOTE: For whatever reason, key events trigger result in a null parameter - if ( anEvent == null ) - { - textField.setText(""); - textField.requestFocus(); - return true; - } - - return true; - } - - public boolean shouldSelectCell(EventObject anEvent) - { // System.out.println( "KeyableCellEditor.shouldSelectCell: " + anEvent ); - - // key events should replace the selection - // NOTE: For whatever reason, key events are not generated - if ( anEvent instanceof KeyEvent ) - { - textField.setText(""); - textField.requestFocus(); - return true; - } - - // otherwise, select all text and continue - textField.selectAll(); - textField.requestFocus(); - - return true; - } - - public boolean stopCellEditing() - { - lastValue = textField.getText(); - fireEditingStopped(); - table.removeKeyListener( this ); // if any - return true; - } - - public void cancelCellEditing() - { - fireEditingCanceled(); - table.removeKeyListener( this ); // if any - } - - public void addCellEditorListener(CellEditorListener l) - { - listeners.add( l ); - } - - public void removeCellEditorListener(CellEditorListener l) - { - listeners.remove( l ); - } - - protected void fireEditingCanceled() - { - ChangeEvent event = new ChangeEvent( this ); - Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception - while ( it.hasNext() ) - { - ((CellEditorListener)it.next()).editingCanceled( event ); - } - } - - protected void fireEditingStopped() - { - ChangeEvent event = new ChangeEvent( this ); - Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception - while ( it.hasNext() ) - { - ((CellEditorListener)it.next()).editingStopped( event ); - } - } - - protected void onEnterKey() - { - stopCellEditing(); - } - - protected void onEscapeKey() - { - cancelCellEditing(); - } - - protected void moveEditCell( int dRow, int dCol ) - { - if ( table == null ) return; - int row = table.getSelectedRow() + dRow; - int col = table.getSelectedColumn() + dCol; - - row = Math.max( 0, row ); - row = Math.min( row, table.getRowCount() - 1 ); - col = Math.max( 0, col ); - col = Math.min( col, table.getColumnCount() - 1 ); - - stopCellEditing(); - table.setRowSelectionInterval( row, row ); - table.setColumnSelectionInterval( col, col ); - table.editCellAt( row, col ); - textField.selectAll(); - textField.requestFocus(); - } - - // interface KeyListener - - public void keyTyped(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyTyped: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - } - - public void keyPressed(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyPressed: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - - // catch LEFT and RIGHT here before JTextField consumes them - - int keyCode = e.getKeyCode(); - if ( keyCode == KeyEvent.VK_LEFT ) - { - if ( textField.getSelectionStart() == 0 ) - { - moveEditCell( 0, -1 ); - e.consume(); - return; - } - } - if ( keyCode == KeyEvent.VK_RIGHT ) - { - if ( textField.getSelectionEnd() == textField.getText().length() ) - { - moveEditCell( 0, 1 ); - e.consume(); - return; - } - } - if ( keyCode == KeyEvent.VK_UP ) - { - moveEditCell( -1, 0 ); - e.consume(); - return; - } - if ( keyCode == KeyEvent.VK_DOWN ) - { - moveEditCell( 1, 0 ); - e.consume(); - return; - } - } - - public void keyReleased(KeyEvent e) - { // System.out.println( "KeyableCellEditor.keyReleased: " + KeyEvent.getKeyText( e.getKeyCode() ) ); - - // catch ENTER here to allow JTextField to process it as well - - int keyCode = e.getKeyCode(); - if ( keyCode == KeyEvent.VK_ENTER ) - { - onEnterKey(); - return; - } - if ( keyCode == KeyEvent.VK_ESCAPE ) - { - onEscapeKey(); - return; - } - - // tabs are apparently only received on key release - if ( keyCode == KeyEvent.VK_TAB ) - { - if ( e.isShiftDown() ) - { - moveEditCell( 0, -1 ); - } - else - { - moveEditCell( 0, 1 ); - } - e.consume(); - return; - } - - } - - // interface FocusListener - - public void focusGained(FocusEvent e) - { // System.out.println( "focusGained: " ); - } - - public void focusLost(FocusEvent e) - { // System.out.println( "focusLost: " ); - stopCellEditing(); - } + * A table cell editor customized for keyboard navigation, much like working + * with a spreadsheet. The default cell editor unfortunately does none of these + * things: + *
    + *
  • Selects text on start of editing. + *
  • Up and down keys move edit cell up and down. + *
  • Right and left keys move cell when selection caret is at end of text. + *
  • Escape cancels editing. + *
  • Enter commits edit. + *
  • Edits are properly committed on lost focus. + *
  • Tab and shift-tab work as expected. + *
  • Cell selection moves with the edit cell. + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class KeyableCellEditor implements TableCellEditor, FocusListener, KeyListener, Serializable { + List listeners; + JTextField textField; + Object lastValue; + Format currentFormat; + + JTable table; + + /** + * Default constructor - a standard JTextField will be used for editing. + */ + public KeyableCellEditor() { + this((JTextField) null); + } + + /** + * Constructor specifying a type of JTextField to be used for editing. The + * JTextField will have its border replaced with a black line border. + * + * @param aTextField A JTextField or subclass for editing values. + */ + public KeyableCellEditor(JTextField aTextField) { + listeners = new Vector(); + lastValue = null; + + // default to stock JTextField + textField = aTextField; + if (textField == null) { + textField = new JTextField(); + } + + textField.setBorder(new LineBorder(Color.black)); + + // handle arrow keys while caret is showing + textField.addKeyListener(this); + + // handle lost focus + textField.addFocusListener(this); + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + this.table = table; + table.removeKeyListener(this); // if any + table.addKeyListener(this); + return getEditorComponent(value); + } + + protected Component getEditorComponent(Object value) { + if (value != null) { + textField.setText(value.toString()); + } else { + textField.setText(""); + } + + if (value instanceof Number) { + textField.setHorizontalAlignment(JTextField.RIGHT); + } else { + textField.setHorizontalAlignment(JTextField.LEFT); + } + + // remember original value + lastValue = value; + + // select all text and get focus + textField.selectAll(); + textField.requestFocus(); + + return textField; + } + + public Object getCellEditorValue() { + return lastValue; + } + + public boolean isCellEditable(EventObject anEvent) { + // key events should replace the selection + // NOTE: For whatever reason, key events trigger result in a null parameter + if (anEvent == null) { + textField.setText(""); + textField.requestFocus(); + return true; + } + + return true; + } + + public boolean shouldSelectCell(EventObject anEvent) { // System.out.println( "KeyableCellEditor.shouldSelectCell: " + // + anEvent ); + + // key events should replace the selection + // NOTE: For whatever reason, key events are not generated + if (anEvent instanceof KeyEvent) { + textField.setText(""); + textField.requestFocus(); + return true; + } + + // otherwise, select all text and continue + textField.selectAll(); + textField.requestFocus(); + + return true; + } + + public boolean stopCellEditing() { + lastValue = textField.getText(); + fireEditingStopped(); + table.removeKeyListener(this); // if any + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + table.removeKeyListener(this); // if any + } + + public void addCellEditorListener(CellEditorListener l) { + listeners.add(l); + } + + public void removeCellEditorListener(CellEditorListener l) { + listeners.remove(l); + } + + protected void fireEditingCanceled() { + ChangeEvent event = new ChangeEvent(this); + Iterator it = new ArrayList(listeners).iterator(); // copy to prevent modification exception + while (it.hasNext()) { + ((CellEditorListener) it.next()).editingCanceled(event); + } + } + + protected void fireEditingStopped() { + ChangeEvent event = new ChangeEvent(this); + Iterator it = new ArrayList(listeners).iterator(); // copy to prevent modification exception + while (it.hasNext()) { + ((CellEditorListener) it.next()).editingStopped(event); + } + } + + protected void onEnterKey() { + stopCellEditing(); + } + + protected void onEscapeKey() { + cancelCellEditing(); + } + + protected void moveEditCell(int dRow, int dCol) { + if (table == null) + return; + int row = table.getSelectedRow() + dRow; + int col = table.getSelectedColumn() + dCol; + + row = Math.max(0, row); + row = Math.min(row, table.getRowCount() - 1); + col = Math.max(0, col); + col = Math.min(col, table.getColumnCount() - 1); + + stopCellEditing(); + table.setRowSelectionInterval(row, row); + table.setColumnSelectionInterval(col, col); + table.editCellAt(row, col); + textField.selectAll(); + textField.requestFocus(); + } + + // interface KeyListener + + public void keyTyped(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyTyped: " + KeyEvent.getKeyText( + // e.getKeyCode() ) ); + } + + public void keyPressed(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyPressed: " + KeyEvent.getKeyText( + // e.getKeyCode() ) ); + + // catch LEFT and RIGHT here before JTextField consumes them + + int keyCode = e.getKeyCode(); + if (keyCode == KeyEvent.VK_LEFT) { + if (textField.getSelectionStart() == 0) { + moveEditCell(0, -1); + e.consume(); + return; + } + } + if (keyCode == KeyEvent.VK_RIGHT) { + if (textField.getSelectionEnd() == textField.getText().length()) { + moveEditCell(0, 1); + e.consume(); + return; + } + } + if (keyCode == KeyEvent.VK_UP) { + moveEditCell(-1, 0); + e.consume(); + return; + } + if (keyCode == KeyEvent.VK_DOWN) { + moveEditCell(1, 0); + e.consume(); + return; + } + } + + public void keyReleased(KeyEvent e) { // System.out.println( "KeyableCellEditor.keyReleased: " + + // KeyEvent.getKeyText( e.getKeyCode() ) ); + + // catch ENTER here to allow JTextField to process it as well + + int keyCode = e.getKeyCode(); + if (keyCode == KeyEvent.VK_ENTER) { + onEnterKey(); + return; + } + if (keyCode == KeyEvent.VK_ESCAPE) { + onEscapeKey(); + return; + } + + // tabs are apparently only received on key release + if (keyCode == KeyEvent.VK_TAB) { + if (e.isShiftDown()) { + moveEditCell(0, -1); + } else { + moveEditCell(0, 1); + } + e.consume(); + return; + } + + } + + // interface FocusListener + + public void focusGained(FocusEvent e) { // System.out.println( "focusGained: " ); + } + + public void focusLost(FocusEvent e) { // System.out.println( "focusLost: " ); + stopCellEditing(); + } } - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java index 4a7f07e..326d825 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/LineWrappingRenderer.java @@ -31,124 +31,111 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** -* A list cell renderer that wraps its text to subsequent lines -* depending on the length of text string and the width of the -* parent list. -* -* This renderer depends on listening to the parent list's viewport -* and fixing the list's width to match the viewport's size. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* @revision $Revision: 904 $ -*/ -public class LineWrappingRenderer extends MultiLineLabel - implements ListCellRenderer, ChangeListener -{ - protected static Border noFocusBorder; - - protected JList list; - protected JViewport viewport; - protected int preferredWidth; - -/** -* Required constructor. The renderer keeps a reference to -* the list in which it is used and its viewport. This list -* is the only list that may use this renderer. The renderer -* will use the current size of the list to determine where -* lines will initially break. -* @param containerList The list that will be using this renderer. -*/ - public LineWrappingRenderer( JList containerList ) - { - super(); - setLineWrap(true); - noFocusBorder = new EmptyBorder(1, 1, 1, 1); - - list = containerList; - preferredWidth = 400; - if ( list.getParent() instanceof JViewport ) - { - viewport = (JViewport) list.getParent(); - viewport.addChangeListener( this ); - int newWidth = viewport.getExtentSize().width; - if ( newWidth > 0 ) preferredWidth = newWidth; - } - else - { - // should function adequately in absence of a viewport - // System.err.println( "LineWrappingRenderer.init: list.getParent = " + list.getParent() ); - } - } - -/** -* Returns the preferred size of the label, with width -* constrained to the current width. -* @return the size -*/ - public Dimension getPreferredSize() - { - int width = getWidth(); - if ( width != preferredWidth ) - { - // if component has not yet been placed within the list - if ( width < list.getWidth() / 2 ) width = list.getWidth(); - preferredWidth = width; - } - return new Dimension( preferredWidth, super.getPreferredSize().height ); - } - -/** -* Returns this component with the width set to the -* width of the specified JList. -* @return this component. -*/ - public Component getListCellRendererComponent ( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ) - { // System.out.println( "LineWrappingRenderer.getListCellRendererComponent:" ); - - if ( list != this.list ) - { - System.err.println( "LineWrappingRenderer.getListCellRendererComponent: " + - "warning: the list using the renderer is not the list specified in the constructor." ); - } - - if (isSelected) - { - setBackground(this.list.getSelectionBackground()); - setForeground(this.list.getSelectionForeground()); - } - else - { - setBackground(this.list.getBackground()); - setForeground(this.list.getForeground()); - } - - setText((value == null) ? "" : value.toString()); - - setEnabled(this.list.isEnabled()); - setFont(this.list.getFont()); - setBorder( (cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder ); - - return this; - } - -/** -* Overridden to respond to viewport changes. -*/ - public void stateChanged(ChangeEvent e) - { - int newWidth = viewport.getExtentSize().width; - if ( newWidth > 0 ) preferredWidth = newWidth; - - // set fixed width on list - list.setFixedCellWidth( preferredWidth ); - setSize( preferredWidth, super.getSize().height ); - } + * A list cell renderer that wraps its text to subsequent lines depending on the + * length of text string and the width of the parent list. + * + * This renderer depends on listening to the parent list's viewport and fixing + * the list's width to match the viewport's size. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @revision $Revision: 904 $ + */ +public class LineWrappingRenderer extends MultiLineLabel implements ListCellRenderer, ChangeListener { + protected static Border noFocusBorder; + + protected JList list; + protected JViewport viewport; + protected int preferredWidth; + + /** + * Required constructor. The renderer keeps a reference to the list in which it + * is used and its viewport. This list is the only list that may use this + * renderer. The renderer will use the current size of the list to determine + * where lines will initially break. + * + * @param containerList The list that will be using this renderer. + */ + public LineWrappingRenderer(JList containerList) { + super(); + setLineWrap(true); + noFocusBorder = new EmptyBorder(1, 1, 1, 1); + + list = containerList; + preferredWidth = 400; + if (list.getParent() instanceof JViewport) { + viewport = (JViewport) list.getParent(); + viewport.addChangeListener(this); + int newWidth = viewport.getExtentSize().width; + if (newWidth > 0) + preferredWidth = newWidth; + } else { + // should function adequately in absence of a viewport + // System.err.println( "LineWrappingRenderer.init: list.getParent = " + + // list.getParent() ); + } + } + + /** + * Returns the preferred size of the label, with width constrained to the + * current width. + * + * @return the size + */ + public Dimension getPreferredSize() { + int width = getWidth(); + if (width != preferredWidth) { + // if component has not yet been placed within the list + if (width < list.getWidth() / 2) + width = list.getWidth(); + preferredWidth = width; + } + return new Dimension(preferredWidth, super.getPreferredSize().height); + } + + /** + * Returns this component with the width set to the width of the specified + * JList. + * + * @return this component. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { // System.out.println( "LineWrappingRenderer.getListCellRendererComponent:" ); + + if (list != this.list) { + System.err.println("LineWrappingRenderer.getListCellRendererComponent: " + + "warning: the list using the renderer is not the list specified in the constructor."); + } + + if (isSelected) { + setBackground(this.list.getSelectionBackground()); + setForeground(this.list.getSelectionForeground()); + } else { + setBackground(this.list.getBackground()); + setForeground(this.list.getForeground()); + } + + setText((value == null) ? "" : value.toString()); + + setEnabled(this.list.isEnabled()); + setFont(this.list.getFont()); + setBorder((cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder); + + return this; + } + + /** + * Overridden to respond to viewport changes. + */ + public void stateChanged(ChangeEvent e) { + int newWidth = viewport.getExtentSize().width; + if (newWidth > 0) + preferredWidth = newWidth; + + // set fixed width on list + list.setFixedCellWidth(preferredWidth); + setSize(preferredWidth, super.getSize().height); + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java index b5f8a9b..ce362c9 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/MultiLineLabel.java @@ -23,113 +23,100 @@ import javax.swing.LookAndFeel; import javax.swing.text.Highlighter; /** -* A custom JTextArea that looks and feels like a JLabel, but supports -* line wrapping. This works a lot more like the IFC label component. -* NOTE: doesn't support icons (yet). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* @revision $Revision: 904 $ -*/ -public class MultiLineLabel extends JTextArea -{ + * A custom JTextArea that looks and feels like a JLabel, but supports line + * wrapping. This works a lot more like the IFC label component. NOTE: doesn't + * support icons (yet). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @date $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @revision $Revision: 904 $ + */ +public class MultiLineLabel extends JTextArea { /** - * Saves a reference to the original highlighter - * to enable/disable text selection. - */ + * Saves a reference to the original highlighter to enable/disable text + * selection. + */ protected Highlighter originalHighlighter; - -/* -* Creates a MultiLineLabel instance with an empty string for the title. -*/ - public MultiLineLabel() - { - super(); - // turn on wrapping and disable editing and highlighting + /* + * Creates a MultiLineLabel instance with an empty string for the title. + */ + public MultiLineLabel() { + super(); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); - } + // turn on wrapping and disable editing and highlighting -/* -* Creates a MultiLineLabel instance with the specified text. -* @param text The specified text. -*/ - public MultiLineLabel( String text ) - { - super( text ); + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); + } - // turn on wrapping and disable editing and highlighting + /* + * Creates a MultiLineLabel instance with the specified text. + * + * @param text The specified text. + */ + public MultiLineLabel(String text) { + super(text); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); - } + // turn on wrapping and disable editing and highlighting -/* -* Overridden to look like a label. -* @param text The specified text. -*/ - public void updateUI() - { - // got the implementation idea from usenet + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); + } - super.updateUI(); + /* + * Overridden to look like a label. + * + * @param text The specified text. + */ + public void updateUI() { + // got the implementation idea from usenet - // turn on wrapping and disable editing and highlighting + super.updateUI(); - setLineWrap(true); - setWrapStyleWord(true); - setEditable(false); - setSelectable( false ); + // turn on wrapping and disable editing and highlighting - // Set the text area's border, colors and font to - // that of a label + setLineWrap(true); + setWrapStyleWord(true); + setEditable(false); + setSelectable(false); - LookAndFeel.installBorder(this, "Label.border"); + // Set the text area's border, colors and font to + // that of a label - LookAndFeel.installColorsAndFont(this, - "Label.background", - "Label.foreground", - "Label.font"); - } + LookAndFeel.installBorder(this, "Label.border"); -/** -* Sets whether text is selectable. -* Default is non-selectable text. -*/ - public void setSelectable( boolean selectable ) - { - if ( selectable ) - { - setHighlighter( originalHighlighter ); - } - else - { + LookAndFeel.installColorsAndFont(this, "Label.background", "Label.foreground", "Label.font"); + } + + /** + * Sets whether text is selectable. Default is non-selectable text. + */ + public void setSelectable(boolean selectable) { + if (selectable) { + setHighlighter(originalHighlighter); + } else { originalHighlighter = getHighlighter(); - setHighlighter( null ); + setHighlighter(null); } } - -/** -* Gets whether text is selectable. -* Default is non-selectable text. -*/ - public boolean isSelectable() - { - return ( getHighlighter() != null ); + + /** + * Gets whether text is selectable. Default is non-selectable text. + */ + public boolean isSelectable() { + return (getHighlighter() != null); } -/** -* Overridden to return false. -*/ - public boolean isFocusTraversable() - { + /** + * Overridden to return false. + */ + public boolean isFocusTraversable() { return false; - } + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java index b3d2d03..dee8f27 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/NumericTextField.java @@ -19,416 +19,346 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.ui.swing.components; /** -* NumericTextField is a "smart" text field that restricts the user's input. The -* input is restructed to numeric only wich can be of two types: integer and real -* numbers. A range can also be placed on the text field. The default type is -* integer, with the being (Integer.MIN_VALUE, Integer.MAX_VALUE). -* -* @author rob@straylight.princeton.com -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class NumericTextField extends SmartTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - -/** -* Restrict the text input to integers (whole numbers) only. -*/ - public final static int INTEGER = 0; - -/** -* Restrict the text input to floating-point numbers only. -*/ - public final static int FLOAT = 1; - - private Number maximumValue = null; - private Number minimumValue = null; - - private boolean sign = false; - private int newCaretPosition = 0; - private int valueType = INTEGER; - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default constructor. -*/ - public NumericTextField() - { - this("", 0); - } - -/** -* Constructor. -* @param text The initial string the text field is set to. -*/ - public NumericTextField(String text) - { - this(text, 0); - } - -/** -* Constructor. -* @param columns Width of the text field (in characters). -*/ - public NumericTextField(int columns) - { - this("", columns); - } - -/** -* Constructor. -* @param text The initial string the text field is set to. -* @param columns Width of the text field (in characters). -*/ - public NumericTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Sets the upper limit of the range of numbers to accept. -* @param newMaximumValue The maximum number accepted by the text field. -*/ - public void setMaximumValue(double newMaximumValue) - { - if (newMaximumValue >= 0) - { - maximumValue = new Double( newMaximumValue ); - } - else - { - maximumValue = null; - } - } - -/** -* Returns the upper limit of the range of numbers to accept. -* @return The maximum number accepted by this text field. -*/ - public double getMaximumValue() - { - if ( valueType == INTEGER ) - { - return (maximumValue == null) ? (double) Integer.MAX_VALUE : maximumValue.doubleValue(); - } - else - { - return (maximumValue == null) ? Double.MAX_VALUE : maximumValue.doubleValue(); - } - } - -/** -* Sets the lower limit of the range of numbers to accept. -* @param newMinimumValue The minimum number accepted by the text field. -*/ - public void setMinimumValue(double newMinimumValue) - { - if (newMinimumValue <= 0) - { - minimumValue = new Double( newMinimumValue ); - } - else - { - minimumValue = null; - } - } - -/** -* Returns the lower limit of the range of numbers to accept. -* @return The minimum number accepted by this text field. -*/ - public double getMinimumValue() - { - if ( valueType == INTEGER ) - { - return (minimumValue == null) ? (double) Integer.MIN_VALUE : minimumValue.doubleValue(); - } - else - { - return (minimumValue == null) ? -1.0*Double.MAX_VALUE : minimumValue.doubleValue(); - // NOTE: Double.MIN_VALUE returns the smallest positive value - oooops. - } - } - -/** -* Sets which type of number this text field can accept. -* @see #INTEGER -* @see #FLOAT -* @param newValueType The type of number to accept. -*/ - public void setValueType(int newValueType) - { - if ((newValueType != INTEGER) && (newValueType != FLOAT)) - { - valueType = INTEGER; - } - else - { - valueType = newValueType; - } - } - -/** -* Returns which type of number this text field accepts. The default is -* integer. -* @see #INTEGER -* @see #FLOAT -* @return The type of number to accept. -*/ - public int getValueType() - { - return valueType; - } - -/** -* Returns the integer numeric value of the string in the text field. The type -* can be either integer of float. -* @return The current value in the text field. -*/ - public int getIntValue() - { - int value = 0; - - try - { - value = Integer.parseInt(getText()); - } - catch (NumberFormatException e) - { - try - { - Double dValue = Double.valueOf(getText()); - value = dValue.intValue(); - } - catch (NumberFormatException ignored) {} - } - - return value; - } - -/** -* Sets the text field to integer value specified. -* @param aValue An integer value to display in the text field. -*/ - public void setIntValue(int aValue) - { - setText(Integer.toString(aValue)); - } - -/** -* Returns the real number numeric value of the string in the text field. The type -* can be either integer of float. -* @return The current value in the text field. -*/ - public double getDoubleValue() - { - Double value = new Double(0); - - try - { - value = Double.valueOf(getText()); - } - catch (NumberFormatException ignored) {} - - return value.doubleValue(); - } - -/** -* Sets the text field to the double value specified. If the text field type is -* FLOAT then the the number is display as a real number. If the text field -* type is INTEGER then the number is converted to a whole number for displaying. -* @param aValue A double value to display in the text field. -*/ - public void setDoubleValue(double aValue) - { - Double temp = new Double(aValue); - - if (valueType == FLOAT) - { - setText(temp.toString()); - } - else - { - setText(Integer.toString(temp.intValue())); - } - } - -/******************************* -* PROTECTED METHODS -*******************************/ - - protected boolean isValidCharacter(char aChar) - { - if (((aChar >= ' ') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) - { - if (aChar == '.') - { - if ( valueType == FLOAT ) - { - return true; - } - else - { - return false; - } - } - else if (aChar == '-') - { - if ( getMinimumValue() < 0 ) - { - return true; - } - else - { - return false; - } - } - else if (aChar == '+') - { - if ( getMaximumValue() >= 0 ) - { - return true; - } - else - { - return false; - } - } - return false; - } - return true; - } - - protected boolean isValidString(String aString) - { - int iValue = 0; - double dValue = 0.0; - - String tempString = new String(scanForSignChar(aString)); - - if ( valueType == INTEGER ) - { - try - { - iValue = Integer.parseInt(tempString); - } - catch (NumberFormatException e1) - { - if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) - { - iValue = 0; - } - else - { - return false; - } - } - if ((((double)iValue) < getMinimumValue()) || (((double)iValue) > getMaximumValue())) - { - return false; - } - } - else - { - // Double.valueOf requires a zero before the decimal point - if ( tempString.startsWith( "." ) ) - { - tempString = "0" + tempString; - } - try - { - dValue = Double.valueOf(tempString).doubleValue(); - } - catch (NumberFormatException e2) - { - if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) - { - dValue = 0.0; - } - else - { - return false; - } - } - - if ((dValue < getMinimumValue()) || (dValue > getMaximumValue())) - { - return false; - } - } - - return true; - } - - protected void postProcessing() - { - if (sign) - { - setText(scanForSignChar(getText())); - setCaretPosition(newCaretPosition); - } - sign = false; - } - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private String scanForSignChar(String aString) - { - String newString = ""; - boolean positive = false; - boolean negative = false; - int oldCaretPosition = getCaretPosition(); - int charactersAdded = 0; - - newCaretPosition = 0; - - if (aString.length() <= 0) - { - return aString; - } - - for (int i = 0; i < aString.length(); ++i) - { - switch (aString.charAt(i)) - { - case '+': positive = true; - break; - case '-': negative = true; - break; - default: newString += aString.charAt(i); - charactersAdded++; - break; - } - - if ((i + 1) == oldCaretPosition) - { - newCaretPosition = charactersAdded; - } - } - - if ((!(positive)) && (negative)) - { - newString = "-" + newString; - newCaretPosition++; - } - - if (positive || negative) - { - sign = true; - } - - return newString; - } + * NumericTextField is a "smart" text field that restricts the user's input. The + * input is restructed to numeric only wich can be of two types: integer and + * real numbers. A range can also be placed on the text field. The default type + * is integer, with the being (Integer.MIN_VALUE, Integer.MAX_VALUE). + * + * @author rob@straylight.princeton.com + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class NumericTextField extends SmartTextField { + + /******************************* + * CONSTANTS + *******************************/ + + /** + * Restrict the text input to integers (whole numbers) only. + */ + public final static int INTEGER = 0; + + /** + * Restrict the text input to floating-point numbers only. + */ + public final static int FLOAT = 1; + + private Number maximumValue = null; + private Number minimumValue = null; + + private boolean sign = false; + private int newCaretPosition = 0; + private int valueType = INTEGER; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default constructor. + */ + public NumericTextField() { + this("", 0); + } + + /** + * Constructor. + * + * @param text The initial string the text field is set to. + */ + public NumericTextField(String text) { + this(text, 0); + } + + /** + * Constructor. + * + * @param columns Width of the text field (in characters). + */ + public NumericTextField(int columns) { + this("", columns); + } + + /** + * Constructor. + * + * @param text The initial string the text field is set to. + * @param columns Width of the text field (in characters). + */ + public NumericTextField(String text, int columns) { + super(text, columns); + } + + /** + * Sets the upper limit of the range of numbers to accept. + * + * @param newMaximumValue The maximum number accepted by the text field. + */ + public void setMaximumValue(double newMaximumValue) { + if (newMaximumValue >= 0) { + maximumValue = new Double(newMaximumValue); + } else { + maximumValue = null; + } + } + + /** + * Returns the upper limit of the range of numbers to accept. + * + * @return The maximum number accepted by this text field. + */ + public double getMaximumValue() { + if (valueType == INTEGER) { + return (maximumValue == null) ? (double) Integer.MAX_VALUE : maximumValue.doubleValue(); + } else { + return (maximumValue == null) ? Double.MAX_VALUE : maximumValue.doubleValue(); + } + } + + /** + * Sets the lower limit of the range of numbers to accept. + * + * @param newMinimumValue The minimum number accepted by the text field. + */ + public void setMinimumValue(double newMinimumValue) { + if (newMinimumValue <= 0) { + minimumValue = new Double(newMinimumValue); + } else { + minimumValue = null; + } + } + + /** + * Returns the lower limit of the range of numbers to accept. + * + * @return The minimum number accepted by this text field. + */ + public double getMinimumValue() { + if (valueType == INTEGER) { + return (minimumValue == null) ? (double) Integer.MIN_VALUE : minimumValue.doubleValue(); + } else { + return (minimumValue == null) ? -1.0 * Double.MAX_VALUE : minimumValue.doubleValue(); + // NOTE: Double.MIN_VALUE returns the smallest positive value - oooops. + } + } + + /** + * Sets which type of number this text field can accept. + * + * @see #INTEGER + * @see #FLOAT + * @param newValueType The type of number to accept. + */ + public void setValueType(int newValueType) { + if ((newValueType != INTEGER) && (newValueType != FLOAT)) { + valueType = INTEGER; + } else { + valueType = newValueType; + } + } + + /** + * Returns which type of number this text field accepts. The default is integer. + * + * @see #INTEGER + * @see #FLOAT + * @return The type of number to accept. + */ + public int getValueType() { + return valueType; + } + + /** + * Returns the integer numeric value of the string in the text field. The type + * can be either integer of float. + * + * @return The current value in the text field. + */ + public int getIntValue() { + int value = 0; + + try { + value = Integer.parseInt(getText()); + } catch (NumberFormatException e) { + try { + Double dValue = Double.valueOf(getText()); + value = dValue.intValue(); + } catch (NumberFormatException ignored) { + } + } + + return value; + } + + /** + * Sets the text field to integer value specified. + * + * @param aValue An integer value to display in the text field. + */ + public void setIntValue(int aValue) { + setText(Integer.toString(aValue)); + } + + /** + * Returns the real number numeric value of the string in the text field. The + * type can be either integer of float. + * + * @return The current value in the text field. + */ + public double getDoubleValue() { + Double value = new Double(0); + + try { + value = Double.valueOf(getText()); + } catch (NumberFormatException ignored) { + } + + return value.doubleValue(); + } + + /** + * Sets the text field to the double value specified. If the text field type is + * FLOAT then the the number is display as a real number. If the text field type + * is INTEGER then the number is converted to a whole number for displaying. + * + * @param aValue A double value to display in the text field. + */ + public void setDoubleValue(double aValue) { + Double temp = new Double(aValue); + + if (valueType == FLOAT) { + setText(temp.toString()); + } else { + setText(Integer.toString(temp.intValue())); + } + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + protected boolean isValidCharacter(char aChar) { + if (((aChar >= ' ') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { + if (aChar == '.') { + if (valueType == FLOAT) { + return true; + } else { + return false; + } + } else if (aChar == '-') { + if (getMinimumValue() < 0) { + return true; + } else { + return false; + } + } else if (aChar == '+') { + if (getMaximumValue() >= 0) { + return true; + } else { + return false; + } + } + return false; + } + return true; + } + + protected boolean isValidString(String aString) { + int iValue = 0; + double dValue = 0.0; + + String tempString = new String(scanForSignChar(aString)); + + if (valueType == INTEGER) { + try { + iValue = Integer.parseInt(tempString); + } catch (NumberFormatException e1) { + if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { + iValue = 0; + } else { + return false; + } + } + if ((((double) iValue) < getMinimumValue()) || (((double) iValue) > getMaximumValue())) { + return false; + } + } else { + // Double.valueOf requires a zero before the decimal point + if (tempString.startsWith(".")) { + tempString = "0" + tempString; + } + try { + dValue = Double.valueOf(tempString).doubleValue(); + } catch (NumberFormatException e2) { + if ((tempString.compareTo("-") == 0) && (getMinimumValue() < 0.0)) { + dValue = 0.0; + } else { + return false; + } + } + + if ((dValue < getMinimumValue()) || (dValue > getMaximumValue())) { + return false; + } + } + + return true; + } + + protected void postProcessing() { + if (sign) { + setText(scanForSignChar(getText())); + setCaretPosition(newCaretPosition); + } + sign = false; + } + + /******************************* + * PRIVATE METHODS + *******************************/ + + private String scanForSignChar(String aString) { + String newString = ""; + boolean positive = false; + boolean negative = false; + int oldCaretPosition = getCaretPosition(); + int charactersAdded = 0; + + newCaretPosition = 0; + + if (aString.length() <= 0) { + return aString; + } + + for (int i = 0; i < aString.length(); ++i) { + switch (aString.charAt(i)) { + case '+': + positive = true; + break; + case '-': + negative = true; + break; + default: + newString += aString.charAt(i); + charactersAdded++; + break; + } + + if ((i + 1) == oldCaretPosition) { + newCaretPosition = charactersAdded; + } + } + + if ((!(positive)) && (negative)) { + newString = "-" + newString; + newCaretPosition++; + } + + if (positive || negative) { + sign = true; + } + + return newString; + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java index 9db2834..996f8e0 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTable.java @@ -47,233 +47,235 @@ import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; /** -* PropertyEditorTable is a table designed to display and edit the properties -* of an object. Because JTable assumes all cells in a column display -* the same data type, we have to subclass to determine the class -* based on the cell contents. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * PropertyEditorTable is a table designed to display and edit the properties of + * an object. Because JTable assumes all cells in a column display the same data + * type, we have to subclass to determine the class based on the cell contents. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class PropertyEditorTable extends JTable { // // Constructors // - /** - * Constructs a default JTable which is initialized with a default - * data model, a default column model, and a default selection - * model. - * - * @see #createDefaultDataModel - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable() { - super(null, null, null); - } - - /** - * Constructs a JTable which is initialized with dm as the - * data model, a default column model, and a default selection - * model. - * - * @param dm The data model for the table - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm) { - super(dm, null, null); - } - - /** - * Constructs a JTable which is initialized with dm as the - * data model, cm as the column model, and a default selection - * model. - * - * @param dm The data model for the table - * @param cm The column model for the table - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm, TableColumnModel cm) { - super(dm, cm, null); - } - - /** - * Constructs a JTable which is initialized with dm as the - * data model, cm as the column model, and sm as the - * selection model. If any of the parameters are null this - * method will initialize the table with the corresponding - * default model. The autoCreateColumnsFromModel flag is set - * to false if cm is non-null, otherwise it is set to true - * and the column model is populated with suitable TableColumns - * for the columns in dm. - * - * @param dm The data model for the table - * @param cm The column model for the table - * @param sm The row selection model for the table - * @see #createDefaultDataModel - * @see #createDefaultColumnModel - * @see #createDefaultSelectionModel - */ - public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { - super( dm, cm, sm ); + /** + * Constructs a default JTable which is initialized with a default data model, a + * default column model, and a default selection model. + * + * @see #createDefaultDataModel + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable() { + super(null, null, null); } - /** - * Constructs a JTable with numRows and numColumns of - * empty cells using the DefaultTableModel. The columns will have - * names of the form "A", "B", "C", etc. - * - * @param numRows The number of rows the table holds - * @param numColumns The number of columns the table holds - */ - public PropertyEditorTable(int numRows, int numColumns) { - super( numRows, numColumns ); - } - - /** - * Constructs a JTable to display the values in the Vector of Vectors, - * rowData, with column names, columnNames. - * The Vectors contained in rowData should contain the values - * for that row. In other words, the value of the cell at row 1, - * column 5 can be obtained with the following code: - *

- *

((Vector)rowData.elementAt(1)).elementAt(5);
- *

- * All rows must be of the same length as columnNames. - *

- * @param rowData The data for the new table - * @param columnNames Names of each column - */ - public PropertyEditorTable(final Vector rowData, final Vector columnNames) { - super( rowData, columnNames ); - } - - /** - * Constructs a JTable to display the values in the two dimensional array, - * rowData, with column names, columnNames. - * rowData is an Array of rows, so the value of the cell at row 1, - * column 5 can be obtained with the following code: - *

- *

 rowData[1][5]; 
- *

- * All rows must be of the same length as columnNames. - *

- * @param rowData The data for the new table - * @param columnNames Names of each column - */ - public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) { - super( rowData, columnNames ); + /** + * Constructs a JTable which is initialized with dm as the data model, a + * default column model, and a default selection model. + * + * @param dm The data model for the table + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm) { + super(dm, null, null); } - /** - * Returns the type of the column at the specified view position. - * - * @return the type of the column at position column in the view - * where the first column is column 0. + /** + * Constructs a JTable which is initialized with dm as the data model, + * cm as the column model, and a default selection model. * - * Modified mln: now a wrapper for getCellClass() + * @param dm The data model for the table + * @param cm The column model for the table + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm, TableColumnModel cm) { + super(dm, cm, null); + } + + /** + * Constructs a JTable which is initialized with dm as the data model, + * cm as the column model, and sm as the selection model. If any + * of the parameters are null this method will initialize the table with + * the corresponding default model. The autoCreateColumnsFromModel flag + * is set to false if cm is non-null, otherwise it is set to true and the + * column model is populated with suitable TableColumns for the columns in + * dm. * - */ - public Class getColumnClass(int column) { - return getCellClass( 0, column ); - } - - /** - * Returns the type of the cell at the specified view position. - * - * @return the type of the cell at position row, column in the view - * where the first column is column 0. + * @param dm The data model for the table + * @param cm The column model for the table + * @param sm The row selection model for the table + * @see #createDefaultDataModel + * @see #createDefaultColumnModel + * @see #createDefaultSelectionModel + */ + public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { + super(dm, cm, sm); + } + + /** + * Constructs a JTable with numRows and numColumns of empty cells + * using the DefaultTableModel. The columns will have names of the form "A", + * "B", "C", etc. * - * Modified mln: new methods + * @param numRows The number of rows the table holds + * @param numColumns The number of columns the table holds + */ + public PropertyEditorTable(int numRows, int numColumns) { + super(numRows, numColumns); + } + + /** + * Constructs a JTable to display the values in the Vector of Vectors, + * rowData, with column names, columnNames. The Vectors contained + * in rowData should contain the values for that row. In other words, the + * value of the cell at row 1, column 5 can be obtained with the following code: + *

+ * + *

+	 * ((Vector) rowData.elementAt(1)).elementAt(5);
+	 * 
+ *

+ * All rows must be of the same length as columnNames. + *

+ * + * @param rowData The data for the new table + * @param columnNames Names of each column + */ + public PropertyEditorTable(final Vector rowData, final Vector columnNames) { + super(rowData, columnNames); + } + + /** + * Constructs a JTable to display the values in the two dimensional array, + * rowData, with column names, columnNames. rowData is an + * Array of rows, so the value of the cell at row 1, column 5 can be obtained + * with the following code: + *

+ * + *

+	 *  rowData[1][5];
+	 * 
+ *

+ * All rows must be of the same length as columnNames. + *

+ * + * @param rowData The data for the new table + * @param columnNames Names of each column + */ + public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) { + super(rowData, columnNames); + } + + /** + * Returns the type of the column at the specified view position. + * + * @return the type of the column at position column in the view where + * the first column is column 0. + * + * Modified mln: now a wrapper for getCellClass() + * + */ + public Class getColumnClass(int column) { + return getCellClass(0, column); + } + + /** + * Returns the type of the cell at the specified view position. + * + * @return the type of the cell at position row, column in the + * view where the first column is column 0. * - */ - public Class getCellClass(int row, int column) { + * Modified mln: new methods + * + */ + public Class getCellClass(int row, int column) { TableModel model = getModel(); - if ( model instanceof PropertyEditorTableModel ) - return ((PropertyEditorTableModel)model).getCellClass( row, column ); - else + if (model instanceof PropertyEditorTableModel) + return ((PropertyEditorTableModel) model).getCellClass(row, column); + else return model.getColumnClass(convertColumnIndexToModel(column)); - } - - /** - * Return an appropriate renderer for the cell specified by this this row and - * column. If the TableColumn for this column has a non-null renderer, return that. - * If not, find the class of the data in this column (using getColumnClass()) - * and return the default renderer for this type of data. - * - * @param row the row of the cell to render, where 0 is the first - * @param column the column of the cell to render, where 0 is the first + } + + /** + * Return an appropriate renderer for the cell specified by this this row and + * column. If the TableColumn for this column has a non-null renderer, return + * that. If not, find the class of the data in this column (using + * getColumnClass()) and return the default renderer for this type of data. + * + * @param row the row of the cell to render, where 0 is the first + * @param column the column of the cell to render, where 0 is the first + * + * Modified mln: calls getCellClass if there's no column model * - * Modified mln: calls getCellClass if there's no column model + */ + public TableCellRenderer getCellRenderer(int row, int column) { + TableColumn tableColumn = getColumnModel().getColumn(column); + TableCellRenderer renderer = tableColumn.getCellRenderer(); + if (renderer == null) { + renderer = getDefaultRenderer(getCellClass(row, column)); + } + return renderer; + } + + /** + * Return an appropriate editor for the cell specified by this this row and + * column. If the TableColumn for this column has a non-null editor, return + * that. If not, find the class of the data in this column (using + * getColumnClass()) and return the default editor for this type of data. * - */ - public TableCellRenderer getCellRenderer(int row, int column) { - TableColumn tableColumn = getColumnModel().getColumn(column); - TableCellRenderer renderer = tableColumn.getCellRenderer(); - if (renderer == null) { - renderer = getDefaultRenderer(getCellClass(row, column)); - } - return renderer; - } - - - /** - * Return an appropriate editor for the cell specified by this this row and - * column. If the TableColumn for this column has a non-null editor, return that. - * If not, find the class of the data in this column (using getColumnClass()) - * and return the default editor for this type of data. - * - * @param row the row of the cell to edit, where 0 is the first - * @param column the column of the cell to edit, where 0 is the first + * @param row the row of the cell to edit, where 0 is the first + * @param column the column of the cell to edit, where 0 is the first * - * Modified mp: calls getCellClass if there's no column model + * Modified mp: calls getCellClass if there's no column model * - */ - public TableCellEditor getCellEditor(int row, int column) { - TableColumn tableColumn = getColumnModel().getColumn(column); - TableCellEditor editor = tableColumn.getCellEditor(); - if (editor == null) { - editor = getDefaultEditor(getCellClass(row, column)); - } - return editor; - } + */ + public TableCellEditor getCellEditor(int row, int column) { + TableColumn tableColumn = getColumnModel().getColumn(column); + TableCellEditor editor = tableColumn.getCellEditor(); + if (editor == null) { + editor = getDefaultEditor(getCellClass(row, column)); + } + return editor; + } protected void createDefaultRenderers() { super.createDefaultRenderers(); -/* // copying this code here as a sample of creating a renderer - // Dates - DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { - DateFormat formatter = DateFormat.getDateInstance(); - public void setValue(Object value) { - setText((value == null) ? "" : formatter.format(value)); } - }; - dateRenderer.setHorizontalAlignment(JLabel.RIGHT); - setDefaultRenderer(Date.class, dateRenderer); -*/ - - DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() { - public void setValue(Object value) { - setText( getFontDescription( (Font) value ) ); + /* + * // copying this code here as a sample of creating a renderer // Dates + * DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { + * DateFormat formatter = DateFormat.getDateInstance(); public void + * setValue(Object value) { setText((value == null) ? "" : + * formatter.format(value)); } }; + * dateRenderer.setHorizontalAlignment(JLabel.RIGHT); + * setDefaultRenderer(Date.class, dateRenderer); + */ + + DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() { + public void setValue(Object value) { + setText(getFontDescription((Font) value)); } }; - fontRenderer.setHorizontalAlignment(JLabel.RIGHT); - setDefaultRenderer(Font.class, fontRenderer); + fontRenderer.setHorizontalAlignment(JLabel.RIGHT); + setDefaultRenderer(Font.class, fontRenderer); - setUpColorRenderer( this ); - setUpMethodRenderer( this ); - } + setUpColorRenderer(this); + setUpMethodRenderer(this); + } - protected String getFontDescription( Font f ) { + protected String getFontDescription(Font f) { String s; - if ( f != null ) { + if (f != null) { s = f.getName(); - if ( f.isBold() ) s += " Bold"; - if ( f.isItalic() ) s += " Italic"; + if (f.isBold()) + s += " Bold"; + if (f.isItalic()) + s += " Italic"; s += " " + f.getSize(); } else { s = ""; @@ -281,292 +283,263 @@ public class PropertyEditorTable extends JTable { return s; } - protected void createDefaultEditors() { + protected void createDefaultEditors() { super.createDefaultEditors(); -/* // copying this code here as a sample of creating an editor - // Numbers - JTextField rightAlignedTextField = new JTextField(); - rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT); - rightAlignedTextField.setBorder(new LineBorder(Color.black)); - setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField)); - - // Booleans - JCheckBox centeredCheckBox = new JCheckBox(); - centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER); - setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox)); -*/ - setUpColorEditor( this ); - setUpMethodEditor( this ); + /* + * // copying this code here as a sample of creating an editor // Numbers + * JTextField rightAlignedTextField = new JTextField(); + * rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT); + * rightAlignedTextField.setBorder(new LineBorder(Color.black)); + * setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField)); + * + * // Booleans JCheckBox centeredCheckBox = new JCheckBox(); + * centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER); + * setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox)); + */ + setUpColorEditor(this); + setUpMethodEditor(this); } - - // following code lifted from: + // following code lifted from: // http://java.sun.com/docs/books/tutorial/ui/swing/example-swing/TableDialogEditDemo.java - class ColorRenderer extends JLabel - implements TableCellRenderer { - Border unselectedBorder = null; - Border selectedBorder = null; - boolean isBordered = true; - - public ColorRenderer(boolean isBordered) { - super(); - this.isBordered = isBordered; - this.setOpaque(true); //MUST do this for background to show up. - } - - public Component getTableCellRendererComponent( - JTable table, Object color, - boolean isSelected, boolean hasFocus, - int row, int column) { - this.setBackground((Color)color); - if (isBordered) { - if (isSelected) { - if (selectedBorder == null) { - selectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getSelectionBackground()); - } - this.setBorder(selectedBorder); - } else { - if (unselectedBorder == null) { - unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5, - table.getBackground()); - } - this.setBorder(unselectedBorder); - } - } - return this; - } - } - - private void setUpColorRenderer(JTable table) { - table.setDefaultRenderer(Color.class, - new ColorRenderer(true)); - } - - //Set up the editor for the Color cells. - private void setUpColorEditor(JTable table) { - //First, set up the button that brings up the dialog. - final JButton button = new JButton("") { - public void setText(String s) { - //Button never shows text -- only color. - } - }; - button.setBackground(Color.white); - button.setBorderPainted(false); - button.setMargin(new Insets(0,0,0,0)); - - //Now create an editor to encapsulate the button, and - //set it up as the editor for all Color cells. - final ColorEditor colorEditor = new ColorEditor(button); - table.setDefaultEditor(Color.class, colorEditor); - - //Set up the dialog that the button brings up. - final JColorChooser colorChooser = new JColorChooser(); - //XXX: PENDING: add the following when setPreviewPanel - //XXX: starts working. - //JComponent preview = new ColorRenderer(false); - //preview.setPreferredSize(new Dimension(50, 10)); - //colorChooser.setPreviewPanel(preview); - ActionListener okListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - colorEditor.currentColor = colorChooser.getColor(); - } - }; - final JDialog dialog = JColorChooser.createDialog(button, - "Pick a Color", - true, - colorChooser, - okListener, - null); //XXXDoublecheck this is OK - - //Here's the code that brings up the dialog. - button.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - button.setBackground(colorEditor.currentColor); - colorChooser.setColor(colorEditor.currentColor); - //Without the following line, the dialog comes up - //in the middle of the screen. - //dialog.setLocationRelativeTo(button); - dialog.show(); - } - }); - } - - /* - * The editor button that brings up the dialog. - * We extend DefaultCellEditor for convenience, - * even though it mean we have to create a dummy - * check box. Another approach would be to copy - * the implementation of TableCellEditor methods - * from the source code for DefaultCellEditor. - */ - class ColorEditor extends DefaultCellEditor { - Color currentColor = null; - - public ColorEditor(JButton b) { - super(new JCheckBox()); //Unfortunately, the constructor - //expects a check box, combo box, - //or text field. - editorComponent = b; - setClickCountToStart(1); //This is usually 1 or 2. - - //Must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - fireEditingStopped(); - } - }); - } - - protected void fireEditingStopped() { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() { - return currentColor; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) { - ((JButton)editorComponent).setText(value.toString()); - currentColor = (Color)value; - return editorComponent; - } - } - - class MethodRenderer extends JLabel - implements TableCellRenderer { - - Method theMethod = null; - JTable theTable = null; - - public MethodRenderer() { - super(); - } - - public Component getTableCellRendererComponent( - JTable table, Object method, - boolean isSelected, boolean hasFocus, - int row, int column) { - theMethod = (Method) method; - theTable = table; - setText( " " + theMethod.getReturnType().getName() ); - return this; - } - } - - private void setUpMethodRenderer(JTable table) { - table.setDefaultRenderer(Method.class, - new MethodRenderer()); - } - - /* - * We extend DefaultCellEditor for convenience, - * as with ColorEditor. - */ - class MethodEditor extends DefaultCellEditor { - Method theMethod = null; - JTable theTable = null; - - public MethodEditor(JButton b) { - super(new JCheckBox()); //Unfortunately, the constructor - //expects a check box, combo box, - //or text field. - editorComponent = b; - setClickCountToStart(1); //This is usually 1 or 2. - - //Must do this so that editing stops when appropriate. - b.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - fireEditingStopped(); - } - }); - } - - protected void fireEditingStopped() { - super.fireEditingStopped(); - } - - public Object getCellEditorValue() { - return theMethod; - } - - public Component getTableCellEditorComponent(JTable table, - Object value, - boolean isSelected, - int row, - int column) { - ((JButton)editorComponent).setText(value.toString()); - theMethod = (Method)value; - theTable = table; - return editorComponent; - } - } - - //Set up the editor for the Method cells. - private void setUpMethodEditor(PropertyEditorTable table) { - //First, set up the button that brings up the dialog. - final JButton button = new JButton("invoking method") { - public void setText(String s) { - //Button never shows text -- only color. - } - }; - button.setBackground(Color.white); - button.setBorderPainted(false); - button.setMargin(new Insets(0,0,0,0)); - - //Now create an editor to encapsulate the button, and - //set it up as the editor for all Color cells. - final MethodEditor methodEditor = new MethodEditor(button); - table.setDefaultEditor(Method.class, methodEditor); - - // handle the button-click - final PropertyEditorTable theTable = table; - button.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - - Component parent = SwingUtilities.getRoot( theTable ); - if ( parent == null ) parent = theTable; - - Cursor oldCursor = parent.getCursor(); - parent.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) ); - - Object result = null; - Object inspectedObject = ((PropertyEditorTableModel) - methodEditor.theTable.getModel()).inspectedObject; - try - { - methodEditor.theMethod.setAccessible( true ); - result = methodEditor.theMethod.invoke( - inspectedObject, (Object[])null ); - } - catch ( Exception exc ) - { - System.err.println( "PropertyEditorTable.MethodRenderer.actionPerformed: " + - "Error occurred: " + exc ); - } - theTable.methodInvoked( inspectedObject, methodEditor.theMethod, result ); - - parent.setCursor( oldCursor ); - } - }); - } + class ColorRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; -/** -* Called by the method cell editor when a method is invoked. -* @param anObject The object upon which the method was invoked. -* @param aMethod The method that was invoked. -* @param aResult The result of the method invocation; may be null. -*/ - public void methodInvoked( Object anObject, Method aMethod, Object aResult ) - { - System.out.println( aMethod.getName() + ": " + aResult ); - } -} + public ColorRenderer(boolean isBordered) { + super(); + this.isBordered = isBordered; + this.setOpaque(true); // MUST do this for background to show up. + } + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, + int row, int column) { + this.setBackground((Color) color); + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getSelectionBackground()); + } + this.setBorder(selectedBorder); + } else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getBackground()); + } + this.setBorder(unselectedBorder); + } + } + return this; + } + } + + private void setUpColorRenderer(JTable table) { + table.setDefaultRenderer(Color.class, new ColorRenderer(true)); + } + // Set up the editor for the Color cells. + private void setUpColorEditor(JTable table) { + // First, set up the button that brings up the dialog. + final JButton button = new JButton("") { + public void setText(String s) { + // Button never shows text -- only color. + } + }; + button.setBackground(Color.white); + button.setBorderPainted(false); + button.setMargin(new Insets(0, 0, 0, 0)); + + // Now create an editor to encapsulate the button, and + // set it up as the editor for all Color cells. + final ColorEditor colorEditor = new ColorEditor(button); + table.setDefaultEditor(Color.class, colorEditor); + + // Set up the dialog that the button brings up. + final JColorChooser colorChooser = new JColorChooser(); + // XXX: PENDING: add the following when setPreviewPanel + // XXX: starts working. + // JComponent preview = new ColorRenderer(false); + // preview.setPreferredSize(new Dimension(50, 10)); + // colorChooser.setPreviewPanel(preview); + ActionListener okListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + colorEditor.currentColor = colorChooser.getColor(); + } + }; + final JDialog dialog = JColorChooser.createDialog(button, "Pick a Color", true, colorChooser, okListener, null); // XXXDoublecheck + // this + // is + // OK + + // Here's the code that brings up the dialog. + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + button.setBackground(colorEditor.currentColor); + colorChooser.setColor(colorEditor.currentColor); + // Without the following line, the dialog comes up + // in the middle of the screen. + // dialog.setLocationRelativeTo(button); + dialog.show(); + } + }); + } + + /* + * The editor button that brings up the dialog. We extend DefaultCellEditor for + * convenience, even though it mean we have to create a dummy check box. Another + * approach would be to copy the implementation of TableCellEditor methods from + * the source code for DefaultCellEditor. + */ + class ColorEditor extends DefaultCellEditor { + Color currentColor = null; + + public ColorEditor(JButton b) { + super(new JCheckBox()); // Unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // This is usually 1 or 2. + + // Must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return currentColor; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, + int column) { + ((JButton) editorComponent).setText(value.toString()); + currentColor = (Color) value; + return editorComponent; + } + } + + class MethodRenderer extends JLabel implements TableCellRenderer { + + Method theMethod = null; + JTable theTable = null; + + public MethodRenderer() { + super(); + } + + public Component getTableCellRendererComponent(JTable table, Object method, boolean isSelected, + boolean hasFocus, int row, int column) { + theMethod = (Method) method; + theTable = table; + setText(" " + theMethod.getReturnType().getName()); + return this; + } + } + + private void setUpMethodRenderer(JTable table) { + table.setDefaultRenderer(Method.class, new MethodRenderer()); + } + + /* + * We extend DefaultCellEditor for convenience, as with ColorEditor. + */ + class MethodEditor extends DefaultCellEditor { + Method theMethod = null; + JTable theTable = null; + + public MethodEditor(JButton b) { + super(new JCheckBox()); // Unfortunately, the constructor + // expects a check box, combo box, + // or text field. + editorComponent = b; + setClickCountToStart(1); // This is usually 1 or 2. + + // Must do this so that editing stops when appropriate. + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEditingStopped(); + } + }); + } + + protected void fireEditingStopped() { + super.fireEditingStopped(); + } + + public Object getCellEditorValue() { + return theMethod; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, + int column) { + ((JButton) editorComponent).setText(value.toString()); + theMethod = (Method) value; + theTable = table; + return editorComponent; + } + } + + // Set up the editor for the Method cells. + private void setUpMethodEditor(PropertyEditorTable table) { + // First, set up the button that brings up the dialog. + final JButton button = new JButton("invoking method") { + public void setText(String s) { + // Button never shows text -- only color. + } + }; + button.setBackground(Color.white); + button.setBorderPainted(false); + button.setMargin(new Insets(0, 0, 0, 0)); + + // Now create an editor to encapsulate the button, and + // set it up as the editor for all Color cells. + final MethodEditor methodEditor = new MethodEditor(button); + table.setDefaultEditor(Method.class, methodEditor); + + // handle the button-click + final PropertyEditorTable theTable = table; + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + + Component parent = SwingUtilities.getRoot(theTable); + if (parent == null) + parent = theTable; + + Cursor oldCursor = parent.getCursor(); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + Object result = null; + Object inspectedObject = ((PropertyEditorTableModel) methodEditor.theTable.getModel()).inspectedObject; + try { + methodEditor.theMethod.setAccessible(true); + result = methodEditor.theMethod.invoke(inspectedObject, (Object[]) null); + } catch (Exception exc) { + System.err + .println("PropertyEditorTable.MethodRenderer.actionPerformed: " + "Error occurred: " + exc); + } + theTable.methodInvoked(inspectedObject, methodEditor.theMethod, result); + + parent.setCursor(oldCursor); + } + }); + } + + /** + * Called by the method cell editor when a method is invoked. + * + * @param anObject The object upon which the method was invoked. + * @param aMethod The method that was invoked. + * @param aResult The result of the method invocation; may be null. + */ + public void methodInvoked(Object anObject, Method aMethod, Object aResult) { + System.out.println(aMethod.getName() + ": " + aResult); + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java index f6a2a8d..f6f80f2 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/PropertyEditorTableModel.java @@ -31,30 +31,28 @@ import javax.swing.Timer; import javax.swing.table.AbstractTableModel; /** -* PropertyEditorTableModel introspects an object to facilitate -* editing it in a PropertyTable. -* -* Because the model always reflects the current state of the -* inspected object, it is useful to have a table update at -* automated intervals. By default, this feature is turned off. -* If you turn it on, you'll want to remember to turn it off -* when you're done with the table model. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener -{ + * PropertyEditorTableModel introspects an object to facilitate editing it in a + * PropertyTable. + * + * Because the model always reflects the current state of the inspected object, + * it is useful to have a table update at automated intervals. By default, this + * feature is turned off. If you turn it on, you'll want to remember to turn it + * off when you're done with the table model. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class PropertyEditorTableModel extends AbstractTableModel implements ActionListener { protected Object inspectedObject = null; final String[] columnNames = { "Property", "Value" }; - static final String METHOD_TAG = " "; + static final String METHOD_TAG = " "; Vector properties = new Vector(); Hashtable methods = new Hashtable(0); - public void setObject( Object o ) { + public void setObject(Object o) { inspectedObject = o; Class c = o.getClass(); Method[] m = c.getMethods(); @@ -62,67 +60,56 @@ public class PropertyEditorTableModel extends AbstractTableModel implements Acti properties = new Vector(); methods = new Hashtable(m.length, 1F); String name, propertyName; - for ( int i = 0; i < m.length; i++ ) { + for (int i = 0; i < m.length; i++) { // if ( m[i].getName().startsWith( "get" ) ) // System.out.println( m[i].getName() + ": " + m[i].getReturnType().getName() ); // get methods - if ( - ( ( m[i].getName().startsWith( "get" ) ) || ( m[i].getName().startsWith( "is" ) ) ) - && ( m[i].getParameterTypes().length == 0 ) - ) { - name = m[i].getName(); - if ( m[i].getName().startsWith( "is" ) ) { - propertyName = name.substring( 2, name.length() ); - // probably should only add "is" methods if accompanied by "set" method - } else { // "get" - propertyName = name.substring( 3, name.length() ); - } - if ( - ( m[i].getReturnType().getName().equals( String.class.getName() ) ) - || ( m[i].getReturnType().getName().equals( "boolean" ) ) - || ( m[i].getReturnType().getName().equals( "int" ) ) - || ( m[i].getReturnType().getName().equals( "float" ) ) - || ( m[i].getReturnType().getName().equals( "char" ) ) - || ( m[i].getReturnType().getName().equals( Color.class.getName() ) ) - || ( m[i].getReturnType().getName().equals( Font.class.getName() ) ) - ) { - properties.addElement( propertyName ); // put property - methods.put( name, m[i] ); // put method + if (((m[i].getName().startsWith("get")) || (m[i].getName().startsWith("is"))) + && (m[i].getParameterTypes().length == 0)) { + name = m[i].getName(); + if (m[i].getName().startsWith("is")) { + propertyName = name.substring(2, name.length()); + // probably should only add "is" methods if accompanied by "set" method + } else { // "get" + propertyName = name.substring(3, name.length()); } - else - { - // handle unknown types as invokable methods: - properties.addElement( propertyName + METHOD_TAG ); - methods.put( propertyName + METHOD_TAG, m[i] ); - } - } - else + if ((m[i].getReturnType().getName().equals(String.class.getName())) + || (m[i].getReturnType().getName().equals("boolean")) + || (m[i].getReturnType().getName().equals("int")) + || (m[i].getReturnType().getName().equals("float")) + || (m[i].getReturnType().getName().equals("char")) + || (m[i].getReturnType().getName().equals(Color.class.getName())) + || (m[i].getReturnType().getName().equals(Font.class.getName()))) { + properties.addElement(propertyName); // put property + methods.put(name, m[i]); // put method + } else { + // handle unknown types as invokable methods: + properties.addElement(propertyName + METHOD_TAG); + methods.put(propertyName + METHOD_TAG, m[i]); + } + } else // set methods - if ( ( m[i].getName().startsWith( "set" ) ) && - ( m[i].getParameterTypes().length == 1 ) ) { + if ((m[i].getName().startsWith("set")) && (m[i].getParameterTypes().length == 1)) { name = m[i].getName(); - if ( - ( m[i].getParameterTypes()[0].getName().equals( String.class.getName() ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "boolean" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "int" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( "float" ) ) - || ( m[i].getParameterTypes()[0].getName().equals( Color.class.getName() ) ) + if ((m[i].getParameterTypes()[0].getName().equals(String.class.getName())) + || (m[i].getParameterTypes()[0].getName().equals("boolean")) + || (m[i].getParameterTypes()[0].getName().equals("int")) + || (m[i].getParameterTypes()[0].getName().equals("float")) + || (m[i].getParameterTypes()[0].getName().equals(Color.class.getName())) // || ( m[i].getParameterTypes()[0].getName().equals( Font.class.getName() ) ) ) { // System.out.println( "Accepted: " + name + ": " + m[i].getParameterTypes()[0].getName() ); - methods.put( name, m[i] ); // set method + methods.put(name, m[i]); // set method } else { // System.out.println( "Rejected: " + name + ": " + m[i].getParameterTypes()[0].getName() ); } + } else + // zero-parameter methods to be invoked on click + if (m[i].getParameterTypes().length == 0) { + properties.addElement(m[i].getName() + METHOD_TAG); + methods.put(m[i].getName() + METHOD_TAG, m[i]); } - else - // zero-parameter methods to be invoked on click - if ( m[i].getParameterTypes().length == 0 ) - { - properties.addElement( m[i].getName() + METHOD_TAG ); - methods.put( m[i].getName() + METHOD_TAG, m[i] ); - } } @@ -131,288 +118,259 @@ public class PropertyEditorTableModel extends AbstractTableModel implements Acti } public int getColumnCount() { - return columnNames.length; + return columnNames.length; } public int getRowCount() { - return properties.size(); + return properties.size(); } public String getColumnName(int col) { - return columnNames[col]; + return columnNames[col]; } public Object getValueAt(int row, int col) { - if ( col == 0 ) - return properties.elementAt( row ); - else - { + if (col == 0) + return properties.elementAt(row); + else { Method m = null; - m = (Method) methods.get( "get" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) // try "is" - m = (Method) methods.get( "is" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) { // try entire method name - m = (Method) methods.get( (String) properties.elementAt( row ) ) ; - if ( m != null ) return m; - } + m = (Method) methods.get("get" + ((String) properties.elementAt(row))); + if (m == null) // try "is" + m = (Method) methods.get("is" + ((String) properties.elementAt(row))); + if (m == null) { // try entire method name + m = (Method) methods.get((String) properties.elementAt(row)); + if (m != null) + return m; + } try { - return m.invoke( inspectedObject, (Object[])null ); - } catch ( Exception exc ) { - System.out.println( "InspectorFrame.tableModel.getValueAt: error occured while reflecting: " ); - System.out.println( exc ); + return m.invoke(inspectedObject, (Object[]) null); + } catch (Exception exc) { + System.out.println("InspectorFrame.tableModel.getValueAt: error occured while reflecting: "); + System.out.println(exc); } return null; } } - public Class getColumnClass( int col ) { + public Class getColumnClass(int col) { // System.out.println( "getColumnClass" ); -/* try { - throw new Exception(); - } catch ( Exception exc ) { - exc.printStackTrace( System.out ); - } -*/ return new String().getClass(); + /* + * try { throw new Exception(); } catch ( Exception exc ) { exc.printStackTrace( + * System.out ); } + */ return new String().getClass(); } public Class getCellClass(int row, int col) { // System.out.println( "getCellClass" ); -/* - - Class c; - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) - c = new Object().getClass(); - else { - c = m.getParameterTypes()[0]; - - // special case for boolean - if ( c.getName().equals( "boolean" ) ) - c = new Boolean(true).getClass(); - - // special case for int - if ( c.getName().equals( "int" ) ) - c = new Integer(0).getClass(); - } - System.out.println( row + ": " + c.getName() ); - return c; -*/ + /* + * + * Class c; Method m = (Method) methods.get( "set" + ( (String) + * properties.elementAt( row ) ) ) ; if ( m == null ) c = new + * Object().getClass(); else { c = m.getParameterTypes()[0]; + * + * // special case for boolean if ( c.getName().equals( "boolean" ) ) c = new + * Boolean(true).getClass(); + * + * // special case for int if ( c.getName().equals( "int" ) ) c = new + * Integer(0).getClass(); } System.out.println( row + ": " + c.getName() ); + * return c; + */ // return new String().getClass(); - Object o = getValueAt( row, col ); - if ( o == null ) o = "null"; + Object o = getValueAt(row, col); + if (o == null) + o = "null"; return o.getClass(); } /* - * Don't need to implement this method unless your table's - * editable. - */ + * Don't need to implement this method unless your table's editable. + */ public boolean isCellEditable(int row, int col) { - //Note that the data/cell address is constant, - //no matter where the cell appears onscreen. - if (col < 1) { - return false; - } else { - // handle method invocation - if ( ((String)properties.elementAt(row)).endsWith(METHOD_TAG) ) return true; - // handle read-only properties - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; - if ( m == null ) + // Note that the data/cell address is constant, + // no matter where the cell appears onscreen. + if (col < 1) { return false; - else - return true; - } + } else { + // handle method invocation + if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) + return true; + // handle read-only properties + Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); + if (m == null) + return false; + else + return true; + } } /* - * Don't need to implement this method unless your table's - * data can change. - */ + * Don't need to implement this method unless your table's data can change. + */ public void setValueAt(Object value, int row, int col) { - // test for inspected object - if ( inspectedObject == null ) return; - // handle method invocation - no need to update values - if ( ((String)properties.elementAt(row)).endsWith( METHOD_TAG ) ) - { - fireTableDataChanged(); - return; - }; - - // handle writable properties - Method m = (Method) methods.get( "set" + ( (String) properties.elementAt( row ) ) ) ; + // test for inspected object + if (inspectedObject == null) + return; + // handle method invocation - no need to update values + if (((String) properties.elementAt(row)).endsWith(METHOD_TAG)) { + fireTableDataChanged(); + return; + } + ; + + // handle writable properties + Method m = (Method) methods.get("set" + ((String) properties.elementAt(row))); String parameterType = m.getParameterTypes()[0].getName(); // ugly cast code - if ( - ( parameterType.equals( "int" ) ) - || ( parameterType.equals( "java.lang.Integer" ) ) - ) - { - try { - value = new Integer((String)value); - } catch (NumberFormatException e) { - System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer" - + " value (" + value - + ") into an integer-only column."); - } + if ((parameterType.equals("int")) || (parameterType.equals("java.lang.Integer"))) { + try { + value = new Integer((String) value); + } catch (NumberFormatException e) { + System.out.println("PropertyEditorTableModel.setValueAt: User attempted to enter non-integer" + + " value (" + value + ") into an integer-only column."); + } } Object[] parameters = { value }; try { - m.invoke( inspectedObject, parameters ); - if ( inspectedObject instanceof Component ) { - Component c = (Component)inspectedObject; - if ( c.getParent() != null ) + m.invoke(inspectedObject, parameters); + if (inspectedObject instanceof Component) { + Component c = (Component) inspectedObject; + if (c.getParent() != null) c.getParent().repaint(); } - } catch ( Exception exc ) { - System.out.println( "PropertyEditorTableModel.setValueAt: error occured while reflecting: " ); - System.out.println( exc ); + } catch (Exception exc) { + System.out.println("PropertyEditorTableModel.setValueAt: error occured while reflecting: "); + System.out.println(exc); } fireTableDataChanged(); } + protected void sort(Vector v) { + quickSort(v, 0, v.size() - 1); + } - protected void sort(Vector v){ - quickSort(v, 0, v.size()-1); - } - - - // Liberated from the BasicDirectoryModel which was... - // Liberated from the 1.1 SortDemo - - // This is a generic version of C.A.R Hoare's Quick Sort - // algorithm. This will handle arrays that are already - // sorted, and arrays with duplicate keys.
- // - // If you think of a one dimensional array as going from - // the lowest index on the left to the highest index on the right - // then the parameters to this function are lowest index or - // left and highest index or right. The first time you call - // this function it will be with the parameters 0, a.length - 1. - // - // @param a an integer array - // @param lo0 left boundary of array partition - // @param hi0 right boundary of array partition - private void quickSort(Vector v, int lo0, int hi0) { - int lo = lo0; - int hi = hi0; - String mid; - - if (hi0 > lo0) { - // Arbitrarily establishing partition element as the midpoint of - // the array. - mid = (String) v.elementAt((lo0 + hi0) / 2); - - // loop through the array until indices cross - while(lo <= hi) { - // find the first element that is greater than or equal to - // the partition element starting from the left Index. - // - // Nasty to have to cast here. Would it be quicker - // to copy the vectors into arrays and sort the arrays? - while((lo < hi0) && lt((String)v.elementAt(lo), mid)) { - ++lo; - } + // Liberated from the BasicDirectoryModel which was... + // Liberated from the 1.1 SortDemo + + // This is a generic version of C.A.R Hoare's Quick Sort + // algorithm. This will handle arrays that are already + // sorted, and arrays with duplicate keys.
+ // + // If you think of a one dimensional array as going from + // the lowest index on the left to the highest index on the right + // then the parameters to this function are lowest index or + // left and highest index or right. The first time you call + // this function it will be with the parameters 0, a.length - 1. + // + // @param a an integer array + // @param lo0 left boundary of array partition + // @param hi0 right boundary of array partition + private void quickSort(Vector v, int lo0, int hi0) { + int lo = lo0; + int hi = hi0; + String mid; + + if (hi0 > lo0) { + // Arbitrarily establishing partition element as the midpoint of + // the array. + mid = (String) v.elementAt((lo0 + hi0) / 2); + + // loop through the array until indices cross + while (lo <= hi) { + // find the first element that is greater than or equal to + // the partition element starting from the left Index. + // + // Nasty to have to cast here. Would it be quicker + // to copy the vectors into arrays and sort the arrays? + while ((lo < hi0) && lt((String) v.elementAt(lo), mid)) { + ++lo; + } + + // find an element that is smaller than or equal to + // the partition element starting from the right Index. + while ((hi > lo0) && lt(mid, (String) v.elementAt(hi))) { + --hi; + } + + // if the indexes have not crossed, swap + if (lo <= hi) { + swap(v, lo, hi); + ++lo; + --hi; + } + } + + // If the right index has not reached the left side of array + // must now sort the left partition. + if (lo0 < hi) { + quickSort(v, lo0, hi); + } + + // If the left index has not reached the right side of array + // must now sort the right partition. + if (lo < hi0) { + quickSort(v, lo, hi0); + } - // find an element that is smaller than or equal to - // the partition element starting from the right Index. - while((hi > lo0) && lt(mid, (String)v.elementAt(hi))) { - --hi; } + } + + private void swap(Vector a, int i, int j) { + Object T = a.elementAt(i); + a.setElementAt(a.elementAt(j), i); + a.setElementAt(T, j); + } + + protected boolean lt(String a, String b) { + return a.compareTo(b) < 0; + } + + // automated updates - // if the indexes have not crossed, swap - if(lo <= hi) { - swap(v, lo, hi); - ++lo; - --hi; + private boolean autoUpdating = false; + private int updateInterval = 2000; // one-second delay on average + protected Timer timer = null; + + public boolean isAutoUpdating() { + return autoUpdating; + } + + public void setAutoUpdating(boolean shouldAutoUpdate) { + if (shouldAutoUpdate) { + if (timer == null) { + timer = new Timer(updateInterval, this); + timer.setRepeats(true); + timer.start(); + } + } else { + if (timer != null) { + timer.stop(); + timer = null; + } } - } + autoUpdating = shouldAutoUpdate; + } + + public int getUpdateInterval() { + return updateInterval; + } - // If the right index has not reached the left side of array - // must now sort the left partition. - if(lo0 < hi) { - quickSort(v, lo0, hi); - } + public void setUpdateInterval(int anInterval) { + if (timer != null) { + timer.setDelay(anInterval); + } - // If the left index has not reached the right side of array - // must now sort the right partition. - if(lo < hi0) { - quickSort(v, lo, hi0); - } + updateInterval = anInterval; + } + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == timer) { + fireTableDataChanged(); + } } - } - - private void swap(Vector a, int i, int j) { - Object T = a.elementAt(i); - a.setElementAt(a.elementAt(j), i); - a.setElementAt(T, j); - } - - protected boolean lt(String a, String b) { - return a.compareTo(b) < 0; - } - - // automated updates - - private boolean autoUpdating = false; - private int updateInterval = 2000; // one-second delay on average - protected Timer timer = null; - - public boolean isAutoUpdating() - { - return autoUpdating; - } - - public void setAutoUpdating( boolean shouldAutoUpdate ) - { - if ( shouldAutoUpdate ) - { - if ( timer == null ) - { - timer = new Timer( updateInterval, this ); - timer.setRepeats( true ); - timer.start(); - } - } - else - { - if ( timer != null ) - { - timer.stop(); - timer = null; - } - } - - autoUpdating = shouldAutoUpdate; - } - - public int getUpdateInterval() - { - return updateInterval; - } - - public void setUpdateInterval( int anInterval ) - { - if ( timer != null ) - { - timer.setDelay( anInterval ); - } - - updateInterval = anInterval; - } - - public void actionPerformed( ActionEvent evt ) - { - if ( evt.getSource() == timer ) - { - fireTableDataChanged(); - } - } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java index 2956c71..62cbc2c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/RadioButtonPanel.java @@ -26,149 +26,139 @@ import javax.swing.JRadioButton; import javax.swing.border.EmptyBorder; /** -* RadioButtonPanel is a simple extension of ButtonPanel. -* Differences are that it uses radio buttons and the -* default alignment is vertical. The radio buttons are -* placed in a ButtonGroup and the panel defaults to having -* no buttons selected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class RadioButtonPanel extends ButtonPanel -{ -/** -* A ButtonGroup to help manage button state. -*/ - protected ButtonGroup buttonGroup; - -/** -* ButtonGroup does not make it easy to unselect all buttons. -* The preferred way to do it is actually to create a hidden button. -*/ - protected JRadioButton hiddenButton; - -/** -* Constructs a RadioButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public RadioButtonPanel() - { - super(); - } - -/** -* Constructs a ButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public RadioButtonPanel( String[] buttonList ) - { - super( buttonList ); - } - -/** -* Overridden to set vertical-center alignment and 2-pixel vgap. -*/ - protected void initLayout() - { - super.initLayout(); - buttonPanelLayout.setAlignment( BetterFlowLayout.CENTER_VERTICAL ); - buttonPanelLayout.setVgap( 2 ); // looks nicer than java l&f recommendation (imho) - } - -/** -* Overridden to return a JRadioButton. -* @param aLabel The label for the component that will be created. -* @return The newly created component. -*/ - protected Component createComponentWithLabel( String aLabel ) - { - String buttonLabel = aLabel; - JRadioButton newButton = new JRadioButton(); - newButton.setName( aLabel ); - newButton.setText( buttonLabel ); - newButton.setActionCommand( aLabel ); - newButton.addActionListener( this ); - - // reduce insets per java l&f guidelines (was 4 on each side) - newButton.setBorder( new EmptyBorder( 0, 4, 0, 4 ) ); - - if ( buttonGroup == null ) - { - buttonGroup = new ButtonGroup(); - - // cheesy hack to allow a buttongroup to have no items selected. - // note that the button is not added to container or buttonList. - hiddenButton = new JRadioButton( "Hidden Button" ); - buttonGroup.add( hiddenButton ); - } - buttonGroup.add( newButton ); - - return newButton; - } - -/** -* Selects the button whose name matches the given text value. -* @param newText A String matching the name of one of the buttons. -* If null, empty, or not matching, all buttons are deselected. -*/ - public void setValue(String aName) - { - if ( aName != null ) - { - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( c instanceof AbstractButton ) - { - if ( c.getName().equals( aName ) ) - { - ((AbstractButton)c).setSelected( true ); - return; - } - } - } - } - - // null, empty, or not matching - deselect all - hiddenButton.setSelected( true ); - } - -/** -* Gets the name of the currently selected button. -* @return A string matching the name of the currently selected button, -* or null of no button is selected. -*/ - public String getValue() - { - String result = null; - Component c = null; - int count = buttonContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = buttonContainer.getComponent( i ); - if ( ( c instanceof AbstractButton ) && ( ((AbstractButton)c).isSelected() ) ) - { - return c.getName(); - } - } - return result; - } - -/** -* Tests whether the specified value is checked. -* @param aValue A value to be tested. -* @return True if the specified value is checked, otherwise false. -*/ - public boolean getValue( String aValue ) - { - if ( aValue == null ) return false; - return aValue.equals( getValue() ); - } + * RadioButtonPanel is a simple extension of ButtonPanel. Differences are that + * it uses radio buttons and the default alignment is vertical. The radio + * buttons are placed in a ButtonGroup and the panel defaults to having no + * buttons selected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class RadioButtonPanel extends ButtonPanel { + /** + * A ButtonGroup to help manage button state. + */ + protected ButtonGroup buttonGroup; + + /** + * ButtonGroup does not make it easy to unselect all buttons. The preferred way + * to do it is actually to create a hidden button. + */ + protected JRadioButton hiddenButton; + + /** + * Constructs a RadioButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public RadioButtonPanel() { + super(); + } + + /** + * Constructs a ButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public RadioButtonPanel(String[] buttonList) { + super(buttonList); + } + + /** + * Overridden to set vertical-center alignment and 2-pixel vgap. + */ + protected void initLayout() { + super.initLayout(); + buttonPanelLayout.setAlignment(BetterFlowLayout.CENTER_VERTICAL); + buttonPanelLayout.setVgap(2); // looks nicer than java l&f recommendation (imho) + } + + /** + * Overridden to return a JRadioButton. + * + * @param aLabel The label for the component that will be created. + * @return The newly created component. + */ + protected Component createComponentWithLabel(String aLabel) { + String buttonLabel = aLabel; + JRadioButton newButton = new JRadioButton(); + newButton.setName(aLabel); + newButton.setText(buttonLabel); + newButton.setActionCommand(aLabel); + newButton.addActionListener(this); + + // reduce insets per java l&f guidelines (was 4 on each side) + newButton.setBorder(new EmptyBorder(0, 4, 0, 4)); + + if (buttonGroup == null) { + buttonGroup = new ButtonGroup(); + + // cheesy hack to allow a buttongroup to have no items selected. + // note that the button is not added to container or buttonList. + hiddenButton = new JRadioButton("Hidden Button"); + buttonGroup.add(hiddenButton); + } + buttonGroup.add(newButton); + + return newButton; + } + + /** + * Selects the button whose name matches the given text value. + * + * @param newText A String matching the name of one of the buttons. If null, + * empty, or not matching, all buttons are deselected. + */ + public void setValue(String aName) { + if (aName != null) { + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if (c instanceof AbstractButton) { + if (c.getName().equals(aName)) { + ((AbstractButton) c).setSelected(true); + return; + } + } + } + } + + // null, empty, or not matching - deselect all + hiddenButton.setSelected(true); + } + + /** + * Gets the name of the currently selected button. + * + * @return A string matching the name of the currently selected button, or null + * of no button is selected. + */ + public String getValue() { + String result = null; + Component c = null; + int count = buttonContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = buttonContainer.getComponent(i); + if ((c instanceof AbstractButton) && (((AbstractButton) c).isSelected())) { + return c.getName(); + } + } + return result; + } + + /** + * Tests whether the specified value is checked. + * + * @param aValue A value to be tested. + * @return True if the specified value is checked, otherwise false. + */ + public boolean getValue(String aValue) { + if (aValue == null) + return false; + return aValue.equals(getValue()); + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java index 6914cf6..55ba2f7 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartPasswordField.java @@ -26,249 +26,227 @@ import java.awt.event.KeyEvent; import javax.swing.JPasswordField; /** - * SmartPasswordField is an extention of JPasswordField. It does everything - * a JPassword does, as well as limit the number of characters. The user - * of this class can specify that a password can only have a maximum of - * 10 characters for instance. + * SmartPasswordField is an extention of JPasswordField. It does everything a + * JPassword does, as well as limit the number of characters. The user of this + * class can specify that a password can only have a maximum of 10 characters + * for instance. * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public class SmartPasswordField extends JPasswordField -{ - -/******************************* -* CONSTANTS -*******************************/ - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int SPACE = 32; - private static final int DASH = 45; - private static final int UNDERSCORE = 95; - private static final int PERIOD = 46; - private static final int PASTE = 22; // Ctl-V - - private int passwordLength = Integer.MAX_VALUE; - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* Default constructor. -*/ - public SmartPasswordField() - { - super(); - } - -/** -* This constructor allows the user to set the maximum length of the password. -* @param aLength The maximum length of the password. -*/ - public SmartPasswordField( int aLength ) - { - this(); - setPasswordLength( aLength ); - } - -/** -* Sets the maximum lenght of the password. The value must be 0 or greater. -* If the length specified is less than 0, then no action occurs. -* @param aLength The maximum lenght of the password. -*/ - public void setPasswordLength( int aLength ) - { - if ( aLength >= 0 ) - { - passwordLength = aLength; - } - } - -/** -* Returns the current maximum length of the password. -* @return The current maximum length of the password. -*/ - public int getPasswordLength() - { - return passwordLength; - } - -/** -* This method processes a key event. This event is generated by input from the -* keyboard when this text field has the focus. This method is called for every -* key that is pressed and released on the keyboard. This includes modifier -* keys like the shift and alt keys. This class looks at the key and determines -* if the key is valid input given the restrictions of this class.

-* @param e A key event generated by a keyboard action. -*/ - public void processKeyEvent(KeyEvent e) - { - String currentText = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int selectionStart = 0; - int selectionEnd = 0; - int endOfHead = 0; - int startOfTail = 0; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean insertionPoint = false; - boolean selectionAtStart = false; - boolean selectionAtEnd = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - - if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) - { - // Analyze the current contents of the field - currentText = new String( getPassword() ); - currentLength = currentText.length(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - insertionPoint = (selectionStart == selectionEnd); - selectionAtStart = (selectionStart == 0); - selectionAtEnd = (selectionEnd >= currentLength); - if (selectionEnd > currentLength) - { - setSelectionEnd(currentLength); - } - - // Generate new string - if (selectionStart > 0) // Create head of test string - { - endOfHead = selectionStart; - if (insertionPoint && backspace) - { - endOfHead -= 1; - } - testString += currentText.substring(0, endOfHead); - } - - if (!(backspace || delete || paste)) // Add the new character - { - testString += newChar; - } - - if (paste) // Add the string from the clipboard - { - Transferable data = getToolkit().getSystemClipboard().getContents(this); - if (data != null) - { - try - { - String clipString = (String)data.getTransferData(DataFlavor.stringFlavor); - testString += clipString; - } - catch (java.io.IOException ioe) - { - // Do nothing - } - catch (UnsupportedFlavorException ufe) - { - // Do nothing - } - } - } - - if (selectionEnd < currentLength) // Add the tail of the string - { - startOfTail = selectionEnd; - if (insertionPoint && delete) - { - startOfTail += 1; - } - testString += currentText.substring(startOfTail); - } - - } - - if (testString.compareTo("") != 0) // Null string is OK - { - if (!(isValidString(testString))) - { - e.consume(); - } - } - } - else - { - e.consume(); - } - } - super.processKeyEvent(e); - - postProcessing(); - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - -/** -* Returns whether the inputted character is valid or not. In this case all -* characters are valid input. -* @param aChar A character to perform the validity test with. -* @return True if the character is valid for this subclassed text field.
-* False is the character is not valid. -*/ - protected boolean isValidCharacter(char aChar) - { - return true; - } - -/** -* Returns whether a string is valid for this text field. As the user types from -* the keyboard, this method is called to determine if the new string in the text -* field is valid based upon the restriction of this class. The length of the -* new string is checked against the maximum password length. -* @param aString The string to perform the validity check with. -* @return True if the length of the string is less than or equal to the maximum length. -* False if the character is not valud. -*/ - protected boolean isValidString(String aString) - { - if ( aString.length() > passwordLength ) - { - return false; - } - - return true; - } - -/** -* This class does not need any post processing. -*/ - protected void postProcessing() - { - /* Do Nothing */ - } - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } +public class SmartPasswordField extends JPasswordField { + + /******************************* + * CONSTANTS + *******************************/ + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int SPACE = 32; + private static final int DASH = 45; + private static final int UNDERSCORE = 95; + private static final int PERIOD = 46; + private static final int PASTE = 22; // Ctl-V + + private int passwordLength = Integer.MAX_VALUE; + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * Default constructor. + */ + public SmartPasswordField() { + super(); + } + + /** + * This constructor allows the user to set the maximum length of the password. + * + * @param aLength The maximum length of the password. + */ + public SmartPasswordField(int aLength) { + this(); + setPasswordLength(aLength); + } + + /** + * Sets the maximum lenght of the password. The value must be 0 or greater. If + * the length specified is less than 0, then no action occurs. + * + * @param aLength The maximum lenght of the password. + */ + public void setPasswordLength(int aLength) { + if (aLength >= 0) { + passwordLength = aLength; + } + } + + /** + * Returns the current maximum length of the password. + * + * @return The current maximum length of the password. + */ + public int getPasswordLength() { + return passwordLength; + } + + /** + * This method processes a key event. This event is generated by input from the + * keyboard when this text field has the focus. This method is called for every + * key that is pressed and released on the keyboard. This includes modifier keys + * like the shift and alt keys. This class looks at the key and determines if + * the key is valid input given the restrictions of this class.
+ *
+ * + * @param e A key event generated by a keyboard action. + */ + public void processKeyEvent(KeyEvent e) { + String currentText = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int selectionStart = 0; + int selectionEnd = 0; + int endOfHead = 0; + int startOfTail = 0; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean insertionPoint = false; + boolean selectionAtStart = false; + boolean selectionAtEnd = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event + { + if (isValidCharacter(newChar)) { + + if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) { + // Analyze the current contents of the field + currentText = new String(getPassword()); + currentLength = currentText.length(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + insertionPoint = (selectionStart == selectionEnd); + selectionAtStart = (selectionStart == 0); + selectionAtEnd = (selectionEnd >= currentLength); + if (selectionEnd > currentLength) { + setSelectionEnd(currentLength); + } + + // Generate new string + if (selectionStart > 0) // Create head of test string + { + endOfHead = selectionStart; + if (insertionPoint && backspace) { + endOfHead -= 1; + } + testString += currentText.substring(0, endOfHead); + } + + if (!(backspace || delete || paste)) // Add the new character + { + testString += newChar; + } + + if (paste) // Add the string from the clipboard + { + Transferable data = getToolkit().getSystemClipboard().getContents(this); + if (data != null) { + try { + String clipString = (String) data.getTransferData(DataFlavor.stringFlavor); + testString += clipString; + } catch (java.io.IOException ioe) { + // Do nothing + } catch (UnsupportedFlavorException ufe) { + // Do nothing + } + } + } + + if (selectionEnd < currentLength) // Add the tail of the string + { + startOfTail = selectionEnd; + if (insertionPoint && delete) { + startOfTail += 1; + } + testString += currentText.substring(startOfTail); + } + + } + + if (testString.compareTo("") != 0) // Null string is OK + { + if (!(isValidString(testString))) { + e.consume(); + } + } + } else { + e.consume(); + } + } + super.processKeyEvent(e); + + postProcessing(); + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + /** + * Returns whether the inputted character is valid or not. In this case all + * characters are valid input. + * + * @param aChar A character to perform the validity test with. + * @return True if the character is valid for this subclassed text field.
+ * False is the character is not valid. + */ + protected boolean isValidCharacter(char aChar) { + return true; + } + + /** + * Returns whether a string is valid for this text field. As the user types from + * the keyboard, this method is called to determine if the new string in the + * text field is valid based upon the restriction of this class. The length of + * the new string is checked against the maximum password length. + * + * @param aString The string to perform the validity check with. + * @return True if the length of the string is less than or equal to the maximum + * length. False if the character is not valud. + */ + protected boolean isValidString(String aString) { + if (aString.length() > passwordLength) { + return false; + } + + return true; + } + + /** + * This class does not need any post processing. + */ + protected void postProcessing() { + /* Do Nothing */ + } + + /******************************* + * PRIVATE METHODS + *******************************/ + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java index cee37e1..5092179 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/SmartTextField.java @@ -27,218 +27,204 @@ import javax.swing.JTextField; /** * SmartTextField is an abstract class for that allows the text field to - * intelligently analyze the user's input in real-time. As the user enters + * intelligently analyze the user's input in real-time. As the user enters * keystrokes, the generated string is analyzed to determine if the new string * is valid based on the criteria of the concrete classes that extend this - * class. An invalid keystroke is rejected and not displayed in the text - * field. This class can be extended to to create smart text fields that only - * accept integers or floating points number or alphabetic strings of maximum - * length. These are several examples. + * class. An invalid keystroke is rejected and not displayed in the text field. + * This class can be extended to to create smart text fields that only accept + * integers or floating points number or alphabetic strings of maximum length. + * These are several examples. * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 904 $ */ -public abstract class SmartTextField extends JTextField -{ - -/******************************* -* CONSTANTS -*******************************/ - private static final int BACKSPACE = 8; - private static final int DELETE = 127; - private static final int SPACE = 32; - private static final int DASH = 45; - private static final int UNDERSCORE = 95; - private static final int PERIOD = 46; - private static final int PASTE = 22; // Ctl-V - - -/******************************* -* PUBLIC METHODS -*******************************/ - -/** -* This method processes a key event. This event is generated by input from the -* keyboard when this text field has the focus. This method is called for every -* key that is pressed and released on the keyboard. This includes modifier -* keys like the shift and alt keys. This class looks at the key and determines -* if the key is valid input given the restrictions of the concrete sub-classes.

-* Example - A smart text field only allows alphabetic characters. If the key -* pressed is a "2" then this method will determine that the key is invalid and -* "consume" the key event.

-* Note - Every printable character has a "TYPED" key event. Currentlt under -* Java 1.2.1 this does not happen. Bug 4186905 relating this bug has been -* fixed and is awaiting release. -* @param e A key event generated by a keyboard action. -*/ - public void processKeyEvent(KeyEvent e) - { - String currentText = ""; - String testString = ""; - char newChar = e.getKeyChar(); - int currentLength = 0; - int selectionStart = 0; - int selectionEnd = 0; - int endOfHead = 0; - int startOfTail = 0; - boolean backspace = false; - boolean delete = false; - boolean paste = false; - boolean insertionPoint = false; - boolean selectionAtStart = false; - boolean selectionAtEnd = false; - - backspace = (newChar == BACKSPACE); - delete = (newChar == DELETE); - paste = (newChar == PASTE); - - if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event - { - if (isValidCharacter(newChar)) - { - - if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) - { - // Analyze the current contents of the field - currentText = getText(); - currentLength = currentText.length(); - - selectionStart = getSelectionStart(); - selectionEnd = getSelectionEnd(); - - insertionPoint = (selectionStart == selectionEnd); - selectionAtStart = (selectionStart == 0); - selectionAtEnd = (selectionEnd >= currentLength); - if (selectionEnd > currentLength) - { - setSelectionEnd(currentLength); - } - - // Generate new string - if (selectionStart > 0) // Create head of test string - { - endOfHead = selectionStart; - if (insertionPoint && backspace) - { - endOfHead -= 1; - } - testString += currentText.substring(0, endOfHead); - } - - if (!(backspace || delete || paste)) // Add the new character - { - testString += newChar; - } - - if (paste) // Add the string from the clipboard - { - Transferable data = getToolkit().getSystemClipboard().getContents(this); - if (data != null) - { - try - { - String clipString = (String)data.getTransferData(DataFlavor.stringFlavor); - testString += clipString; - } - catch (java.io.IOException ioe) - { - // Do nothing - } - catch (UnsupportedFlavorException ufe) - { - // Do nothing - } - } - } - - if (selectionEnd < currentLength) // Add the tail of the string - { - startOfTail = selectionEnd; - if (insertionPoint && delete) - { - startOfTail += 1; - } - testString += currentText.substring(startOfTail); - } - - } - - if (testString.compareTo("") != 0) // Null string is OK - { - if (!(isValidString(testString))) - { - e.consume(); - } - } - } - else - { - e.consume(); - } - } - super.processKeyEvent(e); - - postProcessing(); - } - - -/******************************* -* PROTECTED METHODS -*******************************/ - -/** -* Default constructor for this class. The initial text of the smart text field -* can be specified as well as the size (in characters) of the text field. -* @param text The initial string that is displayed in the text field. -* @param columns THe width of the text field in characters. -*/ - protected SmartTextField(String text, int columns) - { - super(text, columns); - } - -/** -* Returns whether a character is valid for this text field. As the user types -* from the keyboard, this method is called to determine if the character is a -* valid character based in the restrictions of the subclass. -* @param aChar A character to perform the validity test with. -* @return True if the character is valid for this subclassed text field.
-* False is the character is not valid. -*/ - abstract protected boolean isValidCharacter(char aChar); - -/** -* Returns whether a string is valid for this text field. As the user types from -* the keyboard, this method is called to determine if the new string in the text -* field is valid based upon the restriction of the subclass. This is done after -* the character has been determined to be valid since there can be restrictions -* placed on the text string as a whole, such a maximum length or date format. -* @param aString The string to perform the validity check with. -* @return True if the string is valid for this subclassed text field.
-* False if the character is not valud. -*/ - abstract protected boolean isValidString(String aString); - -/** -* This method is used by the any subclass that need to complete any processing -* of the text string in the text field after all the requirement checks have -* been performed. -*/ - abstract protected void postProcessing(); - - -/******************************* -* PRIVATE METHODS -*******************************/ - - private boolean isPrintableCharacter(char inputChar) - { - if ((inputChar >= ' ') && (inputChar <= '~')) - { - return true; - } - return false; - } +public abstract class SmartTextField extends JTextField { + + /******************************* + * CONSTANTS + *******************************/ + private static final int BACKSPACE = 8; + private static final int DELETE = 127; + private static final int SPACE = 32; + private static final int DASH = 45; + private static final int UNDERSCORE = 95; + private static final int PERIOD = 46; + private static final int PASTE = 22; // Ctl-V + + /******************************* + * PUBLIC METHODS + *******************************/ + + /** + * This method processes a key event. This event is generated by input from the + * keyboard when this text field has the focus. This method is called for every + * key that is pressed and released on the keyboard. This includes modifier keys + * like the shift and alt keys. This class looks at the key and determines if + * the key is valid input given the restrictions of the concrete sub-classes. + *
+ *
+ * Example - A smart text field only allows alphabetic characters. If the key + * pressed is a "2" then this method will determine that the key is invalid and + * "consume" the key event.
+ *
+ * Note - Every printable character has a "TYPED" key event. Currentlt under + * Java 1.2.1 this does not happen. Bug 4186905 relating this bug has been fixed + * and is awaiting release. + * + * @param e A key event generated by a keyboard action. + */ + public void processKeyEvent(KeyEvent e) { + String currentText = ""; + String testString = ""; + char newChar = e.getKeyChar(); + int currentLength = 0; + int selectionStart = 0; + int selectionEnd = 0; + int endOfHead = 0; + int startOfTail = 0; + boolean backspace = false; + boolean delete = false; + boolean paste = false; + boolean insertionPoint = false; + boolean selectionAtStart = false; + boolean selectionAtEnd = false; + + backspace = (newChar == BACKSPACE); + delete = (newChar == DELETE); + paste = (newChar == PASTE); + + if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste))) // A "key-typed" event + { + if (isValidCharacter(newChar)) { + + if ((isPrintableCharacter(newChar)) || (backspace) || (delete) || (paste)) { + // Analyze the current contents of the field + currentText = getText(); + currentLength = currentText.length(); + + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + + insertionPoint = (selectionStart == selectionEnd); + selectionAtStart = (selectionStart == 0); + selectionAtEnd = (selectionEnd >= currentLength); + if (selectionEnd > currentLength) { + setSelectionEnd(currentLength); + } + + // Generate new string + if (selectionStart > 0) // Create head of test string + { + endOfHead = selectionStart; + if (insertionPoint && backspace) { + endOfHead -= 1; + } + testString += currentText.substring(0, endOfHead); + } + + if (!(backspace || delete || paste)) // Add the new character + { + testString += newChar; + } + + if (paste) // Add the string from the clipboard + { + Transferable data = getToolkit().getSystemClipboard().getContents(this); + if (data != null) { + try { + String clipString = (String) data.getTransferData(DataFlavor.stringFlavor); + testString += clipString; + } catch (java.io.IOException ioe) { + // Do nothing + } catch (UnsupportedFlavorException ufe) { + // Do nothing + } + } + } + + if (selectionEnd < currentLength) // Add the tail of the string + { + startOfTail = selectionEnd; + if (insertionPoint && delete) { + startOfTail += 1; + } + testString += currentText.substring(startOfTail); + } + + } + + if (testString.compareTo("") != 0) // Null string is OK + { + if (!(isValidString(testString))) { + e.consume(); + } + } + } else { + e.consume(); + } + } + super.processKeyEvent(e); + + postProcessing(); + } + + /******************************* + * PROTECTED METHODS + *******************************/ + + /** + * Default constructor for this class. The initial text of the smart text field + * can be specified as well as the size (in characters) of the text field. + * + * @param text The initial string that is displayed in the text field. + * @param columns THe width of the text field in characters. + */ + protected SmartTextField(String text, int columns) { + super(text, columns); + } + + /** + * Returns whether a character is valid for this text field. As the user types + * from the keyboard, this method is called to determine if the character is a + * valid character based in the restrictions of the subclass. + * + * @param aChar A character to perform the validity test with. + * @return True if the character is valid for this subclassed text field.
+ * False is the character is not valid. + */ + abstract protected boolean isValidCharacter(char aChar); + + /** + * Returns whether a string is valid for this text field. As the user types from + * the keyboard, this method is called to determine if the new string in the + * text field is valid based upon the restriction of the subclass. This is done + * after the character has been determined to be valid since there can be + * restrictions placed on the text string as a whole, such a maximum length or + * date format. + * + * @param aString The string to perform the validity check with. + * @return True if the string is valid for this subclassed text field.
+ * False if the character is not valud. + */ + abstract protected boolean isValidString(String aString); + + /** + * This method is used by the any subclass that need to complete any processing + * of the text string in the text field after all the requirement checks have + * been performed. + */ + abstract protected void postProcessing(); + + /******************************* + * PRIVATE METHODS + *******************************/ + + private boolean isPrintableCharacter(char inputChar) { + if ((inputChar >= ' ') && (inputChar <= '~')) { + return true; + } + return false; + } } diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java index 3d9a85b..ba9f361 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/StatusButtonPanel.java @@ -34,243 +34,228 @@ import javax.swing.UIManager; import javax.swing.border.EmptyBorder; /** -* StatusButtonPanel extends ButtonPanel to provide a space -* to display status messages in a consistent manner.

-* Messages are erased after a certain predefined interval, -* defaulting to 10 seconds. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class StatusButtonPanel extends ButtonPanel -{ -/** -* This is the action command to all listeners when the status text is changed. -*/ - public static final String STATUS_CHANGED = "STATUS_CHANGED"; - - // note: weirdness happens if you initialize - // this variable. Because it is set by initLayout - // and initLayout is called by the superclass constructor, - // this variable would get initialized after initLayout - // is called... - protected Component statusComponent; // = null; - - protected Timer timer = null; - protected int interval = 10000; // adjust as needed - -/** -* Constructs a StatusButtonPanel. Three buttons are created -* so the panel is filled when used in a GUI-builder environment. -*/ - public StatusButtonPanel() - { - super(); - setupTimer(); - } - -/** -* Constructs a StatusButtonPanel using specified buttons. -* @param buttonList An array containing the strings to be used in labeling the buttons. -*/ - public StatusButtonPanel( String[] buttonList ) - { - super( buttonList ); - setupTimer(); - } - -/** -* Initializes the timer instance variable. -*/ - protected void setupTimer() - { - timer = new Timer( interval, this ); - timer.addActionListener( this ); - timer.setRepeats( false ); - timer.start(); - } - -/** -* Returns the number of milliseconds before the status message is cleared. -* The default is 10000. -* @return The current delay interval in milliseconds. -*/ - public int getDelayInterval() - { - return interval; - } - -/** -* Sets the number of milliseconds before the status message is cleared. -* @param millis The new delay interval in milliseconds. -*/ - public void setDelayInterval( int millis ) - { - interval = millis; - timer.setDelay( interval ); - } - -/** -* Returns the visual component used to display the status. -* @return A component used for displaying status. -*/ - public Component getStatusComponent() - { - return statusComponent; - - } -/** -* Receives ActionEvents from the internal timer. -* @param e The action event in question. -*/ - public void actionPerformed(ActionEvent e) - { - if ( e.getSource() == timer ) - { - setText( "" ); - return; - } - - // otherwise continue with superclass implementation - super.actionPerformed( e ); - } - -/** -* This method is responsible for the initial layout of the panel. -* Subclasses can implement different layouts, but this method -* is responsible for initializing buttonPanelLayout to a valid -* layout manager and setting this panel to use it. This method -* must should initialize statusComponent to a component that ideally -* has get/setText methods, although this is not required. -*/ - protected void initLayout() - { - - statusComponent = new JTextField(); - JTextField textField = (JTextField) statusComponent; - textField.setColumns( 20 ); - textField.setBackground( getBackground() ); - textField.setEditable( false ); + * StatusButtonPanel extends ButtonPanel to provide a space to display status + * messages in a consistent manner.
+ *
+ * Messages are erased after a certain predefined interval, defaulting to 10 + * seconds. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class StatusButtonPanel extends ButtonPanel { + /** + * This is the action command to all listeners when the status text is changed. + */ + public static final String STATUS_CHANGED = "STATUS_CHANGED"; + + // note: weirdness happens if you initialize + // this variable. Because it is set by initLayout + // and initLayout is called by the superclass constructor, + // this variable would get initialized after initLayout + // is called... + protected Component statusComponent; // = null; + + protected Timer timer = null; + protected int interval = 10000; // adjust as needed + + /** + * Constructs a StatusButtonPanel. Three buttons are created so the panel is + * filled when used in a GUI-builder environment. + */ + public StatusButtonPanel() { + super(); + setupTimer(); + } + + /** + * Constructs a StatusButtonPanel using specified buttons. + * + * @param buttonList An array containing the strings to be used in labeling the + * buttons. + */ + public StatusButtonPanel(String[] buttonList) { + super(buttonList); + setupTimer(); + } + + /** + * Initializes the timer instance variable. + */ + protected void setupTimer() { + timer = new Timer(interval, this); + timer.addActionListener(this); + timer.setRepeats(false); + timer.start(); + } + + /** + * Returns the number of milliseconds before the status message is cleared. The + * default is 10000. + * + * @return The current delay interval in milliseconds. + */ + public int getDelayInterval() { + return interval; + } + + /** + * Sets the number of milliseconds before the status message is cleared. + * + * @param millis The new delay interval in milliseconds. + */ + public void setDelayInterval(int millis) { + interval = millis; + timer.setDelay(interval); + } + + /** + * Returns the visual component used to display the status. + * + * @return A component used for displaying status. + */ + public Component getStatusComponent() { + return statusComponent; + + } + + /** + * Receives ActionEvents from the internal timer. + * + * @param e The action event in question. + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == timer) { + setText(""); + return; + } + + // otherwise continue with superclass implementation + super.actionPerformed(e); + } + + /** + * This method is responsible for the initial layout of the panel. Subclasses + * can implement different layouts, but this method is responsible for + * initializing buttonPanelLayout to a valid layout manager and setting this + * panel to use it. This method must should initialize statusComponent to a + * component that ideally has get/setText methods, although this is not + * required. + */ + protected void initLayout() { + + statusComponent = new JTextField(); + JTextField textField = (JTextField) statusComponent; + textField.setColumns(20); + textField.setBackground(getBackground()); + textField.setEditable(false); // statusComponent = new PickListPanel(); // for testing - this.setLayout( new GridBagLayout() ); - - GridBagConstraints gbc = - new GridBagConstraints(); - gbc.gridx = GridBagConstraints.RELATIVE; - gbc.gridy = GridBagConstraints.RELATIVE; - gbc.gridwidth = 1; - gbc.gridheight = 1; - gbc.weightx = 1.0; - gbc.weighty = 0.0; - gbc.anchor = GridBagConstraints.CENTER; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(0, 5, 0, 10); - gbc.ipadx = 0; - gbc.ipady = 0; + this.setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = GridBagConstraints.RELATIVE; + gbc.gridy = GridBagConstraints.RELATIVE; + gbc.gridwidth = 1; + gbc.gridheight = 1; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(0, 5, 0, 10); + gbc.ipadx = 0; + gbc.ipady = 0; //1.2 new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, //1.2 GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 10), 0, 0 ); - this.add( statusComponent, gbc ); - - buttonContainer = new JPanel(); - buttonPanelLayout = new BetterFlowLayout(); - buttonContainer.setLayout(buttonPanelLayout); - buttonPanelLayout.setAlignment( BetterFlowLayout.RIGHT ); - ((BetterFlowLayout)buttonPanelLayout).setWidthUniform( true ); - gbc.weightx = 0.0; - gbc.insets = new Insets( 0, 0, 0, 0 ); - this.add( buttonContainer, gbc ); - } - -/** -* Sets the text to appear in the status area. -* @param newText A string to appear in the status area. Nulls are allowed. -*/ - public void setText(String newText) - { - // TODO: should use property introspection instead - - // use reflection to call the "setText" method, if any. - try - { - Class c = statusComponent.getClass(); - Method m = c.getMethod( "setText", new Class[] { new String().getClass() } ); - m.invoke( statusComponent, new Object[] { newText } ); - broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATUS_CHANGED ) ); - statusComponent.paint( statusComponent.getGraphics() ); - } - catch ( Exception exc ) - { - // "setText" method does not exist; do nothing. - } - - // if non-empty string, start the timer - if ( ! "".equals( newText ) ) - { - timer.restart(); - } - } - -/** -* Gets the text in the status area. -* @return The string being displayed in the status area. -*/ - public String getText() - { - // TODO: should use property introspection instead - - String value = ""; - // use reflection to call the "setText" method, if any. - try - { - Class c = statusComponent.getClass(); - Method m = c.getMethod( "getText", (Class[])null ); - value = (String) m.invoke( statusComponent, (Object[])null ); - } - catch ( Exception exc ) - { - // "getText" method does not exist; do nothing. - } - return value; - } - - // for testing - - public static void main( String[] argv ) - { - try - { - UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); - } - catch (Exception exc) - { - - } - - JFrame dialog = new JFrame(); - BorderLayout bl = new BorderLayout( 20, 20 ); + this.add(statusComponent, gbc); + + buttonContainer = new JPanel(); + buttonPanelLayout = new BetterFlowLayout(); + buttonContainer.setLayout(buttonPanelLayout); + buttonPanelLayout.setAlignment(BetterFlowLayout.RIGHT); + ((BetterFlowLayout) buttonPanelLayout).setWidthUniform(true); + gbc.weightx = 0.0; + gbc.insets = new Insets(0, 0, 0, 0); + this.add(buttonContainer, gbc); + } + + /** + * Sets the text to appear in the status area. + * + * @param newText A string to appear in the status area. Nulls are allowed. + */ + public void setText(String newText) { + // TODO: should use property introspection instead + + // use reflection to call the "setText" method, if any. + try { + Class c = statusComponent.getClass(); + Method m = c.getMethod("setText", new Class[] { new String().getClass() }); + m.invoke(statusComponent, new Object[] { newText }); + broadcastEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, STATUS_CHANGED)); + statusComponent.paint(statusComponent.getGraphics()); + } catch (Exception exc) { + // "setText" method does not exist; do nothing. + } + + // if non-empty string, start the timer + if (!"".equals(newText)) { + timer.restart(); + } + } + + /** + * Gets the text in the status area. + * + * @return The string being displayed in the status area. + */ + public String getText() { + // TODO: should use property introspection instead + + String value = ""; + // use reflection to call the "setText" method, if any. + try { + Class c = statusComponent.getClass(); + Method m = c.getMethod("getText", (Class[]) null); + value = (String) m.invoke(statusComponent, (Object[]) null); + } catch (Exception exc) { + // "getText" method does not exist; do nothing. + } + return value; + } + + // for testing + + public static void main(String[] argv) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception exc) { + + } + + JFrame dialog = new JFrame(); + BorderLayout bl = new BorderLayout(20, 20); // StatusButtonPanel panel = new StatusButtonPanel(); // System.out.println( panel.statusComponent ); - StatusButtonPanel panel = new StatusButtonPanel( new String[] { "Okay", "Cancel" } ); + StatusButtonPanel panel = new StatusButtonPanel(new String[] { "Okay", "Cancel" }); - dialog.getContentPane().setLayout( bl ); - dialog.getContentPane().add( panel, BorderLayout.SOUTH ); - dialog.setLocation( 50, 50 ); - // dialog.setSize( 450, 150 ); - dialog.pack(); - dialog.setVisible( true ); + dialog.getContentPane().setLayout(bl); + dialog.getContentPane().add(panel, BorderLayout.SOUTH); + dialog.setLocation(50, 50); + // dialog.setSize( 450, 150 ); + dialog.pack(); + dialog.setVisible(true); - panel.setBorder( new EmptyBorder( 5, 5, 5, 5 ) ); - panel.setAlignment( BetterFlowLayout.RIGHT ); + panel.setBorder(new EmptyBorder(5, 5, 5, 5)); + panel.setAlignment(BetterFlowLayout.RIGHT); // panel.getButton( "One" ).setEnabled( false ); - panel.setText( "File saved." ); - System.out.println( panel.getText() ); - } + panel.setText("File saved."); + System.out.println(panel.getText()); + } } - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java index a51ed16..b15a660 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TintedImageFilter.java @@ -22,79 +22,75 @@ import java.awt.Color; import java.awt.image.RGBImageFilter; /** - * TintedImageFilter tints all gray pixels half-way towards - * the value passed into the constructor. This "tints" a - * mostly grayscale image. This has proven useful for tinting - * user interface decorative images towards one of the SystemColor - * constants to better mesh with a platform look and feel. + * TintedImageFilter tints all gray pixels half-way towards the value passed + * into the constructor. This "tints" a mostly grayscale image. This has proven + * useful for tinting user interface decorative images towards one of the + * SystemColor constants to better mesh with a platform look and feel. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 893 $ */ - public class TintedImageFilter extends RGBImageFilter - { - double redOffset, greenOffset, blueOffset; - - public TintedImageFilter( Color aColor ) - { - canFilterIndexColorModel = true; - redOffset = getOffset( aColor.getRed() ); - greenOffset = getOffset( aColor.getGreen() ); - blueOffset = getOffset( aColor.getBlue() ); - } - - /** - * Calculates the offset used to modify color - * values. This method returns half the difference - * between the specified color level and 192. - */ - protected double getOffset( int colorValue ) - { - return ( colorValue - 192 ) / 2; - } +public class TintedImageFilter extends RGBImageFilter { + double redOffset, greenOffset, blueOffset; - public int filterRGB(int x, int y, int rgb) - { + public TintedImageFilter(Color aColor) { + canFilterIndexColorModel = true; + redOffset = getOffset(aColor.getRed()); + greenOffset = getOffset(aColor.getGreen()); + blueOffset = getOffset(aColor.getBlue()); + } + + /** + * Calculates the offset used to modify color values. This method returns half + * the difference between the specified color level and 192. + */ + protected double getOffset(int colorValue) { + return (colorValue - 192) / 2; + } + + public int filterRGB(int x, int y, int rgb) { + + int red = (rgb & 0xff0000) >> 16; + int green = (rgb & 0x00ff00) >> 8; + int blue = (rgb & 0x0000ff); + + // if roughly black + if (red + green + blue < 30) + return rgb; + + // if roughly gray + if ((Math.abs(red - green) < 10) && (Math.abs(red - blue) < 10)) { + red += redOffset; + if (red < 0) + red = 0; + if (red > 255) + red = 255; + green += greenOffset; + if (green < 0) + green = 0; + if (green > 255) + green = 255; + blue += blueOffset; + if (blue < 0) + blue = 0; + if (blue > 255) + blue = 255; + + return new Color(red, green, blue).getRGB(); + } + + return rgb; + } +} - int red = ( rgb & 0xff0000 ) >> 16; - int green = ( rgb & 0x00ff00 ) >> 8; - int blue = ( rgb & 0x0000ff ); - - // if roughly black - if ( red + green + blue < 30 ) return rgb; - - // if roughly gray - if ( ( Math.abs( red - green ) < 10 ) - && ( Math.abs( red - blue ) < 10 ) ) - { - red += redOffset; - if ( red < 0 ) red = 0; - if ( red > 255 ) red = 255; - green += greenOffset; - if ( green < 0 ) green = 0; - if ( green > 255 ) green = 255; - blue += blueOffset; - if ( blue < 0 ) blue = 0; - if ( blue > 255 ) blue = 255; - - return new Color( red, green, blue ).getRGB(); - } - - return rgb; - } - } - /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/01/18 21:27:04 mpowers - * Made the tinting a little darker. + * Revision 1.2 2001/01/18 21:27:04 mpowers Made the tinting a little darker. * - * Revision 1.1 2001/01/12 17:36:27 mpowers - * Contributing TintedImageFilter. + * Revision 1.1 2001/01/12 17:36:27 mpowers Contributing TintedImageFilter. * * */ diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java index f0bb6c2..f5ab50c 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeChooser.java @@ -66,662 +66,538 @@ import javax.swing.tree.TreeSelectionModel; import net.wotonomy.foundation.internal.WotonomyException; /** -* TreeChooser is a FileChooser-like panel that -* uses a TreeModel as a data source. It basically -* provides an alternative to JTree for rendering -* and manipulating tree-like data. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TreeChooser extends JPanel - implements ActionListener, ListSelectionListener, - TreeSelectionListener, TreeModelListener, ListCellRenderer -{ - /** - * The TreeChooser responds to this action command - * by calling displayPrevious(). - */ + * TreeChooser is a FileChooser-like panel that uses a TreeModel as a data + * source. It basically provides an alternative to JTree for rendering and + * manipulating tree-like data. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TreeChooser extends JPanel + implements ActionListener, ListSelectionListener, TreeSelectionListener, TreeModelListener, ListCellRenderer { + /** + * The TreeChooser responds to this action command by calling displayPrevious(). + */ public static final String BACK = "Back"; - /** - * The TreeChooser responds to this action command - * by calling displayHome(). - */ + /** + * The TreeChooser responds to this action command by calling displayHome(). + */ public static final String HOME = "Home"; - /** - * The TreeChooser responds to this action command - * by calling displayParent(). - */ + /** + * The TreeChooser responds to this action command by calling displayParent(). + */ public static final String UP = "Up"; - /** - * The TreeChooser responds to this action command - * by attempting to navigate to the first node in - * the current selection and display that node's children. - */ + /** + * The TreeChooser responds to this action command by attempting to navigate to + * the first node in the current selection and display that node's children. + */ public static final String SELECT = "Select"; protected JList contents; - protected JComboBox pathCombo; + protected JComboBox pathCombo; protected JToolBar toolBar; - + protected TreeModel model; protected TreeSelectionModel selectionModel; - protected TreeCellRenderer renderer; - protected TreePath displayPath; - protected Stack pathStack; - protected int pathIndent; - - private ChooserComboBoxModel comboBoxModel; - private JTree bogusJTree; // needed for tree cell renderer - private Dimension preferredSize; - - public TreeChooser() - { - preferredSize = new Dimension( 300, 200 ); - model = new DefaultTreeModel( new DefaultMutableTreeNode( "Root" ) ); - displayPath = new TreePath( model.getRoot() ); - selectionModel = new DefaultTreeSelectionModel(); - renderer = new DefaultTreeCellRenderer(); - pathStack = new Stack(); - pathIndent = 0; // 16; - comboBoxModel = new ChooserComboBoxModel( this ); - - bogusJTree = new JTree(); - bogusJTree.setModel( model ); + protected TreeCellRenderer renderer; + protected TreePath displayPath; + protected Stack pathStack; + protected int pathIndent; + + private ChooserComboBoxModel comboBoxModel; + private JTree bogusJTree; // needed for tree cell renderer + private Dimension preferredSize; + + public TreeChooser() { + preferredSize = new Dimension(300, 200); + model = new DefaultTreeModel(new DefaultMutableTreeNode("Root")); + displayPath = new TreePath(model.getRoot()); + selectionModel = new DefaultTreeSelectionModel(); + renderer = new DefaultTreeCellRenderer(); + pathStack = new Stack(); + pathIndent = 0; // 16; + comboBoxModel = new ChooserComboBoxModel(this); + + bogusJTree = new JTree(); + bogusJTree.setModel(model); init(); - displayHome(); - - stopListening(); // clear existing listeners - startListening(); - } - - public Dimension getPreferredSize() - { - return preferredSize; - } - - protected void init() - { - this.setLayout( new BorderLayout( 10, 10 ) ); - + displayHome(); + + stopListening(); // clear existing listeners + startListening(); + } + + public Dimension getPreferredSize() { + return preferredSize; + } + + protected void init() { + this.setLayout(new BorderLayout(10, 10)); + contents = initList(); - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); - // synchs with DefaultTreeSelectionModel - - JScrollPane scrollPane = new JScrollPane( contents ); - scrollPane.setPreferredSize( new Dimension( 200, 150 ) ); - this.add( scrollPane, BorderLayout.CENTER ); - - Component previewPane = initPreviewPane(); - if ( previewPane != null ) - { - this.add( previewPane, BorderLayout.EAST ); - } - + contents.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + // synchs with DefaultTreeSelectionModel + + JScrollPane scrollPane = new JScrollPane(contents); + scrollPane.setPreferredSize(new Dimension(200, 150)); + this.add(scrollPane, BorderLayout.CENTER); + + Component previewPane = initPreviewPane(); + if (previewPane != null) { + this.add(previewPane, BorderLayout.EAST); + } + JPanel navigationPanel = new JPanel(); - navigationPanel.setLayout( new BorderLayout( 10, 10 ) ); - this.add( navigationPanel, BorderLayout.NORTH ); - + navigationPanel.setLayout(new BorderLayout(10, 10)); + this.add(navigationPanel, BorderLayout.NORTH); + pathCombo = initComboBox(); - if ( pathCombo != null ) - { - pathCombo.setModel( comboBoxModel ); + if (pathCombo != null) { + pathCombo.setModel(comboBoxModel); // put combo in a grid bag to handle varying - // heights of JToolBars across platforms + // heights of JToolBars across platforms JPanel panel = new JPanel(); - panel.setLayout( new GridBagLayout() ); + panel.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; - panel.add( pathCombo, gbc ); - navigationPanel.add( panel, BorderLayout.CENTER ); - } - + panel.add(pathCombo, gbc); + navigationPanel.add(panel, BorderLayout.CENTER); + } + Component toolBar = initToolBar(); - if ( toolBar != null ) - { - navigationPanel.add( toolBar, BorderLayout.EAST ); - } - - } - - /** - * Creates tool bar or return null if no tool bar is desired. - * This implementation returns a JToolBar containing buttons - * for BACK, UP, and HOME. - */ - protected Component initToolBar() - { + if (toolBar != null) { + navigationPanel.add(toolBar, BorderLayout.EAST); + } + + } + + /** + * Creates tool bar or return null if no tool bar is desired. This + * implementation returns a JToolBar containing buttons for BACK, UP, and HOME. + */ + protected Component initToolBar() { JToolBar toolBar = new JToolBar(); - toolBar.setFloatable( false ); - JButton button; - button = new JButton( UIManager.getIcon("FileChooser.upFolderIcon") ); - button.setActionCommand( UP ); - button.addActionListener( this ); - toolBar.add( button ); - button = new JButton( UIManager.getIcon("FileChooser.homeFolderIcon") ); - button.setActionCommand( HOME ); - button.addActionListener( this ); - toolBar.add( button ); -/* - button = new JButton( UIManager.getIcon("FileChooser.newFolderIcon") ); - button.setActionCommand( BACK ); - button.addActionListener( this ); - toolBar.add( button ); -*/ - return toolBar; - } - - /** - * Creates the component that is used to display a preview of the - * selected item(s) in the content area. This component would listen - * to the selection model to update itself when the selected items change. - * Return null to omit this component. - * This implementation returns null. - */ - protected Component initPreviewPane() - { - return null; - } - - /** - * Creates the JComboBox that is used to render the path leading to - * the displayed contents. Return null to omit this combo box. - * This implementation returns a stock JComboBox that uses this - * class as its cell renderer. - */ - protected JComboBox initComboBox() - { - JComboBox comboBox = new JComboBox(); - comboBox.setRenderer( this ); - return comboBox; - } - - /** - * Creates the JList that is used to render the path leading to - * the displayed contents. This method may not return null. - * This implementation returns a stock JList that uses this - * class as its cell renderer and fires a SELECT action event - * on double click. - */ - protected JList initList() - { - JList list = new JList(); - list.setCellRenderer( this ); - list.addMouseListener( new MouseAdapter() - { - public void mouseClicked( MouseEvent evt ) - { - if ( evt.getClickCount() > 1 ) - { - actionPerformed( new ActionEvent( this, 0, SELECT ) ); - } - } - }); - return list; - } - - /** - * Begins listening to the specified tree model - * and tree selection model. - */ - protected void startListening() - { - model.addTreeModelListener( this ); - selectionModel.addTreeSelectionListener( this ); - contents.addListSelectionListener( this ); - } - - /** - * Stops listening to the specified tree model - * and tree selection model. - */ - protected void stopListening() - { - model.removeTreeModelListener( this ); - selectionModel.removeTreeSelectionListener( this ); - contents.removeListSelectionListener( this ); - } - - /** - * Returns the TreeModel used by the TreeChooser. - */ - public TreeModel getModel() - { - return model; - } - - /** - * Sets the TreeModel used by the TreeChooser. - */ - public void setModel( TreeModel aTreeModel ) - { - stopListening(); - model = aTreeModel; - bogusJTree.setModel( aTreeModel ); - pathStack.removeAllElements(); - startListening(); - displayHome(); - } - - /** - * Returns the TreeSelectionModel used by the TreeChooser. - */ - public TreeSelectionModel getSelectionModel() - { - return selectionModel; - } - - /** - * Sets the TreeSelectionModel used by the TreeChooser. - */ - public void setSelectionModel( TreeSelectionModel aSelectionModel ) - { - selectionModel = aSelectionModel; - if ( aSelectionModel.getSelectionMode() == - TreeSelectionModel.SINGLE_TREE_SELECTION ) - { - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.SINGLE_SELECTION ); - } - else - { - contents.getSelectionModel().setSelectionMode( - ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); - } - updateSelection(); - } - - /** - * Returns the TreeCellRenderer used by the TreeChooser. - */ - public TreeCellRenderer getRenderer() - { - return renderer; - } - - /** - * Sets the TreeCellRenderer used by the TreeChooser. - */ - public void setRenderer( TreeCellRenderer aRenderer ) - { - renderer = aRenderer; - updateContents(); - } - - /** - * Displays the "home" directory. - * This implementation displays the root node's children. - */ - public void displayHome() - { - setDisplayPath( null ); - } - - /** - * Displays the parent path of the currently displayed path. - */ - public void displayParent() - { - setDisplayPath( displayPath.getParentPath() ); - } - - /** - * Displays the last displayed path before the current one, - * emulating the behavior of a "back" button. - */ - public void displayPrevious() - { - if ( pathStack.empty() ) - { - displayHome(); - } - else - { - setDisplayPathDirect( (TreePath) pathStack.pop() ); - updateContents(); - } - } - - /** - * Pushes the previous item onto the stack, sets - * the display path, and then updates the contents. - * If aPath is null, the root node's children are displayed. - */ - public void setDisplayPath( TreePath aPath ) - { - if ( aPath == null ) - { - aPath = new TreePath( getModel().getRoot() ); - } - if ( ! displayPath.equals ( aPath ) ) - { - pathStack.push( displayPath ); - setDisplayPathDirect( aPath ); - } - updateContents(); - } - - /** - * Sets the displayPath field and does not - * update the stack nor update the contents. - */ - protected void setDisplayPathDirect( TreePath aPath ) - { - displayPath = aPath; - } - - /** - * Gets the currently displayed path. - */ - public TreePath getDisplayPath() - { - return displayPath; - } - - /** - * Called when selected path changes or when model indicates - * that the displayed path has changed. - */ - protected void updateContents() - { - stopListening(); - - // update combo box - comboBoxModel.fireContentsChanged(); - - // update list contents - Object displayedObject = displayPath.getLastPathComponent(); -/* -//FIXME: this display group doesn't seem to be getting the sort orderings from parent -if ( displayedObject instanceof net.wotonomy.ui.EODisplayGroup ) -System.out.println( ((net.wotonomy.ui.EODisplayGroup)displayedObject).displayedObjects() ); -*/ - int count = model.getChildCount( displayedObject ); - Object[] children = new Object[ count ]; - for ( int i = 0; i < count; i++ ) - { - children[i] = model.getChild( displayedObject, i ); - } - contents.setListData( children ); - - startListening(); - - // synchronize the selection - updateSelection(); - } - - /** - * Updates the selection in the list to reflect the - * selection in the tree selection model. - */ - public void updateSelection() - { - int index; - Object last = displayPath.getLastPathComponent(); - TreePath[] selectionPaths = selectionModel.getSelectionPaths(); - if ( selectionPaths != null ) - { - List selectedIndices = new LinkedList(); - for ( int i = 0; i < selectionPaths.length; i++ ) - { - if ( displayPath.equals( selectionPaths[i].getParentPath() ) ) - { - index = getModel().getIndexOfChild( - last, selectionPaths[i].getLastPathComponent() ); - if ( index != -1 ) - { - selectedIndices.add( new Integer( index ) ); - } - else // should never happen - { - throw new WotonomyException( - "Could not find child of displayed node." ); - } - } - } - int[] selected = new int[ selectedIndices.size() ]; - for ( int i = 0; i < selected.length; i++ ) - { - selected[i] = ((Integer)selectedIndices.get(i)).intValue(); - } - stopListening(); - contents.setSelectedIndices( selected ); - startListening(); - } - } - - // interface TreeModelListener - - public void treeNodesChanged( TreeModelEvent evt ) - { -/* - if ( displayPath.getLastPathComponent().toString().equals( - evt.getTreePath().getLastPathComponent().toString() ) ) - { -System.out.println( "TreeChooser.treeNodesChanged: " + count++ ); -*/ - updateContents(); -/* - } - else - { - System.out.println( evt.getTreePath() + " != " + displayPath ); - } -*/ - } - - public void treeNodesInserted( TreeModelEvent evt ) - { + toolBar.setFloatable(false); + JButton button; + button = new JButton(UIManager.getIcon("FileChooser.upFolderIcon")); + button.setActionCommand(UP); + button.addActionListener(this); + toolBar.add(button); + button = new JButton(UIManager.getIcon("FileChooser.homeFolderIcon")); + button.setActionCommand(HOME); + button.addActionListener(this); + toolBar.add(button); + /* + * button = new JButton( UIManager.getIcon("FileChooser.newFolderIcon") ); + * button.setActionCommand( BACK ); button.addActionListener( this ); + * toolBar.add( button ); + */ + return toolBar; + } + + /** + * Creates the component that is used to display a preview of the selected + * item(s) in the content area. This component would listen to the selection + * model to update itself when the selected items change. Return null to omit + * this component. This implementation returns null. + */ + protected Component initPreviewPane() { + return null; + } + + /** + * Creates the JComboBox that is used to render the path leading to the + * displayed contents. Return null to omit this combo box. This implementation + * returns a stock JComboBox that uses this class as its cell renderer. + */ + protected JComboBox initComboBox() { + JComboBox comboBox = new JComboBox(); + comboBox.setRenderer(this); + return comboBox; + } + + /** + * Creates the JList that is used to render the path leading to the displayed + * contents. This method may not return null. This implementation returns a + * stock JList that uses this class as its cell renderer and fires a SELECT + * action event on double click. + */ + protected JList initList() { + JList list = new JList(); + list.setCellRenderer(this); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() > 1) { + actionPerformed(new ActionEvent(this, 0, SELECT)); + } + } + }); + return list; + } + + /** + * Begins listening to the specified tree model and tree selection model. + */ + protected void startListening() { + model.addTreeModelListener(this); + selectionModel.addTreeSelectionListener(this); + contents.addListSelectionListener(this); + } + + /** + * Stops listening to the specified tree model and tree selection model. + */ + protected void stopListening() { + model.removeTreeModelListener(this); + selectionModel.removeTreeSelectionListener(this); + contents.removeListSelectionListener(this); + } + + /** + * Returns the TreeModel used by the TreeChooser. + */ + public TreeModel getModel() { + return model; + } + + /** + * Sets the TreeModel used by the TreeChooser. + */ + public void setModel(TreeModel aTreeModel) { + stopListening(); + model = aTreeModel; + bogusJTree.setModel(aTreeModel); + pathStack.removeAllElements(); + startListening(); + displayHome(); + } + + /** + * Returns the TreeSelectionModel used by the TreeChooser. + */ + public TreeSelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Sets the TreeSelectionModel used by the TreeChooser. + */ + public void setSelectionModel(TreeSelectionModel aSelectionModel) { + selectionModel = aSelectionModel; + if (aSelectionModel.getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION) { + contents.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } else { + contents.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + } + updateSelection(); + } + + /** + * Returns the TreeCellRenderer used by the TreeChooser. + */ + public TreeCellRenderer getRenderer() { + return renderer; + } + + /** + * Sets the TreeCellRenderer used by the TreeChooser. + */ + public void setRenderer(TreeCellRenderer aRenderer) { + renderer = aRenderer; + updateContents(); + } + + /** + * Displays the "home" directory. This implementation displays the root node's + * children. + */ + public void displayHome() { + setDisplayPath(null); + } + + /** + * Displays the parent path of the currently displayed path. + */ + public void displayParent() { + setDisplayPath(displayPath.getParentPath()); + } + + /** + * Displays the last displayed path before the current one, emulating the + * behavior of a "back" button. + */ + public void displayPrevious() { + if (pathStack.empty()) { + displayHome(); + } else { + setDisplayPathDirect((TreePath) pathStack.pop()); + updateContents(); + } + } + + /** + * Pushes the previous item onto the stack, sets the display path, and then + * updates the contents. If aPath is null, the root node's children are + * displayed. + */ + public void setDisplayPath(TreePath aPath) { + if (aPath == null) { + aPath = new TreePath(getModel().getRoot()); + } + if (!displayPath.equals(aPath)) { + pathStack.push(displayPath); + setDisplayPathDirect(aPath); + } + updateContents(); + } + + /** + * Sets the displayPath field and does not update the stack nor update the + * contents. + */ + protected void setDisplayPathDirect(TreePath aPath) { + displayPath = aPath; + } + + /** + * Gets the currently displayed path. + */ + public TreePath getDisplayPath() { + return displayPath; + } + + /** + * Called when selected path changes or when model indicates that the displayed + * path has changed. + */ + protected void updateContents() { + stopListening(); + + // update combo box + comboBoxModel.fireContentsChanged(); + + // update list contents + Object displayedObject = displayPath.getLastPathComponent(); + /* + * //FIXME: this display group doesn't seem to be getting the sort orderings + * from parent if ( displayedObject instanceof net.wotonomy.ui.EODisplayGroup ) + * System.out.println( + * ((net.wotonomy.ui.EODisplayGroup)displayedObject).displayedObjects() ); + */ + int count = model.getChildCount(displayedObject); + Object[] children = new Object[count]; + for (int i = 0; i < count; i++) { + children[i] = model.getChild(displayedObject, i); + } + contents.setListData(children); + + startListening(); + + // synchronize the selection + updateSelection(); + } + + /** + * Updates the selection in the list to reflect the selection in the tree + * selection model. + */ + public void updateSelection() { + int index; + Object last = displayPath.getLastPathComponent(); + TreePath[] selectionPaths = selectionModel.getSelectionPaths(); + if (selectionPaths != null) { + List selectedIndices = new LinkedList(); + for (int i = 0; i < selectionPaths.length; i++) { + if (displayPath.equals(selectionPaths[i].getParentPath())) { + index = getModel().getIndexOfChild(last, selectionPaths[i].getLastPathComponent()); + if (index != -1) { + selectedIndices.add(new Integer(index)); + } else // should never happen + { + throw new WotonomyException("Could not find child of displayed node."); + } + } + } + int[] selected = new int[selectedIndices.size()]; + for (int i = 0; i < selected.length; i++) { + selected[i] = ((Integer) selectedIndices.get(i)).intValue(); + } + stopListening(); + contents.setSelectedIndices(selected); + startListening(); + } + } + + // interface TreeModelListener + + public void treeNodesChanged(TreeModelEvent evt) { + /* + * if ( displayPath.getLastPathComponent().toString().equals( + * evt.getTreePath().getLastPathComponent().toString() ) ) { System.out.println( + * "TreeChooser.treeNodesChanged: " + count++ ); + */ + updateContents(); + /* + * } else { System.out.println( evt.getTreePath() + " != " + displayPath ); } + */ + } + + public void treeNodesInserted(TreeModelEvent evt) { // updateContents(); - } - - public void treeNodesRemoved( TreeModelEvent evt ) - { + } + + public void treeNodesRemoved(TreeModelEvent evt) { // updateContents(); - } - - public void treeStructureChanged( TreeModelEvent evt ) - { - if ( ( evt.getTreePath().equals( displayPath ) ) - || ( evt.getTreePath().isDescendant( displayPath ) ) ) - { + } + + public void treeStructureChanged(TreeModelEvent evt) { + if ((evt.getTreePath().equals(displayPath)) || (evt.getTreePath().isDescendant(displayPath))) { // setDisplayPath( evt.getTreePath() ); - } - - displayHome(); - } - - // interface TreeSelectionListener - - /** - * Called when the tree selection model's value changes. - * This is presumably an external change, so this calls - * updateSelection. - */ - public void valueChanged( TreeSelectionEvent evt ) - { - updateSelection(); - } - - // interface ListSelectionListener - - /** - * Called when user changes the selection in the list. - * This implementation updates the tree selection model - * with the corresponding selection. - */ - public void valueChanged( ListSelectionEvent evt ) - { - if ( ! evt.getValueIsAdjusting() ) - { - Object last = displayPath.getLastPathComponent(); - int[] selection = contents.getSelectedIndices(); - TreePath[] selectionPaths = new TreePath[ selection.length ]; - for ( int i = 0; i < selection.length; i++ ) - { - selectionPaths[i] = displayPath.pathByAddingChild( - getModel().getChild( last, selection[i] ) ); - } - selectionModel.setSelectionPaths( selectionPaths ); - } - - } - - // interface ListCellRenderer - - /** - * This method returns the component returned by the tree cell renderer. - */ - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ) - { - boolean isLeaf = ( model.isLeaf( value ) ); - - bogusJTree.setForeground( list.getForeground() ); - bogusJTree.setBackground( list.getBackground() ); - - JComponent result = (JComponent) renderer.getTreeCellRendererComponent( - bogusJTree, value, isSelected, (list != contents), - isLeaf, index, cellHasFocus ); -/* - if ( ( list != contents ) && ( index > -1 ) ) - { - result.setBorder( - BorderFactory.createEmptyBorder( 0, index*pathIndent, 0, 0 ) ); - } - else - { - result.setBorder( - BorderFactory.createEmptyBorder() ); - } -*/ - return result; - } - - // interface ActionListener - - public void actionPerformed( ActionEvent evt ) - { - String command = evt.getActionCommand(); - - if ( HOME.equals( command ) ) - { - displayHome(); - } - else - if ( UP.equals( command ) ) - { - displayParent(); - } - else - if ( BACK.equals( command ) ) - { - displayPrevious(); - } - else - if ( SELECT.equals( command ) ) - { - Cursor oldCursor = getCursor(); - setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) ); - - int index = contents.getSelectedIndex(); - // if selection - if ( index != -1 ) - { - Object parent = displayPath.getLastPathComponent(); - Object child = getModel().getChild( parent, index ); - // if selected item is not a leaf - if ( getModel().getChildCount( child ) > 0 ) - { - // navigate to selected item - setDisplayPath( displayPath.pathByAddingChild( child ) ); - } - } - - setCursor( oldCursor ); - } - - } - - private class ChooserComboBoxModel implements ComboBoxModel - { - TreeChooser treeChooser; - Vector listeners; - - ChooserComboBoxModel( TreeChooser aTreeChooser ) - { - treeChooser = aTreeChooser; - listeners = new Vector(); - } - - public int getSize() - { - return treeChooser.displayPath.getPathCount(); - } - - public Object getElementAt(int index) - { - return treeChooser.displayPath.getPathComponent( index ); - } - - public Object getSelectedItem() - { - return treeChooser.displayPath.getLastPathComponent(); - } - - public void setSelectedItem(Object anItem) - { - if ( ! ( - treeChooser.displayPath.getLastPathComponent().equals( anItem ) ) ) - { - Object[] items = treeChooser.displayPath.getPath(); - TreePath path = new TreePath( getModel().getRoot() ); - for ( int i = 1; i < items.length; i++ ) - { - if ( path.getLastPathComponent() == anItem ) - { - treeChooser.setDisplayPath( path ); - return; - } - path = path.pathByAddingChild( items[i] ); - } - } - } - - public void addListDataListener(ListDataListener l) - { - listeners.add( l ); - } - - public void removeListDataListener(ListDataListener l) - { - listeners.remove( l ); - } - - public void fireContentsChanged() - { - Enumeration e = listeners.elements(); - while ( e.hasMoreElements() ) - { - ((ListDataListener)e.nextElement()).contentsChanged( - new ListDataEvent( - this, ListDataEvent.CONTENTS_CHANGED, 0, getSize() ) ); - } - } - } - -} + } + + displayHome(); + } + + // interface TreeSelectionListener + + /** + * Called when the tree selection model's value changes. This is presumably an + * external change, so this calls updateSelection. + */ + public void valueChanged(TreeSelectionEvent evt) { + updateSelection(); + } + // interface ListSelectionListener + /** + * Called when user changes the selection in the list. This implementation + * updates the tree selection model with the corresponding selection. + */ + public void valueChanged(ListSelectionEvent evt) { + if (!evt.getValueIsAdjusting()) { + Object last = displayPath.getLastPathComponent(); + int[] selection = contents.getSelectedIndices(); + TreePath[] selectionPaths = new TreePath[selection.length]; + for (int i = 0; i < selection.length; i++) { + selectionPaths[i] = displayPath.pathByAddingChild(getModel().getChild(last, selection[i])); + } + selectionModel.setSelectionPaths(selectionPaths); + } + + } + + // interface ListCellRenderer + + /** + * This method returns the component returned by the tree cell renderer. + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + boolean isLeaf = (model.isLeaf(value)); + + bogusJTree.setForeground(list.getForeground()); + bogusJTree.setBackground(list.getBackground()); + + JComponent result = (JComponent) renderer.getTreeCellRendererComponent(bogusJTree, value, isSelected, + (list != contents), isLeaf, index, cellHasFocus); + /* + * if ( ( list != contents ) && ( index > -1 ) ) { result.setBorder( + * BorderFactory.createEmptyBorder( 0, index*pathIndent, 0, 0 ) ); } else { + * result.setBorder( BorderFactory.createEmptyBorder() ); } + */ + return result; + } + + // interface ActionListener + + public void actionPerformed(ActionEvent evt) { + String command = evt.getActionCommand(); + + if (HOME.equals(command)) { + displayHome(); + } else if (UP.equals(command)) { + displayParent(); + } else if (BACK.equals(command)) { + displayPrevious(); + } else if (SELECT.equals(command)) { + Cursor oldCursor = getCursor(); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + int index = contents.getSelectedIndex(); + // if selection + if (index != -1) { + Object parent = displayPath.getLastPathComponent(); + Object child = getModel().getChild(parent, index); + // if selected item is not a leaf + if (getModel().getChildCount(child) > 0) { + // navigate to selected item + setDisplayPath(displayPath.pathByAddingChild(child)); + } + } + + setCursor(oldCursor); + } + + } + + private class ChooserComboBoxModel implements ComboBoxModel { + TreeChooser treeChooser; + Vector listeners; + + ChooserComboBoxModel(TreeChooser aTreeChooser) { + treeChooser = aTreeChooser; + listeners = new Vector(); + } + + public int getSize() { + return treeChooser.displayPath.getPathCount(); + } + + public Object getElementAt(int index) { + return treeChooser.displayPath.getPathComponent(index); + } + + public Object getSelectedItem() { + return treeChooser.displayPath.getLastPathComponent(); + } + + public void setSelectedItem(Object anItem) { + if (!(treeChooser.displayPath.getLastPathComponent().equals(anItem))) { + Object[] items = treeChooser.displayPath.getPath(); + TreePath path = new TreePath(getModel().getRoot()); + for (int i = 1; i < items.length; i++) { + if (path.getLastPathComponent() == anItem) { + treeChooser.setDisplayPath(path); + return; + } + path = path.pathByAddingChild(items[i]); + } + } + } + + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public void fireContentsChanged() { + Enumeration e = listeners.elements(); + while (e.hasMoreElements()) { + ((ListDataListener) e.nextElement()) + .contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize())); + } + } + } + +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java index fbf3791..c6e1a99 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/components/TreeTableCellRenderer.java @@ -32,193 +32,158 @@ import javax.swing.JViewport; import javax.swing.table.TableCellRenderer; /** -* A TableCellRenderer that paints a portion of a JTree. -* Extends JViewport to take advantage of buffering and -* fast blitting (avoids repeated clipping and repainting). -* Defaults opaque to false: to see selection background -* painted, call setOpaque( true ). -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * A TableCellRenderer that paints a portion of a JTree. Extends JViewport to + * take advantage of buffering and fast blitting (avoids repeated clipping and + * repainting). Defaults opaque to false: to see selection background painted, + * call setOpaque( true ). + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ public class TreeTableCellRenderer extends JViewport implements TableCellRenderer, MouseListener { - JTree tree; - Component emptyComponent; - JTable delegateTable; - int lastKnownColumn; - - /** - * Constructor takes a JTree and modifies it by setting - * rootVisible to false, showsRootHandles to true, - * opaque to false, and border to null. - */ - public TreeTableCellRenderer( JTree aTree ) - { - setView( aTree ); - setBorder( null ); - tree = aTree; - tree.setRootVisible( false ); - tree.setShowsRootHandles( true ); - tree.setBorder( null ); - tree.setOpaque( false ); - - Object renderer = tree.getCellRenderer(); - if ( renderer instanceof JComponent ) - { - ((JComponent)renderer).setOpaque( false ); - } - Object editor = tree.getCellEditor(); - if ( editor instanceof JComponent ) - { - ((JComponent)editor).setOpaque( false ); - } - - this.setOpaque( false ); - emptyComponent = new JLabel(); - } - - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) - { - if ( isSelected ) - { - setForeground( table.getSelectionForeground() ); - setBackground( table.getSelectionBackground() ); - } - else - { - setForeground( table.getForeground() ); - setBackground( table.getBackground() ); - } - - lastKnownColumn = column; - if ( delegateTable != table ) - { - if ( delegateTable != null ) - { - delegateTable.removeMouseListener( this ); - } - table.addMouseListener( this ); - delegateTable = table; - } - - Rectangle rect = tree.getRowBounds( row ); - if ( rect != null ) - { - setViewPosition( new Point( 0 /*rect.x*/, rect.y ) ); - - //FIXME: this causes problems for some LAFs (like Metal): - // in particular, the table height seems to get stuck. - //if ( table.getRowHeight( row ) != rect.height ) - //{ - // table.setRowHeight( row, rect.height ); - //} - return this; - } - else - { - return emptyComponent; - } - } - - public void mouseClicked(MouseEvent e) - { - delegateToTree( e ); - } - - public void mousePressed(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseReleased(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseEntered(MouseEvent e) - { - delegateToTree( e ); - } - - public void mouseExited(MouseEvent e) - { - delegateToTree( e ); - } - - protected void delegateToTree(MouseEvent e) - { - int col = delegateTable.getColumnModel().getColumnIndexAtX( e.getX() ); - if ( col == lastKnownColumn ) - { - Rectangle nodeRect = tree.getRowBounds( 0 ); - Rectangle cellRect = delegateTable.getCellRect( -1, col, false ); - if ( nodeRect != null ) - { - e.translatePoint( -cellRect.x, nodeRect.y ); - tree.dispatchEvent( // e ); - new MouseEvent( tree, e.getID(), e.getWhen(), e.getModifiers(), - e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger() ) ); - } - } - } - - public void repaint() - { - //if ( delegateTable != null ) delegateTable.repaint(); - - // not calling super.repaint() does not seem to cause - // any problems so we're not doing it. - } + JTree tree; + Component emptyComponent; + JTable delegateTable; + int lastKnownColumn; + + /** + * Constructor takes a JTree and modifies it by setting rootVisible to false, + * showsRootHandles to true, opaque to false, and border to null. + */ + public TreeTableCellRenderer(JTree aTree) { + setView(aTree); + setBorder(null); + tree = aTree; + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + tree.setBorder(null); + tree.setOpaque(false); + + Object renderer = tree.getCellRenderer(); + if (renderer instanceof JComponent) { + ((JComponent) renderer).setOpaque(false); + } + Object editor = tree.getCellEditor(); + if (editor instanceof JComponent) { + ((JComponent) editor).setOpaque(false); + } + + this.setOpaque(false); + emptyComponent = new JLabel(); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) { + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } else { + setForeground(table.getForeground()); + setBackground(table.getBackground()); + } + + lastKnownColumn = column; + if (delegateTable != table) { + if (delegateTable != null) { + delegateTable.removeMouseListener(this); + } + table.addMouseListener(this); + delegateTable = table; + } + + Rectangle rect = tree.getRowBounds(row); + if (rect != null) { + setViewPosition(new Point(0 /* rect.x */, rect.y)); + + // FIXME: this causes problems for some LAFs (like Metal): + // in particular, the table height seems to get stuck. + // if ( table.getRowHeight( row ) != rect.height ) + // { + // table.setRowHeight( row, rect.height ); + // } + return this; + } else { + return emptyComponent; + } + } + + public void mouseClicked(MouseEvent e) { + delegateToTree(e); + } + + public void mousePressed(MouseEvent e) { + delegateToTree(e); + } + + public void mouseReleased(MouseEvent e) { + delegateToTree(e); + } + + public void mouseEntered(MouseEvent e) { + delegateToTree(e); + } + + public void mouseExited(MouseEvent e) { + delegateToTree(e); + } + + protected void delegateToTree(MouseEvent e) { + int col = delegateTable.getColumnModel().getColumnIndexAtX(e.getX()); + if (col == lastKnownColumn) { + Rectangle nodeRect = tree.getRowBounds(0); + Rectangle cellRect = delegateTable.getCellRect(-1, col, false); + if (nodeRect != null) { + e.translatePoint(-cellRect.x, nodeRect.y); + tree.dispatchEvent( // e ); + new MouseEvent(tree, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), + e.getClickCount(), e.isPopupTrigger())); + } + } + } + + public void repaint() { + // if ( delegateTable != null ) delegateTable.repaint(); + + // not calling super.repaint() does not seem to cause + // any problems so we're not doing it. + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.11 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.11 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.10 2002/04/12 20:07:35 mpowers - * Fixed cool/annoying view position. + * Revision 1.10 2002/04/12 20:07:35 mpowers Fixed cool/annoying view position. * - * Revision 1.9 2002/04/09 18:12:21 mpowers - * Fixes for 1.4. + * Revision 1.9 2002/04/09 18:12:21 mpowers Fixes for 1.4. * - * Revision 1.8 2002/03/22 22:39:24 mpowers - * Can now move column to any position in the table. + * Revision 1.8 2002/03/22 22:39:24 mpowers Can now move column to any position + * in the table. * - * Revision 1.7 2002/03/11 03:13:22 mpowers - * Adjusting for viewport position; no longer responding to repaint(). + * Revision 1.7 2002/03/11 03:13:22 mpowers Adjusting for viewport position; no + * longer responding to repaint(). * - * Revision 1.6 2002/03/07 23:04:36 mpowers - * Refining TreeColumnAssociation. + * Revision 1.6 2002/03/07 23:04:36 mpowers Refining TreeColumnAssociation. * - * Revision 1.5 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.5 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.3 2002/02/27 23:19:17 mpowers - * Refactoring of TreeAssociation to create TreeModelAssociation parent. + * Revision 1.3 2002/02/27 23:19:17 mpowers Refactoring of TreeAssociation to + * create TreeModelAssociation parent. * - * Revision 1.2 2002/02/18 23:13:55 mpowers - * Only setting row height when needed. + * Revision 1.2 2002/02/18 23:13:55 mpowers Only setting row height when needed. * - * Revision 1.1 2002/02/18 03:46:08 mpowers - * Implemented TreeTableCellRenderer. + * Revision 1.1 2002/02/18 03:46:08 mpowers Implemented TreeTableCellRenderer. * * */ - - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java index 4412dbc..0e95d2d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ClassGrabber.java @@ -25,79 +25,67 @@ import java.io.InputStream; import java.util.Hashtable; /** - * ClassGrabber is a class loader used by WindowGrabber. - * It simply loads classes by filename and nothing more. - * It exists mainly because the java 1.1 class loading - * framework doesn't easily allow the creation of class - * loaders nor the loading of arbitrary classes. + * ClassGrabber is a class loader used by WindowGrabber. It simply loads classes + * by filename and nothing more. It exists mainly because the java 1.1 class + * loading framework doesn't easily allow the creation of class loaders nor the + * loading of arbitrary classes. * * @author michael@mpowers.net - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ -public class ClassGrabber extends ClassLoader -{ +public class ClassGrabber extends ClassLoader { Hashtable classMap = new Hashtable(); - public ClassGrabber() - { + public ClassGrabber() { super(); } - protected Class loadClass(String name, boolean resolve) - throws ClassNotFoundException - { - Class c = (Class) classMap.get( name ); + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class c = (Class) classMap.get(name); - if ( c != null ) return c; + if (c != null) + return c; - try - { - c = findSystemClass( name ); - } - catch ( Exception exc1 ) - { + try { + c = findSystemClass(name); + } catch (Exception exc1) { // System.err.print( "findSystemClass: " + name + ": " ); // System.err.println( exc1 ); } - if ( c != null ) return c; + if (c != null) + return c; - try - { - c = findLoadedClass( name ); - } - catch ( Exception exc1 ) - { + try { + c = findLoadedClass(name); + } catch (Exception exc1) { // System.err.print( "findLoadedClass: " + name + ": " ); // System.err.println( exc1 ); } - - if ( c != null ) return c; - - try - { - InputStream input = new BufferedInputStream( new FileInputStream( name ) ); - ByteArrayOutputStream output = new ByteArrayOutputStream( 200 ); + + if (c != null) + return c; + + try { + InputStream input = new BufferedInputStream(new FileInputStream(name)); + ByteArrayOutputStream output = new ByteArrayOutputStream(200); int ch; - while ( ( ch = input.read() ) != -1 ) - { - output.write( ch ); + while ((ch = input.read()) != -1) { + output.write(ch); } byte[] data = output.toByteArray(); - c = defineClass( null, data, 0, data.length ); - } - catch ( Exception exc ) - { - System.err.print( "getResource: " + name + ": " ); - System.err.println( exc ); - c = null; + c = defineClass(null, data, 0, data.length); + } catch (Exception exc) { + System.err.print("getResource: " + name + ": "); + System.err.println(exc); + c = null; } - if ( c != null ) - { - classMap.put( name, c ); - if ( resolve ) resolveClass( c ); + if (c != null) { + classMap.put(name, c); + if (resolve) + resolveClass(c); } return c; @@ -105,22 +93,18 @@ public class ClassGrabber extends ClassLoader } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java index c63157d..08e6216 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ComponentHighlighter.java @@ -36,125 +36,115 @@ import javax.swing.SwingUtilities; import javax.swing.Timer; /** -* Visually highlights a component with the specified image for a brief period. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -*/ -public class ComponentHighlighter implements ActionListener -{ - // lots of state to track - JRootPane rootPane; - JComponent component; - Component oldGlassPane; - JLabel imageLabel; - Timer timer; - JPanel glassPane; - -/** -* Alternate "Fire-and-forget" constructor loads an image from a URL. -* @param aComponent A Component that will be highlighted. -* @param aURL A URL pointing to an image. -*/ - public ComponentHighlighter( JComponent aComponent, URL aURL ) - { - if ( aURL == null ) return; - init( aComponent, Toolkit.getDefaultToolkit().getImage( aURL ) ); - } - -/** -* "Fire-and-forget" constructor. -* @param aComponent A Component that will be highlighted. -* @param anImage An image, preferably an animated GIF with transparency, -* that will slide along the length of the component. -*/ - public ComponentHighlighter( JComponent aComponent, Image anImage ) - { - init( aComponent, anImage ); - } - - protected void init( JComponent aComponent, Image anImage ) - { - if ( ( aComponent == null ) || ( anImage == null ) ) return; - - component = aComponent; - rootPane = SwingUtilities.getRootPane( component ); - oldGlassPane = rootPane.getGlassPane(); + * Visually highlights a component with the specified image for a brief period. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + */ +public class ComponentHighlighter implements ActionListener { + // lots of state to track + JRootPane rootPane; + JComponent component; + Component oldGlassPane; + JLabel imageLabel; + Timer timer; + JPanel glassPane; + + /** + * Alternate "Fire-and-forget" constructor loads an image from a URL. + * + * @param aComponent A Component that will be highlighted. + * @param aURL A URL pointing to an image. + */ + public ComponentHighlighter(JComponent aComponent, URL aURL) { + if (aURL == null) + return; + init(aComponent, Toolkit.getDefaultToolkit().getImage(aURL)); + } + + /** + * "Fire-and-forget" constructor. + * + * @param aComponent A Component that will be highlighted. + * @param anImage An image, preferably an animated GIF with transparency, + * that will slide along the length of the component. + */ + public ComponentHighlighter(JComponent aComponent, Image anImage) { + init(aComponent, anImage); + } + + protected void init(JComponent aComponent, Image anImage) { + if ((aComponent == null) || (anImage == null)) + return; + + component = aComponent; + rootPane = SwingUtilities.getRootPane(component); + oldGlassPane = rootPane.getGlassPane(); glassPane = new JPanel(); - rootPane.setGlassPane( glassPane ); - glassPane.setVisible( true ); - glassPane.setOpaque( false ); - glassPane.setLayout( null ); - - ImageIcon icon = new ImageIcon( anImage ); - - imageLabel = new JLabel(); - imageLabel.setIconTextGap( 0 ); - imageLabel.setIcon( icon ); - imageLabel.setSize( icon.getIconWidth(), icon.getIconHeight() ); - glassPane.add( imageLabel ); - - Rectangle bounds = component.getBounds(); - if ( component.getParent() instanceof Component ) - { - bounds = SwingUtilities.convertRectangle( (Container) component.getParent(), - bounds, rootPane.getContentPane() ); - } - imageLabel.setLocation( - bounds.x, bounds.y + bounds.height - imageLabel.getBounds().height ); - - glassPane.revalidate(); - glassPane.repaint(); - - component.transferFocus(); // halts a caret, if necessary - - timer = new Timer( 80, this ); - timer.setRepeats( true ); - timer.start(); - } - - public void actionPerformed( ActionEvent evt ) - { - Rectangle bounds = imageLabel.getBounds(); - Rectangle target = component.getBounds(); - if ( component.getParent() instanceof Component ) - { - target = SwingUtilities.convertRectangle( (Container) component.getParent(), - target, rootPane.getContentPane() ); - } - - if ( bounds.x + bounds.width > target.x + target.width ) - { // clean up and end - timer.stop(); - rootPane.setGlassPane( oldGlassPane ); - component.requestFocus(); - return; - } - - // else, slide to the right and continue - imageLabel.setLocation( - bounds.x + Math.max( bounds.width / 12, 1 ), bounds.y ); - imageLabel.repaint(); - } + rootPane.setGlassPane(glassPane); + glassPane.setVisible(true); + glassPane.setOpaque(false); + glassPane.setLayout(null); + + ImageIcon icon = new ImageIcon(anImage); + + imageLabel = new JLabel(); + imageLabel.setIconTextGap(0); + imageLabel.setIcon(icon); + imageLabel.setSize(icon.getIconWidth(), icon.getIconHeight()); + glassPane.add(imageLabel); + + Rectangle bounds = component.getBounds(); + if (component.getParent() instanceof Component) { + bounds = SwingUtilities.convertRectangle((Container) component.getParent(), bounds, + rootPane.getContentPane()); + } + imageLabel.setLocation(bounds.x, bounds.y + bounds.height - imageLabel.getBounds().height); + + glassPane.revalidate(); + glassPane.repaint(); + + component.transferFocus(); // halts a caret, if necessary + + timer = new Timer(80, this); + timer.setRepeats(true); + timer.start(); + } + + public void actionPerformed(ActionEvent evt) { + Rectangle bounds = imageLabel.getBounds(); + Rectangle target = component.getBounds(); + if (component.getParent() instanceof Component) { + target = SwingUtilities.convertRectangle((Container) component.getParent(), target, + rootPane.getContentPane()); + } + + if (bounds.x + bounds.width > target.x + target.width) { // clean up and end + timer.stop(); + rootPane.setGlassPane(oldGlassPane); + component.requestFocus(); + return; + } + + // else, slide to the right and continue + imageLabel.setLocation(bounds.x + Math.max(bounds.width / 12, 1), bounds.y); + imageLabel.repaint(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * 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:51:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:18 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java index 82fd897..9ff7496 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/GIFEncoder.java @@ -12,509 +12,445 @@ import java.io.IOException; import java.io.OutputStream; /** - * GIFEncoder is a class which takes an image and saves it to a stream - * using the GIF file format. A GIFEncoder is constructed with either - * an AWT Image (which must be fully loaded) or a set of RGB arrays. - * The image can be written out with a call to write.

+ * GIFEncoder is a class which takes an image and saves it to a stream using the + * GIF file format. A GIFEncoder is constructed with either an AWT Image (which + * must be fully loaded) or a set of RGB arrays. The image can be written out + * with a call to write.
+ *
* * Three caveats: *

    - *
  • GIFEncoder will convert the image to indexed color upon - * construction, and this is not fast. + *
  • GIFEncoder will convert the image to indexed color upon construction, and + * this is not fast. * - *
  • The image cannot have more than 256 colors, since GIF is an 8 - * bit format. + *
  • The image cannot have more than 256 colors, since GIF is an 8 bit format. * - *
  • Since the image must be completely loaded into memory, - * there may be problems with large images. + *
  • Since the image must be completely loaded into memory, there may be + * problems with large images. *
* - * This implementation is heavily based on code made available by - * Adam Doppelt, which was based upon gifsave.c, which was written - * and released by Sverre H. Huseby. + * This implementation is heavily based on code made available by Adam Doppelt, + * which was based upon gifsave.c, which was written and released by Sverre H. + * Huseby. * * @author amd@brown.edu * @author sverrehu@ifi.uio.no * @author michael@mpowers.net */ -public class GIFEncoder -{ - short width_, height_; - int numColors_; - byte pixels_[], colors_[]; - - ScreenDescriptor sd_; - ImageDescriptor id_; - -/** - * Construct a GIFEncoder. The constructor will convert the image to - * an indexed color array. This may take some time. If more than 256 - * colors are encountered, all subsequent colors are mapped to the first - * color encountered. - * @param image The image to encode. The image must be completely loaded. - * @exception AWTException Will be thrown if the pixel grab fails. This - * can happen if Java runs out of memory. - * */ - public GIFEncoder(Image image) throws AWTException - { - width_ = (short)image.getWidth(null); - height_ = (short)image.getHeight(null); - - int values[] = new int[width_ * height_]; - PixelGrabber grabber = new PixelGrabber( - image, 0, 0, width_, height_, values, 0, width_); - - try - { - if(grabber.grabPixels() != true) - throw new AWTException("Grabber returned false: " + - grabber.status()); - } - catch (InterruptedException e) - { - } - - byte r[][] = new byte[width_][height_]; - byte g[][] = new byte[width_][height_]; - byte b[][] = new byte[width_][height_]; - int index = 0; - for (int y = 0; y < height_; ++y) - { - for (int x = 0; x < width_; ++x) - { - r[x][y] = (byte)((values[index] >> 16) & 0xFF); - g[x][y] = (byte)((values[index] >> 8) & 0xFF); - b[x][y] = (byte)((values[index]) & 0xFF); - ++index; - } - } - toIndexedColor(r, g, b); - } - -/** - * Construct a GIFEncoder. The constructor will convert the image to - * an indexed color array. This may take some time.

- * Each array stores intensity values for the image. In other words, - * r[x][y] refers to the red intensity of the pixel at column x, row y. - * @param r An array containing the red intensity values. - * @param g An array containing the green intensity values. - * @param b An array containing the blue intensity values. - * - * @exception AWTException Will be thrown if the image contains more than - * 256 colors. - * */ - public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException - { - width_ = (short)(r.length); - height_ = (short)(r[0].length); - - toIndexedColor(r, g, b); - } - -/** - * Writes the image out to a stream in the GIF file format. This will - * be a single GIF87a image, non-interlaced, with no background color. - * This may take some time.

- * - * @param output The stream to output to. This should probably be a - * buffered stream. - * - * @exception IOException Will be thrown if a write operation fails. - * */ - public void write(OutputStream output) throws IOException - { - BitUtils.writeString(output, "GIF87a"); - ScreenDescriptor sd = new ScreenDescriptor(width_, height_, - numColors_); - sd.write(output); - - output.write(colors_, 0, colors_.length); - - ImageDescriptor id = new ImageDescriptor(width_, height_, ','); - id.write(output); - - byte codesize = BitUtils.bitsNeeded(numColors_); - if (codesize == 1) - ++codesize; - output.write(codesize); - - LZWCompressor.LZWCompress(output, codesize, pixels_); - output.write(0); - id = new ImageDescriptor((byte)0, (byte)0, ';'); - id.write(output); - output.flush(); - } - - void toIndexedColor(byte r[][], byte g[][], - byte b[][]) throws AWTException - { - pixels_ = new byte[width_ * height_]; - colors_ = new byte[256 * 3]; - int colornum = 0; - for (int x = 0; x < width_; ++x) - { - for (int y = 0; y < height_; ++y) - { - int search; - for (search = 0; search < colornum; ++search) - if (colors_[search * 3] == r[x][y] && - colors_[search * 3 + 1] == g[x][y] && - colors_[search * 3 + 2] == b[x][y]) - break; - - if (search > 255) - search = 0; - //throw new AWTException("Too many colors."); - - pixels_[y * width_ + x] = (byte)search; - - if (search == colornum) { - colors_[search * 3] = r[x][y]; - colors_[search * 3 + 1] = g[x][y]; - colors_[search * 3 + 2] = b[x][y]; - ++colornum; - } - } - } - - numColors_ = 1 << BitUtils.bitsNeeded(colornum); - byte copy[] = new byte[numColors_ * 3]; - System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); - colors_ = copy; - } +public class GIFEncoder { + short width_, height_; + int numColors_; + byte pixels_[], colors_[]; + + ScreenDescriptor sd_; + ImageDescriptor id_; + + /** + * Construct a GIFEncoder. The constructor will convert the image to an indexed + * color array. This may take some time. If more than 256 colors are + * encountered, all subsequent colors are mapped to the first color encountered. + * + * @param image The image to encode. The image must be completely loaded. + * @exception AWTException Will be thrown if the pixel grab fails. This can + * happen if Java runs out of memory. + */ + public GIFEncoder(Image image) throws AWTException { + width_ = (short) image.getWidth(null); + height_ = (short) image.getHeight(null); + + int values[] = new int[width_ * height_]; + PixelGrabber grabber = new PixelGrabber(image, 0, 0, width_, height_, values, 0, width_); + + try { + if (grabber.grabPixels() != true) + throw new AWTException("Grabber returned false: " + grabber.status()); + } catch (InterruptedException e) { + } + + byte r[][] = new byte[width_][height_]; + byte g[][] = new byte[width_][height_]; + byte b[][] = new byte[width_][height_]; + int index = 0; + for (int y = 0; y < height_; ++y) { + for (int x = 0; x < width_; ++x) { + r[x][y] = (byte) ((values[index] >> 16) & 0xFF); + g[x][y] = (byte) ((values[index] >> 8) & 0xFF); + b[x][y] = (byte) ((values[index]) & 0xFF); + ++index; + } + } + toIndexedColor(r, g, b); + } + + /** + * Construct a GIFEncoder. The constructor will convert the image to an indexed + * color array. This may take some time.
+ *
+ * Each array stores intensity values for the image. In other words, r[x][y] + * refers to the red intensity of the pixel at column x, row y. + * + * @param r An array containing the red intensity values. + * @param g An array containing the green intensity values. + * @param b An array containing the blue intensity values. + * + * @exception AWTException Will be thrown if the image contains more than 256 + * colors. + */ + public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException { + width_ = (short) (r.length); + height_ = (short) (r[0].length); + + toIndexedColor(r, g, b); + } + + /** + * Writes the image out to a stream in the GIF file format. This will be a + * single GIF87a image, non-interlaced, with no background color. This may + * take some time. + *

+ * + * @param output The stream to output to. This should probably be a buffered + * stream. + * + * @exception IOException Will be thrown if a write operation fails. + */ + public void write(OutputStream output) throws IOException { + BitUtils.writeString(output, "GIF87a"); + ScreenDescriptor sd = new ScreenDescriptor(width_, height_, numColors_); + sd.write(output); + + output.write(colors_, 0, colors_.length); + + ImageDescriptor id = new ImageDescriptor(width_, height_, ','); + id.write(output); + + byte codesize = BitUtils.bitsNeeded(numColors_); + if (codesize == 1) + ++codesize; + output.write(codesize); + + LZWCompressor.LZWCompress(output, codesize, pixels_); + output.write(0); + id = new ImageDescriptor((byte) 0, (byte) 0, ';'); + id.write(output); + output.flush(); + } + + void toIndexedColor(byte r[][], byte g[][], byte b[][]) throws AWTException { + pixels_ = new byte[width_ * height_]; + colors_ = new byte[256 * 3]; + int colornum = 0; + for (int x = 0; x < width_; ++x) { + for (int y = 0; y < height_; ++y) { + int search; + for (search = 0; search < colornum; ++search) + if (colors_[search * 3] == r[x][y] && colors_[search * 3 + 1] == g[x][y] + && colors_[search * 3 + 2] == b[x][y]) + break; + + if (search > 255) + search = 0; + // throw new AWTException("Too many colors."); + + pixels_[y * width_ + x] = (byte) search; + + if (search == colornum) { + colors_[search * 3] = r[x][y]; + colors_[search * 3 + 1] = g[x][y]; + colors_[search * 3 + 2] = b[x][y]; + ++colornum; + } + } + } + + numColors_ = 1 << BitUtils.bitsNeeded(colornum); + byte copy[] = new byte[numColors_ * 3]; + System.arraycopy(colors_, 0, copy, 0, numColors_ * 3); + colors_ = copy; + } } -class BitFile -{ - OutputStream output_; - byte buffer_[]; - int index_, bitsLeft_; - - public BitFile(OutputStream output) - { - output_ = output; - buffer_ = new byte[256]; - index_ = 0; - bitsLeft_ = 8; - } - - public void flush() throws IOException - { - int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); - if (numBytes > 0) - { - output_.write(numBytes); - output_.write(buffer_, 0, numBytes); - buffer_[0] = 0; - index_ = 0; - bitsLeft_ = 8; - } - } - - public void writeBits(int bits, int numbits) throws IOException { - int bitsWritten = 0; - int numBytes = 255; - do - { - if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) - { - output_.write(numBytes); - output_.write(buffer_, 0, numBytes); - - buffer_[0] = 0; - index_ = 0; - bitsLeft_ = 8; - } - - if (numbits <= bitsLeft_) - { - buffer_[index_] |= (bits & ((1 << numbits) - 1)) << - (8 - bitsLeft_); - bitsWritten += numbits; - bitsLeft_ -= numbits; - numbits = 0; - } - else - { - buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << - (8 - bitsLeft_); - bitsWritten += bitsLeft_; - bits >>= bitsLeft_; - numbits -= bitsLeft_; - buffer_[++index_] = 0; - bitsLeft_ = 8; - } - - } - while (numbits != 0); - } +class BitFile { + OutputStream output_; + byte buffer_[]; + int index_, bitsLeft_; + + public BitFile(OutputStream output) { + output_ = output; + buffer_ = new byte[256]; + index_ = 0; + bitsLeft_ = 8; + } + + public void flush() throws IOException { + int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1); + if (numBytes > 0) { + output_.write(numBytes); + output_.write(buffer_, 0, numBytes); + buffer_[0] = 0; + index_ = 0; + bitsLeft_ = 8; + } + } + + public void writeBits(int bits, int numbits) throws IOException { + int bitsWritten = 0; + int numBytes = 255; + do { + if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) { + output_.write(numBytes); + output_.write(buffer_, 0, numBytes); + + buffer_[0] = 0; + index_ = 0; + bitsLeft_ = 8; + } + + if (numbits <= bitsLeft_) { + buffer_[index_] |= (bits & ((1 << numbits) - 1)) << (8 - bitsLeft_); + bitsWritten += numbits; + bitsLeft_ -= numbits; + numbits = 0; + } else { + buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) << (8 - bitsLeft_); + bitsWritten += bitsLeft_; + bits >>= bitsLeft_; + numbits -= bitsLeft_; + buffer_[++index_] = 0; + bitsLeft_ = 8; + } + + } while (numbits != 0); + } } -class LZWStringTable -{ - private final static int RES_CODES = 2; - private final static short HASH_FREE = (short)0xFFFF; - private final static short NEXT_FIRST = (short)0xFFFF; - private final static int MAXBITS = 12; - private final static int MAXSTR = (1 << MAXBITS); - private final static short HASHSIZE = 9973; - private final static short HASHSTEP = 2039; - - byte strChr_[]; - short strNxt_[]; - short strHsh_[]; - short numStrings_; - - public LZWStringTable() - { - strChr_ = new byte[MAXSTR]; - strNxt_ = new short[MAXSTR]; - strHsh_ = new short[HASHSIZE]; - } - - public int addCharString(short index, byte b) - { - int hshidx; - - if (numStrings_ >= MAXSTR) - return 0xFFFF; - - hshidx = Hash(index, b); - while (strHsh_[hshidx] != HASH_FREE) - hshidx = (hshidx + HASHSTEP) % HASHSIZE; - - strHsh_[hshidx] = numStrings_; - strChr_[numStrings_] = b; - strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; - - return numStrings_++; - } - - public short findCharString(short index, byte b) - { - int hshidx, nxtidx; - - if (index == HASH_FREE) - return b; - - hshidx = Hash(index, b); - while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) - { - if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b) - return (short)nxtidx; - hshidx = (hshidx + HASHSTEP) % HASHSIZE; - } - - return (short)0xFFFF; - } - - public void clearTable(int codesize) - { - numStrings_ = 0; - - for (int q = 0; q < HASHSIZE; q++) - { - strHsh_[q] = HASH_FREE; - } - - int w = (1 << codesize) + RES_CODES; - for (int q = 0; q < w; q++) - { - addCharString((short)0xFFFF, (byte)q); - } - } - - static public int Hash(short index, byte lastbyte) - { - return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; - } +class LZWStringTable { + private final static int RES_CODES = 2; + private final static short HASH_FREE = (short) 0xFFFF; + private final static short NEXT_FIRST = (short) 0xFFFF; + private final static int MAXBITS = 12; + private final static int MAXSTR = (1 << MAXBITS); + private final static short HASHSIZE = 9973; + private final static short HASHSTEP = 2039; + + byte strChr_[]; + short strNxt_[]; + short strHsh_[]; + short numStrings_; + + public LZWStringTable() { + strChr_ = new byte[MAXSTR]; + strNxt_ = new short[MAXSTR]; + strHsh_ = new short[HASHSIZE]; + } + + public int addCharString(short index, byte b) { + int hshidx; + + if (numStrings_ >= MAXSTR) + return 0xFFFF; + + hshidx = Hash(index, b); + while (strHsh_[hshidx] != HASH_FREE) + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + + strHsh_[hshidx] = numStrings_; + strChr_[numStrings_] = b; + strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST; + + return numStrings_++; + } + + public short findCharString(short index, byte b) { + int hshidx, nxtidx; + + if (index == HASH_FREE) + return b; + + hshidx = Hash(index, b); + while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) { + if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b) + return (short) nxtidx; + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + } + + return (short) 0xFFFF; + } + + public void clearTable(int codesize) { + numStrings_ = 0; + + for (int q = 0; q < HASHSIZE; q++) { + strHsh_[q] = HASH_FREE; + } + + int w = (1 << codesize) + RES_CODES; + for (int q = 0; q < w; q++) { + addCharString((short) 0xFFFF, (byte) q); + } + } + + static public int Hash(short index, byte lastbyte) { + return ((int) ((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; + } } class LZWCompressor { - public static void LZWCompress(OutputStream output, int codesize, - byte toCompress[]) throws IOException - { - byte c; - short index; - int clearcode, endofinfo, numbits, limit, errcode; - short prefix = (short)0xFFFF; - - BitFile bitFile = new BitFile(output); - LZWStringTable strings = new LZWStringTable(); - - clearcode = 1 << codesize; - endofinfo = clearcode + 1; - - numbits = codesize + 1; - limit = (1 << numbits) - 1; - strings.clearTable(codesize); - bitFile.writeBits(clearcode, numbits); - - for (int loop = 0; loop < toCompress.length; ++loop) - { - c = toCompress[loop]; - if ((index = strings.findCharString(prefix, c)) != -1) - { - prefix = index; - } - else - { - bitFile.writeBits(prefix, numbits); - if (strings.addCharString(prefix, c) > limit) { - if (++numbits > 12) { - bitFile.writeBits(clearcode, numbits - 1); - strings.clearTable(codesize); - numbits = codesize + 1; - } - limit = (1 << numbits) - 1; - } - - prefix = (short)((short)c & 0xFF); - } - } - - if (prefix != -1) - bitFile.writeBits(prefix, numbits); - - bitFile.writeBits(endofinfo, numbits); - bitFile.flush(); - } + public static void LZWCompress(OutputStream output, int codesize, byte toCompress[]) throws IOException { + byte c; + short index; + int clearcode, endofinfo, numbits, limit, errcode; + short prefix = (short) 0xFFFF; + + BitFile bitFile = new BitFile(output); + LZWStringTable strings = new LZWStringTable(); + + clearcode = 1 << codesize; + endofinfo = clearcode + 1; + + numbits = codesize + 1; + limit = (1 << numbits) - 1; + strings.clearTable(codesize); + bitFile.writeBits(clearcode, numbits); + + for (int loop = 0; loop < toCompress.length; ++loop) { + c = toCompress[loop]; + if ((index = strings.findCharString(prefix, c)) != -1) { + prefix = index; + } else { + bitFile.writeBits(prefix, numbits); + if (strings.addCharString(prefix, c) > limit) { + if (++numbits > 12) { + bitFile.writeBits(clearcode, numbits - 1); + strings.clearTable(codesize); + numbits = codesize + 1; + } + limit = (1 << numbits) - 1; + } + + prefix = (short) ((short) c & 0xFF); + } + } + + if (prefix != -1) + bitFile.writeBits(prefix, numbits); + + bitFile.writeBits(endofinfo, numbits); + bitFile.flush(); + } } -class ScreenDescriptor -{ - public short localScreenWidth_, localScreenHeight_; - private byte byte_; - public byte backgroundColorIndex_, pixelAspectRatio_; - - public ScreenDescriptor(short width, short height, int numColors) - { - localScreenWidth_ = width; - localScreenHeight_ = height; - setGlobalColorTableSize((byte)(BitUtils.bitsNeeded(numColors) - 1)); - setGlobalColorTableFlag((byte)1); - setSortFlag((byte)0); - setColorResolution((byte)7); - backgroundColorIndex_ = 0; - pixelAspectRatio_ = 0; - } - - public void write(OutputStream output) throws IOException - { - BitUtils.writeWord(output, localScreenWidth_); - BitUtils.writeWord(output, localScreenHeight_); - output.write(byte_); - output.write(backgroundColorIndex_); - output.write(pixelAspectRatio_); - } - - public void setGlobalColorTableSize(byte num) - { - byte_ |= (num & 7); - } - - public void setSortFlag(byte num) - { - byte_ |= (num & 1) << 3; - } - - public void setColorResolution(byte num) - { - byte_ |= (num & 7) << 4; - } - - public void setGlobalColorTableFlag(byte num) - { - byte_ |= (num & 1) << 7; - } +class ScreenDescriptor { + public short localScreenWidth_, localScreenHeight_; + private byte byte_; + public byte backgroundColorIndex_, pixelAspectRatio_; + + public ScreenDescriptor(short width, short height, int numColors) { + localScreenWidth_ = width; + localScreenHeight_ = height; + setGlobalColorTableSize((byte) (BitUtils.bitsNeeded(numColors) - 1)); + setGlobalColorTableFlag((byte) 1); + setSortFlag((byte) 0); + setColorResolution((byte) 7); + backgroundColorIndex_ = 0; + pixelAspectRatio_ = 0; + } + + public void write(OutputStream output) throws IOException { + BitUtils.writeWord(output, localScreenWidth_); + BitUtils.writeWord(output, localScreenHeight_); + output.write(byte_); + output.write(backgroundColorIndex_); + output.write(pixelAspectRatio_); + } + + public void setGlobalColorTableSize(byte num) { + byte_ |= (num & 7); + } + + public void setSortFlag(byte num) { + byte_ |= (num & 1) << 3; + } + + public void setColorResolution(byte num) { + byte_ |= (num & 7) << 4; + } + + public void setGlobalColorTableFlag(byte num) { + byte_ |= (num & 1) << 7; + } } -class ImageDescriptor -{ - public byte separator_; - public short leftPosition_, topPosition_, width_, height_; - private byte byte_; - - public ImageDescriptor(short width, short height, char separator) - { - separator_ = (byte)separator; - leftPosition_ = 0; - topPosition_ = 0; - width_ = width; - height_ = height; - setLocalColorTableSize((byte)0); - setReserved((byte)0); - setSortFlag((byte)0); - setInterlaceFlag((byte)0); - setLocalColorTableFlag((byte)0); - } - - public void write(OutputStream output) throws IOException - { - output.write(separator_); - BitUtils.writeWord(output, leftPosition_); - BitUtils.writeWord(output, topPosition_); - BitUtils.writeWord(output, width_); - BitUtils.writeWord(output, height_); - output.write(byte_); - } - - public void setLocalColorTableSize(byte num) - { - byte_ |= (num & 7); - } - - public void setReserved(byte num) - { - byte_ |= (num & 3) << 3; - } - - public void setSortFlag(byte num) - { - byte_ |= (num & 1) << 5; - } - - public void setInterlaceFlag(byte num) - { - byte_ |= (num & 1) << 6; - } - - public void setLocalColorTableFlag(byte num) - { - byte_ |= (num & 1) << 7; - } +class ImageDescriptor { + public byte separator_; + public short leftPosition_, topPosition_, width_, height_; + private byte byte_; + + public ImageDescriptor(short width, short height, char separator) { + separator_ = (byte) separator; + leftPosition_ = 0; + topPosition_ = 0; + width_ = width; + height_ = height; + setLocalColorTableSize((byte) 0); + setReserved((byte) 0); + setSortFlag((byte) 0); + setInterlaceFlag((byte) 0); + setLocalColorTableFlag((byte) 0); + } + + public void write(OutputStream output) throws IOException { + output.write(separator_); + BitUtils.writeWord(output, leftPosition_); + BitUtils.writeWord(output, topPosition_); + BitUtils.writeWord(output, width_); + BitUtils.writeWord(output, height_); + output.write(byte_); + } + + public void setLocalColorTableSize(byte num) { + byte_ |= (num & 7); + } + + public void setReserved(byte num) { + byte_ |= (num & 3) << 3; + } + + public void setSortFlag(byte num) { + byte_ |= (num & 1) << 5; + } + + public void setInterlaceFlag(byte num) { + byte_ |= (num & 1) << 6; + } + + public void setLocalColorTableFlag(byte num) { + byte_ |= (num & 1) << 7; + } } -class BitUtils -{ - public static byte bitsNeeded(int n) - { - byte ret = 1; - - if (n-- == 0) - return 0; - - while ((n >>= 1) != 0) - ++ret; - - return ret; - } - - public static void writeWord(OutputStream output, - short w) throws IOException - { - output.write(w & 0xFF); - output.write((w >> 8) & 0xFF); - } - - static void writeString(OutputStream output, - String string) throws IOException - { - for (int loop = 0; loop < string.length(); ++loop) - output.write((byte)(string.charAt(loop))); - } -} +class BitUtils { + public static byte bitsNeeded(int n) { + byte ret = 1; + if (n-- == 0) + return 0; + while ((n >>= 1) != 0) + ++ret; + + return ret; + } + + public static void writeWord(OutputStream output, short w) throws IOException { + output.write(w & 0xFF); + output.write((w >> 8) & 0xFF); + } + + static void writeString(OutputStream output, String string) throws IOException { + for (int loop = 0; loop < string.length(); ++loop) + output.write((byte) (string.charAt(loop))); + } +} diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java index 6c8d7ee..ad94ddd 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/ObjectInspector.java @@ -44,183 +44,165 @@ import net.wotonomy.ui.swing.components.PropertyEditorTable; import net.wotonomy.ui.swing.components.PropertyEditorTableModel; /** -* The ObjectInspector displays a JFrame containing -* a PropertyEditorTable that displays an object.

-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ + * The ObjectInspector displays a JFrame containing a PropertyEditorTable that + * displays an object.
+ *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ -public class ObjectInspector implements ActionListener, MouseListener -{ - protected JTable table = null; +public class ObjectInspector implements ActionListener, MouseListener { + protected JTable table = null; - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + // key command to copy contents to clipboard + static public final String COPY = "COPY"; /** - * Displays the specified object in a frame. - */ - public ObjectInspector( Object anObject ) - { - initLayout( anObject ); - } - - protected void initLayout( Object aTargetObject ) - { - PropertyEditorTableModel model = - new PropertyEditorTableModel(); - model.setObject( aTargetObject ); - table = new PropertyEditorTable() - { - public void methodInvoked( Object anObject, Method aMethod, Object aResult ) - { - if - ( ( aResult == null ) - || ( aResult instanceof Number ) - || ( aResult instanceof Boolean ) - || ( aResult instanceof String ) ) - { - System.out.println( aMethod.getName() + ": " + aResult ); - } - else - { - new ObjectInspector( aResult ); + * Displays the specified object in a frame. + */ + public ObjectInspector(Object anObject) { + initLayout(anObject); + } + + protected void initLayout(Object aTargetObject) { + PropertyEditorTableModel model = new PropertyEditorTableModel(); + model.setObject(aTargetObject); + table = new PropertyEditorTable() { + public void methodInvoked(Object anObject, Method aMethod, Object aResult) { + if ((aResult == null) || (aResult instanceof Number) || (aResult instanceof Boolean) + || (aResult instanceof String)) { + System.out.println(aMethod.getName() + ": " + aResult); + } else { + new ObjectInspector(aResult); } } }; - table.setModel( model ); - table.addMouseListener( this ); // listen for double-clicks - - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 325, 350 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( aTargetObject.getClass().getName() ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - // interface MouseListener - - /** - * Double click to call invokeFileFromString. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - - if ( ( row == -1 ) || ( col != 0 ) ) return; - - /* do something here */ - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - - // interface ActionEventListener - for listening to key commands - - public void actionPerformed(ActionEvent evt) - { - if ( COPY.equals( evt.getActionCommand() ) ) - { - copyToClipboard(); - return; - } - } + table.setModel(model); + table.addMouseListener(this); // listen for double-clicks + + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(325, 350)); + panel.add(scrollPane, BorderLayout.CENTER); + + JFrame window = new JFrame(); + window.setTitle(aTargetObject.getClass().getName()); + window.getContentPane().add(panel); + + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } + + // interface MouseListener -/** -* Copies the contents of the table to the clipboard as a tab-delimited string. -*/ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getTabDelimitedString() ); - clipboard.setContents( selection, selection ); - } - /** - * Converts the contents of the table to a tab-delimited string. - * @return A String containing the text contents of the table. - */ - public String getTabDelimitedString() - { - StringBuffer result = new StringBuffer(64); - - TableModel model = table.getModel(); - int cols = model.getColumnCount(); - int rows = model.getRowCount(); - - Object o = null; - for ( int y = 0; y < rows; y++ ) - { - for ( int x = 0; x < cols; x++ ) - { - o = model.getValueAt( y, x ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\t' ); - } - result.append( '\n' ); - } - - return result.toString(); - } + * Double click to call invokeFileFromString. + */ + + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); + + if ((row == -1) || (col != 0)) + return; + + /* do something here */ + } + } + } + + public void mouseReleased(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + // interface ActionEventListener - for listening to key commands + + public void actionPerformed(ActionEvent evt) { + if (COPY.equals(evt.getActionCommand())) { + copyToClipboard(); + return; + } + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getTabDelimitedString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the contents of the table to a tab-delimited string. + * + * @return A String containing the text contents of the table. + */ + public String getTabDelimitedString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = table.getModel(); + int cols = model.getColumnCount(); + int rows = model.getRowCount(); + + Object o = null; + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + o = model.getValueAt(y, x); + if (o == null) + o = ""; + result.append(o); + result.append('\t'); + } + result.append('\n'); + } + + return result.toString(); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.3 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.3 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.2 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java index f5fe3e4..7f2adb8 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/PositionComparator.java @@ -27,63 +27,55 @@ import java.util.Comparator; import javax.swing.SwingUtilities; /** -* A Comparator that will sort components in a common container -* based first on their y-coordinate and then on their x-coordinate, -* producing a list sorted from top to bottom and left to right. -* If all components are not in the same container, the resulting -* sort is undefined. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class PositionComparator implements Comparator, Serializable -{ - private Container rootContainer; - private transient Component c1, c2; - private transient Point p1, p2; + * A Comparator that will sort components in a common container based first on + * their y-coordinate and then on their x-coordinate, producing a list sorted + * from top to bottom and left to right. If all components are not in the same + * container, the resulting sort is undefined. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class PositionComparator implements Comparator, Serializable { + private Container rootContainer; + private transient Component c1, c2; + private transient Point p1, p2; -/** -* Standard constructor to configure the comparator. -* @param aContainer The common container for all the objects to be compared. -*/ - public PositionComparator( Container aContainer ) - { - rootContainer = aContainer; - } + /** + * Standard constructor to configure the comparator. + * + * @param aContainer The common container for all the objects to be compared. + */ + public PositionComparator(Container aContainer) { + rootContainer = aContainer; + } - // interface Comparable + // interface Comparable - public int compare(Object o1, Object o2) - { - c1 = (Component) o1; - c2 = (Component) o2; + public int compare(Object o1, Object o2) { + c1 = (Component) o1; + c2 = (Component) o2; - p1 = SwingUtilities.convertPoint( c1.getParent(), c1.getLocation(), rootContainer ); - p2 = SwingUtilities.convertPoint( c2.getParent(), c2.getLocation(), rootContainer ); + p1 = SwingUtilities.convertPoint(c1.getParent(), c1.getLocation(), rootContainer); + p2 = SwingUtilities.convertPoint(c2.getParent(), c2.getLocation(), rootContainer); - if ( p1.y != p2.y ) - { - return p1.y - p2.y; - } - return p1.x - p2.x; - } + if (p1.y != p2.y) { + return p1.y - p2.y; + } + return p1.x - p2.x; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * 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:51:27 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:27 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java index 7e61411..ae4bb2d 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/StackTraceInspector.java @@ -50,408 +50,364 @@ import javax.swing.table.TableModel; import net.wotonomy.ui.swing.components.MultiLineLabel; /** -* The StackTraceInspector displays a JFrame containing -* stack trace information for a Throwable.

-* -* There are also a few static methods for obtaining -* information about the current stack, which is useful -* for determining who's calling you at runtime. -* -* @author michael@mpowers.net -* @version $Revision: 904 $ -*/ + * The StackTraceInspector displays a JFrame containing stack trace information + * for a Throwable.
+ *
+ * + * There are also a few static methods for obtaining information about the + * current stack, which is useful for determining who's calling you at runtime. + * + * @author michael@mpowers.net + * @version $Revision: 904 $ + */ -public class StackTraceInspector - implements TableModel, MouseListener, ActionListener -{ - protected JTable table = null; - protected List tableModelListeners = null; - protected List methodNames = new ArrayList(); +public class StackTraceInspector implements TableModel, MouseListener, ActionListener { + protected JTable table = null; + protected List tableModelListeners = null; + protected List methodNames = new ArrayList(); + + // key command to copy contents to clipboard + static public final String COPY = "COPY"; + + /** + * Displays the current stack trace at the time of instantiation in a table on a + * frame. + */ + public StackTraceInspector() { + initLayout(parseStackTrace(new RuntimeException()), null); + } + + /** + * Displays the current stack trace at the time of instantiation in a table on a + * frame annotated with the specified message. + */ + public StackTraceInspector(String aMessage) { + initLayout(parseStackTrace(new RuntimeException()), aMessage); + } + + /** + * Displays the stack trace for the given throwable in a table on a frame. + * + * @param aThrowable A Throwable whose stack will be examined. + */ + public StackTraceInspector(Throwable aThrowable) { + initLayout(parseStackTrace(aThrowable), aThrowable.getClass() + ": " + aThrowable.getMessage()); + } + + /** + * Simply displays the list items in a dialog. Presumably (but not necessarily) + * called from the other constructors. (I guess if you just want a frame with + * strings in table, you can call this.) + * + * @param aStringList A List containing Strings. + */ + public StackTraceInspector(List aStringList) { + initLayout(aStringList, null); + } + + protected void initLayout(List items, String message) { + methodNames = new ArrayList(items); + table = new JTable(this); // this class is the table model + table.addMouseListener(this); // listen for double-clicks + + // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + table.registerKeyboardAction(this, COPY, + KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); + panel.setLayout(new BorderLayout(10, 10)); + + if (message != null) { + panel.add(new MultiLineLabel(message), BorderLayout.NORTH); + } - // key command to copy contents to clipboard - static public final String COPY = "COPY"; + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setPreferredSize(new Dimension(325, 350)); + panel.add(scrollPane, BorderLayout.CENTER); -/** -* Displays the current stack trace at the time -* of instantiation in a table on a frame. -*/ - public StackTraceInspector() - { - initLayout( parseStackTrace( new RuntimeException() ), null ); - } + JFrame window = new JFrame(); + window.setTitle("Stack Trace Inspector"); + window.getContentPane().add(panel); -/** -* Displays the current stack trace at the time -* of instantiation in a table on a frame -* annotated with the specified message. -*/ - public StackTraceInspector( String aMessage ) - { - initLayout( parseStackTrace( new RuntimeException() ), aMessage ); - } + window.pack(); + WindowUtilities.cascade(window); + window.show(); + } -/** -* Displays the stack trace for the given throwable -* in a table on a frame. -* @param aThrowable A Throwable whose stack will be examined. -*/ - public StackTraceInspector( Throwable aThrowable ) - { - initLayout( parseStackTrace( aThrowable ), - aThrowable.getClass() + ": " + aThrowable.getMessage() ); - } + // interface TableModel -/** -* Simply displays the list items in a dialog. -* Presumably (but not necessarily) called from -* the other constructors. (I guess if you just -* want a frame with strings in table, you can -* call this.) -* @param aStringList A List containing Strings. -*/ - public StackTraceInspector( List aStringList ) - { - initLayout( aStringList, null ); - } - - protected void initLayout( List items, String message ) - { - methodNames = new ArrayList( items ); - table = new JTable( this ); // this class is the table model - table.addMouseListener( this ); // listen for double-clicks - - // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_C, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - table.registerKeyboardAction( this, COPY, - KeyStroke.getKeyStroke( KeyEvent.VK_X, - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); - - JPanel panel = new JPanel(); - panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) ); - panel.setLayout( new BorderLayout( 10, 10 ) ); - - if ( message != null ) - { - panel.add( new MultiLineLabel( message ), BorderLayout.NORTH ); + public int getRowCount() { + return methodNames.size(); + } + + public int getColumnCount() { + return 1; + } + + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return "Methods"; + case 1: + return "Property"; } + System.out.println("StackTraceInspector.getColumnName: unknown column: " + columnIndex); + return ""; + } + + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return String.class; + case 1: + return String.class; + } + System.out.println("StackTraceInspector.getColumnClass: unknown column: " + columnIndex); + return Object.class; + } - JScrollPane scrollPane = new JScrollPane( table ); - scrollPane.setPreferredSize( new Dimension( 325, 350 ) ); - panel.add( scrollPane, BorderLayout.CENTER ); - - JFrame window = new JFrame(); - window.setTitle( "Stack Trace Inspector" ); - window.getContentPane().add( panel ); - - window.pack(); - WindowUtilities.cascade( window ); - window.show(); - } - - // interface TableModel - - public int getRowCount() - { - return methodNames.size(); - } - - public int getColumnCount() - { - return 1; - } - - public String getColumnName(int columnIndex) - { - switch ( columnIndex ) - { - case 0: - return "Methods"; - case 1: - return "Property"; - } - System.out.println( "StackTraceInspector.getColumnName: unknown column: " + columnIndex ); - return ""; - } - - public Class getColumnClass(int columnIndex) - { - switch ( columnIndex ) - { - case 0: - return String.class; - case 1: - return String.class; - } - System.out.println( "StackTraceInspector.getColumnClass: unknown column: " + columnIndex ); - return Object.class; - } - - public boolean isCellEditable(int rowIndex, - int columnIndex) - { - return false; - } - - public Object getValueAt(int rowIndex, - int columnIndex) - { - return methodNames.get( rowIndex ); - } - - public void setValueAt(Object aValue, - int rowIndex, - int columnIndex) - { - } - - public void addTableModelListener(TableModelListener l) - { - if ( tableModelListeners == null ) - { - tableModelListeners = new ArrayList(); - } - tableModelListeners.add( l ); - } - - public void removeTableModelListener(TableModelListener l) - { - if ( tableModelListeners != null ) - { - tableModelListeners.remove( l ); - } - } - - // interface MouseListener - - /** - * Double click to call invokeFileFromString. - */ - - public void mouseClicked(MouseEvent e) - { - if ( e.getSource() == table ) - { - if ( e.getClickCount() > 1 ) - { - int row = table.rowAtPoint( e.getPoint() ); - int col = table.columnAtPoint( e.getPoint() ); - - if ( ( row == -1 ) || ( col != 0 ) ) return; - - invokeFileFromString( methodNames.get( row ).toString() ); - } - } - } - - public void mouseReleased(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - - // interface ActionEventListener - for listening to key commands - - public void actionPerformed(ActionEvent evt) - { - if ( COPY.equals( evt.getActionCommand() ) ) - { - copyToClipboard(); - return; - } - } + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } -/** -* Copies the contents of the table to the clipboard as a tab-delimited string. -*/ - public void copyToClipboard() - { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = - new StringSelection( getSelectedStackString() ); - clipboard.setContents( selection, selection ); - } + public Object getValueAt(int rowIndex, int columnIndex) { + return methodNames.get(rowIndex); + } -/** -* Converts the selected contents of the table to a string. -* @return A String containing the text contents of the table. -*/ - public String getSelectedStackString() - { - StringBuffer result = new StringBuffer(64); + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + } - TableModel model = table.getModel(); + public void addTableModelListener(TableModelListener l) { + if (tableModelListeners == null) { + tableModelListeners = new ArrayList(); + } + tableModelListeners.add(l); + } - Object o; - int[] selectedRows = table.getSelectedRows(); - for ( int i = 0; i < selectedRows.length; i++ ) - { - o = model.getValueAt( selectedRows[i], 0 ); - if ( o == null ) o = ""; - result.append( o ); - result.append( '\n' ); - } + public void removeTableModelListener(TableModelListener l) { + if (tableModelListeners != null) { + tableModelListeners.remove(l); + } + } - return result.toString(); - } + // interface MouseListener + /** + * Double click to call invokeFileFromString. + */ - // static methods + public void mouseClicked(MouseEvent e) { + if (e.getSource() == table) { + if (e.getClickCount() > 1) { + int row = table.rowAtPoint(e.getPoint()); + int col = table.columnAtPoint(e.getPoint()); -/** -* Obtains a list of strings representing the stack trace -* associated with this throwable starting with the most recent call. -* @param aThrowable A Throwable whose stack trace is parsed. -* @return a List containing the method names as Strings. -*/ - static public List parseStackTrace( Throwable aThrowable ) - { - String trace = null; + if ((row == -1) || (col != 0)) + return; - // create new stream - ByteArrayOutputStream os = new ByteArrayOutputStream( 256 ); - PrintStream newErr = new PrintStream( os ); - aThrowable.printStackTrace( newErr ); // prints to System.err + invokeFileFromString(methodNames.get(row).toString()); + } + } + } - // convert to string - trace = os.toString(); + public void mouseReleased(MouseEvent e) { + } - List result = new ArrayList(); + public void mousePressed(MouseEvent e) { + } - // populate list with parsed trace, starting from top - String token; - StringTokenizer tokens = new StringTokenizer( trace, "\n" ); - tokens.nextToken(); // strip off description of throwable - while ( tokens.hasMoreTokens() ) - { - token = tokens.nextToken(); - if ( token.indexOf( StackTraceInspector.class.getName() ) == -1 ) - { // add only those methods not from this class + public void mouseEntered(MouseEvent e) { + } - // strip whitespace, "at " from front, and \r from end - token.trim(); - token = token.substring( 4, token.length() - 1 ); + public void mouseExited(MouseEvent e) { + } - result.add( token ); - } - } + // interface ActionEventListener - for listening to key commands - return result; - } + public void actionPerformed(ActionEvent evt) { + if (COPY.equals(evt.getActionCommand())) { + copyToClipboard(); + return; + } + } + + /** + * Copies the contents of the table to the clipboard as a tab-delimited string. + */ + public void copyToClipboard() { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(getSelectedStackString()); + clipboard.setContents(selection, selection); + } + + /** + * Converts the selected contents of the table to a string. + * + * @return A String containing the text contents of the table. + */ + public String getSelectedStackString() { + StringBuffer result = new StringBuffer(64); + + TableModel model = table.getModel(); + + Object o; + int[] selectedRows = table.getSelectedRows(); + for (int i = 0; i < selectedRows.length; i++) { + o = model.getValueAt(selectedRows[i], 0); + if (o == null) + o = ""; + result.append(o); + result.append('\n'); + } -/** -* Convenience method that obtains a String representing -* the caller's caller. -* @return a String representing a method in stack trace format. -*/ - static public String getMyCaller() - { - List trace = parseStackTrace( new RuntimeException() ); - if ( trace.size() > 1 ) - { - return trace.get( 1 ).toString(); - } + return result.toString(); + } + + // static methods + + /** + * Obtains a list of strings representing the stack trace associated with this + * throwable starting with the most recent call. + * + * @param aThrowable A Throwable whose stack trace is parsed. + * @return a List containing the method names as Strings. + */ + static public List parseStackTrace(Throwable aThrowable) { + String trace = null; + + // create new stream + ByteArrayOutputStream os = new ByteArrayOutputStream(256); + PrintStream newErr = new PrintStream(os); + aThrowable.printStackTrace(newErr); // prints to System.err + + // convert to string + trace = os.toString(); + + List result = new ArrayList(); + + // populate list with parsed trace, starting from top + String token; + StringTokenizer tokens = new StringTokenizer(trace, "\n"); + tokens.nextToken(); // strip off description of throwable + while (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + if (token.indexOf(StackTraceInspector.class.getName()) == -1) { // add only those methods not from this + // class + + // strip whitespace, "at " from front, and \r from end + token.trim(); + token = token.substring(4, token.length() - 1); + + result.add(token); + } + } - return null; - } + return result; + } + + /** + * Convenience method that obtains a String representing the caller's caller. + * + * @return a String representing a method in stack trace format. + */ + static public String getMyCaller() { + List trace = parseStackTrace(new RuntimeException()); + if (trace.size() > 1) { + return trace.get(1).toString(); + } -/** -* Prints a stack trace up to the first method whose fully -* qualified class name begins with "java" to System.out. -*/ - static public void printShortStackTrace() - { - String s; - Iterator i = parseStackTrace( new RuntimeException() ).iterator(); - while ( i.hasNext() ) - { - System.out.println( " " + ( s = i.next().toString() ) ); - if ( s.startsWith( "java" ) ) break; - } - } - - protected void invokeFileFromString( String aString ) - { - // strip off parentheses, if any - int openParam = aString.indexOf( "(" ); - if ( openParam != -1 ) - { - aString = aString.substring( 0, openParam ); - } - - // separate class name from method name - int lastDot = aString.lastIndexOf( "." ); - if ( lastDot == -1 ) return; - String className = aString.substring( 0, lastDot ); - String methodName = aString.substring( lastDot + 1 ); - - // convert "."s to file separator characters - StringBuffer buf = new StringBuffer(); - StringTokenizer tokens = new StringTokenizer( className, "." ); - while ( true ) - { - buf.append( tokens.nextToken() ); - if ( ! tokens.hasMoreTokens() ) break; - buf.append( File.separator ); - } - String path = buf.toString(); - java.net.URL url = ClassLoader.getSystemResource( path + ".java" ); - if ( url == null ) return; // do nothing - - String name = url.getFile(); - - // try to launch the document - try - { - // NOTE: This is Windows-dependent! - String args[] = new String[] { - "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" }; - // this translates to: cmd /c "start "" "path"" - // apparently an array is more reliable for calling exec(). - // all the extra quotes are to handle paths with spaces. - // trims off the first "/" before the drive letter. - // needed a dummy title for the console or it wouldn't work. - Runtime.getRuntime().exec( args ); - } - catch ( Exception exc ) - { - System.out.println( "DocumentLinkPanel.invokeDocument: " + exc ); - } - return; - } + return null; + } + + /** + * Prints a stack trace up to the first method whose fully qualified class name + * begins with "java" to System.out. + */ + static public void printShortStackTrace() { + String s; + Iterator i = parseStackTrace(new RuntimeException()).iterator(); + while (i.hasNext()) { + System.out.println(" " + (s = i.next().toString())); + if (s.startsWith("java")) + break; + } + } + + protected void invokeFileFromString(String aString) { + // strip off parentheses, if any + int openParam = aString.indexOf("("); + if (openParam != -1) { + aString = aString.substring(0, openParam); + } + + // separate class name from method name + int lastDot = aString.lastIndexOf("."); + if (lastDot == -1) + return; + String className = aString.substring(0, lastDot); + String methodName = aString.substring(lastDot + 1); + + // convert "."s to file separator characters + StringBuffer buf = new StringBuffer(); + StringTokenizer tokens = new StringTokenizer(className, "."); + while (true) { + buf.append(tokens.nextToken()); + if (!tokens.hasMoreTokens()) + break; + buf.append(File.separator); + } + String path = buf.toString(); + java.net.URL url = ClassLoader.getSystemResource(path + ".java"); + if (url == null) + return; // do nothing + + String name = url.getFile(); + + // try to launch the document + try { + // NOTE: This is Windows-dependent! + String args[] = new String[] { "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" }; + // this translates to: cmd /c "start "" "path"" + // apparently an array is more reliable for calling exec(). + // all the extra quotes are to handle paths with spaces. + // trims off the first "/" before the drive letter. + // needed a dummy title for the console or it wouldn't work. + Runtime.getRuntime().exec(args); + } catch (Exception exc) { + System.out.println("DocumentLinkPanel.invokeDocument: " + exc); + } + return; + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.5 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.4 2002/11/16 16:33:31 mpowers - * Now using platform-specific accelerator key for shortcuts. + * Revision 1.4 2002/11/16 16:33:31 mpowers Now using platform-specific + * accelerator key for shortcuts. * - * Revision 1.3 2001/07/18 21:53:33 mpowers - * Added a string argument for display as a message. + * Revision 1.3 2001/07/18 21:53:33 mpowers Added a string argument for display + * as a message. * - * Revision 1.2 2001/07/17 14:01:43 mpowers - * Added short stack trace method. + * Revision 1.2 2001/07/17 14:01:43 mpowers Added short stack trace method. * - * Revision 1.1.1.1 2000/12/21 15:51:34 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:34 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:45 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:45 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java index 36dbacf..20f37c1 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/TextInputRangeChecker.java @@ -30,339 +30,298 @@ import javax.swing.SwingUtilities; import javax.swing.text.JTextComponent; /** -* This class will actively check the inputs of 2 numbers in seperate text -* components. The number in the text components represent an upper and lower -* bound to some range. This class checks to make sure the user inputs values -* in the lower bound text field that are less than the value of the upper -* bound and vice versa for the upper bound text field. This class will also -* check to make sure the bounds fall within a given range if specified. -* -* The checks are automatically performed when the focus is lost on either -* component. If the inputs are correct then no event occurs. If the inputs -* are not correct, then a dialog message is displayed stating the reason why -* the bounds are invalid, and the original correct value is restored into the -* text components. -* -* @author rglista -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class TextInputRangeChecker implements FocusListener -{ - protected static final int NONE = 0; - protected static final int LOWER = 1; - protected static final int UPPER = 2; - - private JTextComponent lowerComponent; - private JTextComponent upperComponent; - private double maxRange; - private double lowerNumber; - private double upperNumber; - private Collection focusListeners; - - private String invalidLowerMessage; - private String invalidUpperMessage; - private String invalidEitherMessage; - private String invalidRangeMessage; - - -/** -* Constructor with some of the settable parameters. No range checking is used. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent ) - { - this( aLowerTextComponent, anUpperTextComponent, null, null, 0.0 ); - } - -/** -* Constructor with some of the settable parameters. No range checking is -* used. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param lowerTextName The name of the lower bound, eg - start year. -* @param upperTextName The name of the upper bound, eg - end year. -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - String lowerTextName, String upperTextName ) - { - this( aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0 ); - } - -/** -* Constructor with some of the settable parameters. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param aMaxRange The range the bounds muist fall between, if 0 then no range -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - double aMaxRange ) - { - this( aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange ); - } - -/** -* Constructor with all the settable parameters. -* @param aLowerTextComponent A text component for the lower bound. -* @param anUpperTextComponent A text component for the upper bound. -* @param lowerTextName The name of the lower bound, eg - start year. -* @param upperTextName The name of the upper bound, eg - end year. -* @param aMaxRange The range the bounds muist fall between, if 0 then no range -* is used. -*/ - public TextInputRangeChecker( JTextComponent aLowerTextComponent, - JTextComponent anUpperTextComponent, - String lowerTextName, String upperTextName, - double aMaxRange ) - { - lowerComponent = aLowerTextComponent; - upperComponent = anUpperTextComponent; - maxRange = aMaxRange; - - focusListeners = new ArrayList( 1 ); // For most cases, there will be only 1 listener. - - lowerComponent.addFocusListener( this ); - upperComponent.addFocusListener( this ); - - lowerNumber = getNumber( lowerComponent ); - upperNumber = getNumber( upperComponent ); - - if ( ( lowerTextName != null ) && ( upperTextName != null ) ) - { - invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + "."; - invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName + "."; - invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct."; - invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is " + maxRange + "."; - } - else - { - invalidLowerMessage = "The lower bound must be less than or equal to the upper bound."; - invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound."; - invalidEitherMessage = "The upper and/or lower bounds are not correct."; - invalidRangeMessage = "The maximum range is " + maxRange + "."; - } - } - -/** -* Allows the caller to perform the validation of the bounds programatically. -* The lower bound is compared to the upper bound and range checking is performed. -* If the lower bound is greater than the upper bound, or the range between the -* bounds is greater than the max range, then validation fails. -* @return TRUE is validation is successfull, FALSE if it fails. -*/ - public boolean performCheck() - { - return validate( null ); - } - -/** -* Adds the listener to the lists of focus listener maintened by this object. -* When one of the 2 text components receives a focus event, this object will -* fire that focus event to any of its listeners. This is useful when the -* calling object wants to be notified of the components focus events, but wants -* to ensure that the validation has occured first. -*

-* NOTE: The focus is only fired if the validation was successful. This might -* have to be changed. -* @param aListener A Focus Listener to receive Focus Events. -*/ - public void addFocusListener( FocusListener aListener ) - { - focusListeners.add( aListener ); - } - -/** -* Returns the last valid value of the lower bound. If this is called while -* the user is updating the text component but before the focus is lost, the -* value returned will be the original value before the user started updating -* the bound. -* @return The last valid value of the lower bound. -*/ - public double getLastValidatedLowerNumber() - { - return lowerNumber; - } - -/** -* Returns the last valid value of the upper bound. If this is called while -* the user is updating the text component but before the focus is lost, the -* value returned will be the original value before the user started updating -* the bound. -* @return The last valid value of the upper bound. -*/ - public double getLastValidatedUpperNumber() - { - return upperNumber; - } - -/** -* Method used to be notified when one of the text components has gained its -* focus. -*/ - public void focusGained( FocusEvent e ) - { - lowerNumber = getNumber( lowerComponent ); - upperNumber = getNumber( upperComponent ); - } - -/** -* Method used to be notified when one of the text components has lost its -* focus. Automatic validation occurs here. -*/ - public void focusLost( FocusEvent e ) - { - if ( e.isTemporary() ) - { - return; - } - - if ( validate( e.getSource() ) ) - { - fireFocusEvent( e ); - } - } - -/** -* Fires a focus lost event if the validation was successfull. -*/ - protected void fireFocusEvent( FocusEvent e ) - { - for ( Iterator it = focusListeners.iterator(); it.hasNext(); ) - { - ( ( FocusListener )it.next() ).focusLost( e ); - } - } - -/** -* Validates the bounds inputed by the user. -* @param aComponent The component to use to display a dialog window, if neccessray. -* If null, then the parent window of the text componets will be used. -* @return TRUE if validation was successful, FALSE otherwise. -*/ - protected boolean validate( Object aComponent ) - { - int componentUsed = NONE; - if ( aComponent == lowerComponent ) - { - componentUsed = LOWER; - } - else if ( aComponent == upperComponent ) - { - componentUsed = UPPER; - } - - double lower = getNumber( lowerComponent ); - double upper = getNumber( upperComponent ); - - if ( lower > upper ) - { - if ( componentUsed == LOWER ) - { - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidLowerMessage, lowerComponent ); - } - else if ( componentUsed == UPPER ) - { - upperComponent.setText( Double.toString( upperNumber ) ); - displayMessage( invalidUpperMessage, upperComponent ); - } - else - { - upperComponent.setText( Double.toString( upperNumber ) ); - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidEitherMessage, lowerComponent.getTopLevelAncestor() ); - } - - return false; - } - - if ( maxRange != 0.0 ) - { - if ( ( upper - lower ) > maxRange ) - { - if ( componentUsed == LOWER ) - { - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidRangeMessage, lowerComponent ); - } - else if ( componentUsed == UPPER ) - { - upperComponent.setText( Double.toString( upperNumber ) ); - displayMessage( invalidRangeMessage, upperComponent ); - } - else - { - upperComponent.setText( Double.toString( upperNumber ) ); - lowerComponent.setText( Double.toString( lowerNumber ) ); - displayMessage( invalidRangeMessage, lowerComponent.getTopLevelAncestor() ); - } - - return false; - } - } - - lowerNumber = lower; - upperNumber = upper; - return true; - } - -/** -* Creates a JOptionPane to display the reason why the bounds failed validation. -*/ - protected void displayMessage( final String message, final Component parent ) - { - SwingUtilities.invokeLater( new Runnable() - { - public void run() - { - JOptionPane.showMessageDialog( parent, message, "Data Entry Error", - JOptionPane.ERROR_MESSAGE ); - } - } ); - } - -/** -* Gets the number represented in the text component. If the text does not -* represent a number, then zero is returned. -*/ - protected double getNumber( JTextComponent aComponent ) - { - try - { - return Double.valueOf( aComponent.getText() ).doubleValue(); + * This class will actively check the inputs of 2 numbers in seperate text + * components. The number in the text components represent an upper and lower + * bound to some range. This class checks to make sure the user inputs values in + * the lower bound text field that are less than the value of the upper bound + * and vice versa for the upper bound text field. This class will also check to + * make sure the bounds fall within a given range if specified. + * + * The checks are automatically performed when the focus is lost on either + * component. If the inputs are correct then no event occurs. If the inputs are + * not correct, then a dialog message is displayed stating the reason why the + * bounds are invalid, and the original correct value is restored into the text + * components. + * + * @author rglista + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class TextInputRangeChecker implements FocusListener { + protected static final int NONE = 0; + protected static final int LOWER = 1; + protected static final int UPPER = 2; + + private JTextComponent lowerComponent; + private JTextComponent upperComponent; + private double maxRange; + private double lowerNumber; + private double upperNumber; + private Collection focusListeners; + + private String invalidLowerMessage; + private String invalidUpperMessage; + private String invalidEitherMessage; + private String invalidRangeMessage; + + /** + * Constructor with some of the settable parameters. No range checking is used. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent) { + this(aLowerTextComponent, anUpperTextComponent, null, null, 0.0); + } + + /** + * Constructor with some of the settable parameters. No range checking is used. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param lowerTextName The name of the lower bound, eg - start year. + * @param upperTextName The name of the upper bound, eg - end year. is + * used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + String lowerTextName, String upperTextName) { + this(aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0); + } + + /** + * Constructor with some of the settable parameters. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param aMaxRange The range the bounds muist fall between, if 0 + * then no range is used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + double aMaxRange) { + this(aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange); + } + + /** + * Constructor with all the settable parameters. + * + * @param aLowerTextComponent A text component for the lower bound. + * @param anUpperTextComponent A text component for the upper bound. + * @param lowerTextName The name of the lower bound, eg - start year. + * @param upperTextName The name of the upper bound, eg - end year. + * @param aMaxRange The range the bounds muist fall between, if 0 + * then no range is used. + */ + public TextInputRangeChecker(JTextComponent aLowerTextComponent, JTextComponent anUpperTextComponent, + String lowerTextName, String upperTextName, double aMaxRange) { + lowerComponent = aLowerTextComponent; + upperComponent = anUpperTextComponent; + maxRange = aMaxRange; + + focusListeners = new ArrayList(1); // For most cases, there will be only 1 listener. + + lowerComponent.addFocusListener(this); + upperComponent.addFocusListener(this); + + lowerNumber = getNumber(lowerComponent); + upperNumber = getNumber(upperComponent); + + if ((lowerTextName != null) && (upperTextName != null)) { + invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + "."; + invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName + + "."; + invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct."; + invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is " + + maxRange + "."; + } else { + invalidLowerMessage = "The lower bound must be less than or equal to the upper bound."; + invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound."; + invalidEitherMessage = "The upper and/or lower bounds are not correct."; + invalidRangeMessage = "The maximum range is " + maxRange + "."; + } + } + + /** + * Allows the caller to perform the validation of the bounds programatically. + * The lower bound is compared to the upper bound and range checking is + * performed. If the lower bound is greater than the upper bound, or the range + * between the bounds is greater than the max range, then validation fails. + * + * @return TRUE is validation is successfull, FALSE if it fails. + */ + public boolean performCheck() { + return validate(null); + } + + /** + * Adds the listener to the lists of focus listener maintened by this object. + * When one of the 2 text components receives a focus event, this object will + * fire that focus event to any of its listeners. This is useful when the + * calling object wants to be notified of the components focus events, but wants + * to ensure that the validation has occured first.
+ *
+ * NOTE: The focus is only fired if the validation was successful. This might + * have to be changed. + * + * @param aListener A Focus Listener to receive Focus Events. + */ + public void addFocusListener(FocusListener aListener) { + focusListeners.add(aListener); + } + + /** + * Returns the last valid value of the lower bound. If this is called while the + * user is updating the text component but before the focus is lost, the value + * returned will be the original value before the user started updating the + * bound. + * + * @return The last valid value of the lower bound. + */ + public double getLastValidatedLowerNumber() { + return lowerNumber; + } + + /** + * Returns the last valid value of the upper bound. If this is called while the + * user is updating the text component but before the focus is lost, the value + * returned will be the original value before the user started updating the + * bound. + * + * @return The last valid value of the upper bound. + */ + public double getLastValidatedUpperNumber() { + return upperNumber; + } + + /** + * Method used to be notified when one of the text components has gained its + * focus. + */ + public void focusGained(FocusEvent e) { + lowerNumber = getNumber(lowerComponent); + upperNumber = getNumber(upperComponent); + } + + /** + * Method used to be notified when one of the text components has lost its + * focus. Automatic validation occurs here. + */ + public void focusLost(FocusEvent e) { + if (e.isTemporary()) { + return; + } + + if (validate(e.getSource())) { + fireFocusEvent(e); + } + } + + /** + * Fires a focus lost event if the validation was successfull. + */ + protected void fireFocusEvent(FocusEvent e) { + for (Iterator it = focusListeners.iterator(); it.hasNext();) { + ((FocusListener) it.next()).focusLost(e); + } + } + + /** + * Validates the bounds inputed by the user. + * + * @param aComponent The component to use to display a dialog window, if + * neccessray. If null, then the parent window of the text + * componets will be used. + * @return TRUE if validation was successful, FALSE otherwise. + */ + protected boolean validate(Object aComponent) { + int componentUsed = NONE; + if (aComponent == lowerComponent) { + componentUsed = LOWER; + } else if (aComponent == upperComponent) { + componentUsed = UPPER; + } + + double lower = getNumber(lowerComponent); + double upper = getNumber(upperComponent); + + if (lower > upper) { + if (componentUsed == LOWER) { + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidLowerMessage, lowerComponent); + } else if (componentUsed == UPPER) { + upperComponent.setText(Double.toString(upperNumber)); + displayMessage(invalidUpperMessage, upperComponent); + } else { + upperComponent.setText(Double.toString(upperNumber)); + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidEitherMessage, lowerComponent.getTopLevelAncestor()); + } + + return false; + } + + if (maxRange != 0.0) { + if ((upper - lower) > maxRange) { + if (componentUsed == LOWER) { + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidRangeMessage, lowerComponent); + } else if (componentUsed == UPPER) { + upperComponent.setText(Double.toString(upperNumber)); + displayMessage(invalidRangeMessage, upperComponent); + } else { + upperComponent.setText(Double.toString(upperNumber)); + lowerComponent.setText(Double.toString(lowerNumber)); + displayMessage(invalidRangeMessage, lowerComponent.getTopLevelAncestor()); + } + + return false; + } + } + + lowerNumber = lower; + upperNumber = upper; + return true; + } + + /** + * Creates a JOptionPane to display the reason why the bounds failed validation. + */ + protected void displayMessage(final String message, final Component parent) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + JOptionPane.showMessageDialog(parent, message, "Data Entry Error", JOptionPane.ERROR_MESSAGE); + } + }); + } + + /** + * Gets the number represented in the text component. If the text does not + * represent a number, then zero is returned. + */ + protected double getNumber(JTextComponent aComponent) { + try { + return Double.valueOf(aComponent.getText()).doubleValue(); //1.2 return Double.parseDouble( aComponent.getText() ); - } - catch ( NumberFormatException e ) - { - System.out.println("[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText() ); - return 0.0; - } - } + } catch (NumberFormatException e) { + System.out.println( + "[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText()); + return 0.0; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.2 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java index c360105..4ac8374 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowGrabber.java @@ -31,173 +31,140 @@ import javax.swing.JFrame; import javax.swing.JWindow; /** - * WindowGrabber is a collection of static utility methods - * for taking screen shots of lightweight containers. All - * components that are not native peers will be drawn to a - * bitmap that will be run-length compressed (LZW encoding, GIF format) - * and written to the specified file or output stream.

+ * WindowGrabber is a collection of static utility methods for taking screen + * shots of lightweight containers. All components that are not native peers + * will be drawn to a bitmap that will be run-length compressed (LZW encoding, + * GIF format) and written to the specified file or output stream.
+ *
* - * Any Swing/JFC or IFC window is a good candidate for use with these - * methods. The component is not expected to contain more than 256 colors - * (the maximum that GIF allows). While there is a color depth limitation, - * the compression is lossless, with no blurs or bleeding color. + * Any Swing/JFC or IFC window is a good candidate for use with these methods. + * The component is not expected to contain more than 256 colors (the maximum + * that GIF allows). While there is a color depth limitation, the compression is + * lossless, with no blurs or bleeding color. * * @author michael@mpowers.net - * @version $Revision: 904 $ - * $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ */ -public class WindowGrabber -{ -/** - * Captures the screen contents of the specified component, - * and write it to a file with the specified name. - * - * @param aComponent a lightweight component or container. - * @param aFileName the name of the file to write, optionally preceded by path. - * @return True if the file was successfully written, false if there was an error. - * Errors are written to the standard error stream. - */ - static public boolean grab( Component aComponent, String aFileName ) - { +public class WindowGrabber { + /** + * Captures the screen contents of the specified component, and write it to a + * file with the specified name. + * + * @param aComponent a lightweight component or container. + * @param aFileName the name of the file to write, optionally preceded by path. + * @return True if the file was successfully written, false if there was an + * error. Errors are written to the standard error stream. + */ + static public boolean grab(Component aComponent, String aFileName) { OutputStream output = null; - try - { - output = new FileOutputStream( aFileName ); - } - catch ( Exception exc ) - { - System.err.println( exc ); + try { + output = new FileOutputStream(aFileName); + } catch (Exception exc) { + System.err.println(exc); return false; } - return grab( aComponent, output ); + return grab(aComponent, output); } -/** - * Captures the screen contents of the specified component, - * and write it to a file with the specified name. - * - * @param aComponent a lightweight component or container. - * @param anOutputStream an output stream to which the image will be written. - * @return True if the image was successfully written, false if there was an error. - * Errors are written to the standard error stream. - */ - static public boolean grab( Component aComponent, OutputStream anOutputStream ) - { - Image img = aComponent.createImage( - aComponent.getSize().width, aComponent.getSize().height ); - aComponent.paintAll( img.getGraphics() ); + /** + * Captures the screen contents of the specified component, and write it to a + * file with the specified name. + * + * @param aComponent a lightweight component or container. + * @param anOutputStream an output stream to which the image will be written. + * @return True if the image was successfully written, false if there was an + * error. Errors are written to the standard error stream. + */ + static public boolean grab(Component aComponent, OutputStream anOutputStream) { + Image img = aComponent.createImage(aComponent.getSize().width, aComponent.getSize().height); + aComponent.paintAll(img.getGraphics()); try { - GIFEncoder encoder = new GIFEncoder( img ); - encoder.write( anOutputStream ); + GIFEncoder encoder = new GIFEncoder(img); + encoder.write(anOutputStream); anOutputStream.flush(); - } catch ( Exception exc ) { - System.err.println( exc ); + } catch (Exception exc) { + System.err.println(exc); return false; } return true; } - protected static void processFilenames( String path, String[] filenames ) - { + protected static void processFilenames(String path, String[] filenames) { ClassLoader loader = new ClassGrabber(); File f; - for ( int i = 0; i < filenames.length; i++ ) - { - try - { - f = new File( path + filenames[i] ); - if ( f.isDirectory() ) - { - processFilenames ( - path + filenames[i] + "/", - f.list( new FilenameFilter() - { - public boolean accept(File dir, String name) - { - return name.endsWith( ".class" ); + for (int i = 0; i < filenames.length; i++) { + try { + f = new File(path + filenames[i]); + if (f.isDirectory()) { + processFilenames(path + filenames[i] + "/", f.list(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".class"); + } + })); + } else { + System.out.println("Loading " + filenames[i] + ": "); + Class c = loader.loadClass(path + filenames[i]); + + System.out.println(c); + + if (JWindow.class.isAssignableFrom(c) || JDialog.class.isAssignableFrom(c) + || JFrame.class.isAssignableFrom(c)) { + try { + Window w = (Window) c.newInstance(); + if (w.getBounds().width * w.getBounds().height == 0) { // if size not specified or set, pack + // it + w.pack(); } - }) - ); - } - else - { - System.out.println( "Loading " + filenames[i] + ": " ); - Class c = loader.loadClass( path + filenames[i] ); - - System.out.println( c ); - - if ( JWindow.class.isAssignableFrom( c ) || - JDialog.class.isAssignableFrom( c ) || - JFrame.class.isAssignableFrom( c ) ) - { - try - { - Window w = (Window) c.newInstance(); - if ( w.getBounds().width * w.getBounds().height == 0 ) - { // if size not specified or set, pack it - w.pack(); - } - String gifName = // replace .class with .gif - filenames[i].substring( - 0, filenames[i].length() - 5 ) + "gif"; - w.addNotify(); - w.repaint(); - grab( w, gifName ); - System.out.println( "wrote: " + gifName ); - } - catch ( Exception exc ) - { - System.err.println( "WindowGrab failed for " + filenames[i] + ": " ); - exc.printStackTrace(); - } - - } - else - { - System.out.println( "not a JWindow, JDialog, or JFrame; ignored." ); - } + String gifName = // replace .class with .gif + filenames[i].substring(0, filenames[i].length() - 5) + "gif"; + w.addNotify(); + w.repaint(); + grab(w, gifName); + System.out.println("wrote: " + gifName); + } catch (Exception exc) { + System.err.println("WindowGrab failed for " + filenames[i] + ": "); + exc.printStackTrace(); + } + + } else { + System.out.println("not a JWindow, JDialog, or JFrame; ignored."); + } } - } - catch ( Exception exc ) - { - System.err.println( filenames[i] + ": " + exc ); + } catch (Exception exc) { + System.err.println(filenames[i] + ": " + exc); } } } -/** - * Captures images of any Swing window component classes specified - * as parameters. If created, image file will have the same name - * as the corresponding class file, but with a ".gif" extension. - * - * @param argv a list of filenames or directory names. - */ - public static void main( String[] argv ) - { - processFilenames( "", argv ); - System.exit( 0 ); + /** + * Captures images of any Swing window component classes specified as + * parameters. If created, image file will have the same name as the + * corresponding class file, but with a ".gif" extension. + * + * @param argv a list of filenames or directory names. + */ + public static void main(String[] argv) { + processFilenames("", argv); + System.exit(0); } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * 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:51:49 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:49 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java index a36ba12..e9d3329 100644 --- a/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java +++ b/projects/net.wotonomy.ui.swing/src/main/java/net/wotonomy/ui/swing/util/WindowUtilities.java @@ -34,488 +34,440 @@ import java.util.List; import java.util.Map; /** -* A collection of window-related utilities. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -* $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) $ -* -*/ -public class WindowUtilities -{ - -/** -* Place frame at center (vertically and horizontally) of screen. -*/ - public static final int CENTER = 0; - -/** -* Center dialog on frame area of parent, if any. -*/ - public static final int CENTER_PARENT = 1; - -/** -* Place lower and to the right of the last window -* placed in this manner. Will wrap to top and left -* of screen as necessary. -*/ - public static final int CASCADE = 10; - - // cascade state - private static int lastX = 0; - private static int lastY = 0; - private static int incrementX = 20; - private static int incrementY = 20; - -/** -* Place the window in the center of the screen. -* Note: don't forget to first set the size of your window. -* This is a convenience method and simply calls place() with -* the CENTER parameter. -* @param aWindow The window to be centered. -* @see #place -*/ - public static void center( Window aWindow ) - { - place( aWindow, CENTER ); - } - -/** -* Place lower and to the right of the last window -* placed in this manner. Will wrap to top and left -* of screen as necessary. -* This is a convenience method and simply calls place() with -* the CASCADE parameter. -* @param aWindow The window to be cascaded. -* @see #place -*/ - public static void cascade( Window aWindow ) - { - place( aWindow, CASCADE ); - } - -/** -* Place the window in the specified location. -* Note: don't forget to first set the size of your window. -* @param aWindow The window to be placed. -* @param location Where on screen to place the frame. -*/ - public static void place( Window aWindow, int location) - { - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - Dimension mySize = aWindow.getSize(); - int x = (aWindow.getLocation()).x; - int y = (aWindow.getLocation()).y; - float aspectRatio = (float)screenSize.height/(float)screenSize.width; - - // hack to make windows appear on left monitor if dual monitor - // if aspect ratio is less than 0.6, assume dual monitor - if (aspectRatio < 0.6) - { - screenSize.width = screenSize.width/2; - } - - switch (location) - { - case CENTER_PARENT: - if ( ( ! ( aWindow instanceof Dialog ) ) - || ( ((Dialog)aWindow).getParent() == null ) ) + * A collection of window-related utilities. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006) + * $ + * + */ +public class WindowUtilities { + + /** + * Place frame at center (vertically and horizontally) of screen. + */ + public static final int CENTER = 0; + + /** + * Center dialog on frame area of parent, if any. + */ + public static final int CENTER_PARENT = 1; + + /** + * Place lower and to the right of the last window placed in this manner. Will + * wrap to top and left of screen as necessary. + */ + public static final int CASCADE = 10; + + // cascade state + private static int lastX = 0; + private static int lastY = 0; + private static int incrementX = 20; + private static int incrementY = 20; + + /** + * Place the window in the center of the screen. Note: don't forget to first set + * the size of your window. This is a convenience method and simply calls + * place() with the CENTER parameter. + * + * @param aWindow The window to be centered. + * @see #place + */ + public static void center(Window aWindow) { + place(aWindow, CENTER); + } + + /** + * Place lower and to the right of the last window placed in this manner. Will + * wrap to top and left of screen as necessary. This is a convenience method and + * simply calls place() with the CASCADE parameter. + * + * @param aWindow The window to be cascaded. + * @see #place + */ + public static void cascade(Window aWindow) { + place(aWindow, CASCADE); + } + + /** + * Place the window in the specified location. Note: don't forget to first set + * the size of your window. + * + * @param aWindow The window to be placed. + * @param location Where on screen to place the frame. + */ + public static void place(Window aWindow, int location) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension mySize = aWindow.getSize(); + int x = (aWindow.getLocation()).x; + int y = (aWindow.getLocation()).y; + float aspectRatio = (float) screenSize.height / (float) screenSize.width; + + // hack to make windows appear on left monitor if dual monitor + // if aspect ratio is less than 0.6, assume dual monitor + if (aspectRatio < 0.6) { + screenSize.width = screenSize.width / 2; + } + + switch (location) { + case CENTER_PARENT: + if ((!(aWindow instanceof Dialog)) || (((Dialog) aWindow).getParent() == null)) //1.2 || ( ((Dialog)aWindow).getOwner() == null ) ) - { - place( aWindow, CENTER ); - return; - } - Point parentLocation = (((Dialog)aWindow).getParent()).getLocation(); + { + place(aWindow, CENTER); + return; + } + Point parentLocation = (((Dialog) aWindow).getParent()).getLocation(); //1.2 (((Dialog)aWindow).getOwner()).getLocation(); - Dimension parentSize = (((Dialog)aWindow).getParent()).getSize(); + Dimension parentSize = (((Dialog) aWindow).getParent()).getSize(); //1.2 Dimension parentSize = (((Dialog)aWindow).getOwner()).getSize(); - if (parentSize.width > mySize.width) - { - x = ((parentSize.width - mySize.width)/2) + parentLocation.x; - } - if (parentSize.height > mySize.height) - { - y = ((parentSize.height - mySize.height)/2) + parentLocation.y; - } - break; - case CENTER: - if (screenSize.width > mySize.width) - { - x = (screenSize.width - mySize.width)/2; - } - if (screenSize.height > mySize.height) - { - y = (screenSize.height - mySize.height)/2; - } - break; - case CASCADE: - x = lastX + incrementX; - if ( x + mySize.width > screenSize.width ) - { - x = incrementX; - } - y = lastY + incrementY; - if ( y + mySize.height > screenSize.height ) - { - y = incrementY; - } - lastX = x; - lastY = y; - break; - default: - // don't move the frame - Point p = aWindow.getLocation(); - x = p.x; - y = p.y; - break; - } - aWindow.setLocation(x,y); - } - -/** - * Returns the first parent Window of the specified component. - * - * @param c the Component whose parent will be found. - * @return the Window that contains the component, - * or null if the component does not have a valid Frame parent. - */ - public static Window getWindowForComponent(Component c) - { - for(Component p = c; p != null; p = p.getParent()) { - if (p instanceof Window) { - return (Window) p; - } - } - return null; - } - - - -/** -* Prints out a list of all components to System.out. -* @param aContainer the Container whose components will be listed. -*/ - public static void dumpComponents( Container aContainer ) - { - dumpComponents( aContainer, "" ); - } - - protected static void dumpComponents( Container aContainer, String padding ) - { - Component c = null; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - if ( c instanceof javax.swing.JComponent ) - { - System.out.println( padding + c.getClass() + ": " - + ((javax.swing.JComponent)c).getAccessibleContext().getAccessibleName() ); - } - else - { - System.out.println( padding + c.getClass() + ": " + c.getName() ); - } - if ( c instanceof Container ) - { - dumpComponents( (Container) c, padding + " " ); - } - } - } - -/** -* Gets a list of all children of a specified container, sorted by position. -* Components are sorted from top to bottom and then left to right. -* @param aContainer The container whose children are to be returned. -* @result A List containing the sorted components. -*/ - public static List getSortedChildComponents( Container aContainer ) - { - List result = new ArrayList( getAllChildComponents( aContainer ) ); - Collections.sort( result, new PositionComparator( aContainer ) ); - return result; - } - - public static void dumpSortedChildComponents( Container aContainer ) - { - Component c = null; - Iterator it = getSortedChildComponents( aContainer ).iterator(); - while ( it.hasNext() ) - { - c = (Component) it.next(); - System.out.println( c.getLocation() + " : " + c.getClass() ); - } - } - - public static void dumpNamedChildComponents( Container aContainer ) - { - Iterator it = getUniqueNameMap( getSortedChildComponents( aContainer ) ).values().iterator(); - while ( it.hasNext() ) - { - System.out.println( it.next() ); - } - } - -/** -* Generates a unique name for each object in a list and returns -* a map that maps the objects to the names. The name is based -* on the class of the object in the object list. -* @param anObjectList A List of objects. -* @return A Map that maps the objects in the list to the generated names. -*/ - public static Map getUniqueNameMap( List anObjectList ) - { - Map namesToObjects = new HashMap(anObjectList.size(), 1F); - Map objectsToNames = new HashMap(anObjectList.size(), 1F); - - Object o = null; - String name = null; - int lastIndex = 0; - Iterator it = anObjectList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - name = o.getClass().getName(); - lastIndex = name.lastIndexOf( "." ); - if ( lastIndex != -1 ) - { - name = name.substring( lastIndex+1 ); - } - name = incrementString( name ); - while ( namesToObjects.get( name ) != null ) - { - name = incrementString( name ); - } - namesToObjects.put( name, o ); - objectsToNames.put( o, name ); - } - - return objectsToNames; - } - -/** -* Numerically increments a string. For example, "hello" becomes "hello1" -* and "hello1" becomes "hello2" while "hello999" becomes "hello1000". -* @param aString a String to be incremented. -* @return The incremented String. -*/ - public static String incrementString( String aString ) - { - int i = aString.length()-1; - while ( ( i >= 0 ) && ( Character.isDigit( aString.charAt( i ) ) ) ) - { - i--; - } - - if ( i == aString.length()-1 ) - { // no numerics at end of string, increment manually - return aString + "1"; - } - - String alpha = aString.substring( 0, i+1 ); - String numeric = aString.substring( i+1 ); - numeric = Integer.toString( Integer.parseInt( numeric ) + 1 ); - - return alpha + numeric; - - } - -/** -* Gets all children of the specified container. -* @param aContainer the Container to be searched. -* @return A Collection containing all of the child components of the container. -*/ - public static Collection getAllChildComponents( Container aContainer ) - { - Collection result = new ArrayList(); - addAllChildComponents( aContainer, result ); - return result; - } - -/** -* Adds all children of the specified container to the specified collection. -* @param aContainer the Container to be searched. -* @param aCollection the Collection to which the child components will be added. -*/ - protected static void addAllChildComponents( Container aContainer, Collection aCollection ) - { - Component c = null; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - aCollection.add( c ); - if ( c instanceof Container ) - { - addAllChildComponents( (Container) c, aCollection ); - } - } - } - -/** -* Sets each child component's tooltip to show the name generated from -* getComponentNameMap(). -* (We're doing this so the tooltip authors can know how to reference -* the components.) -* @param aContainer the Container whose components will be labeled. -*/ - public static void labelComponents( Container aContainer ) - { - Map nameToComponent = getNameToComponentMap( aContainer ); - Map nameToName = new HashMap(nameToComponent.size(), 1F); - Iterator it = nameToComponent.keySet().iterator(); - String key; - while ( it.hasNext() ) - { - key = it.next().toString(); - nameToName.put( key, key ); - } - labelComponents( aContainer, nameToName ); - } - -/** -* Sets each child component's tooltip to show a given string retrieved -* from a map using the component's generated name as a key. -* @param aContainer the Container whose components will be labeled. -* @param aNameMap a Map of generated names to string values. -*/ - public static void labelComponents( Container aContainer, Map aNameMap ) - { - if ( aNameMap == null ) return; - - String key; - Object o ; - Iterator it = aNameMap.keySet().iterator(); - Map nameToComponent = getNameToComponentMap( aContainer ); - while ( it.hasNext() ) - { - key = it.next().toString(); - o = nameToComponent.get( key ); - if ( o instanceof javax.swing.JComponent ) - { - ((javax.swing.JComponent)o).setToolTipText( aNameMap.get( key ).toString() ); - } - } - } - -/** -* Generates a deterministically unique name for each component in a -* container. The name is based on the name of the class of the component -* followed by a number. Each class of component is numbered based on it's -* position in the container, sorted from top to bottom and left to right. -* @param aContainer the Container whose components will named. -* @return a Map that maps each component to its name. -*/ - public static Map getComponentToNameMap( Container aContainer ) - { - return getUniqueNameMap( getSortedChildComponents( aContainer ) ); - } - -/** -* Maps a deterministically unique name to a component in a -* container. The name is based on the name of the class of the component -* followed by a number. Each class of component is numbered based on it's -* position in the container, sorted from top to bottom and left to right. -* @param aContainer the Container whose components will named. -* @return a Map that maps each component to its name. -*/ - public static Map getNameToComponentMap( Container aContainer ) - { - Map componentToName = getComponentToNameMap( aContainer ); - Map result = new HashMap(componentToName.size(), 1F); - Iterator it = componentToName.keySet().iterator(); - Object key; - while ( it.hasNext() ) - { - key = it.next(); - result.put( componentToName.get( key ), key ); - } - return result; - } - -/** -* Sets the tooltips of all components in a container to the -* respective names of those components. (We're using this -* so the tooltip authors can know how to reference the components.) -* @param aContainer the Container whose components will be labeled. -*/ - public static void nameComponents( Container aContainer ) - { - nameComponents( aContainer, "" ); - } - - protected static void nameComponents( Container aContainer, String path ) - { - Component c = null; - String className = null; - int index = 0; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - className = c.getClass().getName(); - className = className.substring( className.lastIndexOf( '.' ) + 1 ); - System.out.println( path + className ); - if ( c instanceof javax.swing.JComponent ) - { - // ((javax.swing.JComponent)c).setToolTipText( path + className + " (" + c.getName() + ")" ); - ((javax.swing.JComponent)c).setToolTipText( c.getName() ); - } - if ( c instanceof Container ) - { - nameComponents( (Container) c, path + className + "." ); - } - } - } - -/** -* Sets the enabled state of a container and all of its components. -* @param aContainer the Container whose components will be enabled. -* @param isEnabled True if enabled, false id disabled. -*/ - public static void enableComponents( Container aContainer, boolean isEnabled ) - { - Component c = null; - String className = null; - int index = 0; - int count = aContainer.getComponentCount(); - for ( int i = 0; i < count; i++ ) - { - c = aContainer.getComponent( i ); - if ( c instanceof Container ) - { - enableComponents( (Container) c, isEnabled ); - } - else - { - c.setEnabled( isEnabled ); - } - } - aContainer.setEnabled( isEnabled ); - } + if (parentSize.width > mySize.width) { + x = ((parentSize.width - mySize.width) / 2) + parentLocation.x; + } + if (parentSize.height > mySize.height) { + y = ((parentSize.height - mySize.height) / 2) + parentLocation.y; + } + break; + case CENTER: + if (screenSize.width > mySize.width) { + x = (screenSize.width - mySize.width) / 2; + } + if (screenSize.height > mySize.height) { + y = (screenSize.height - mySize.height) / 2; + } + break; + case CASCADE: + x = lastX + incrementX; + if (x + mySize.width > screenSize.width) { + x = incrementX; + } + y = lastY + incrementY; + if (y + mySize.height > screenSize.height) { + y = incrementY; + } + lastX = x; + lastY = y; + break; + default: + // don't move the frame + Point p = aWindow.getLocation(); + x = p.x; + y = p.y; + break; + } + aWindow.setLocation(x, y); + } + + /** + * Returns the first parent Window of the specified component. + * + * @param c the Component whose parent will be found. + * @return the Window that contains the component, or null if the component does + * not have a valid Frame parent. + */ + public static Window getWindowForComponent(Component c) { + for (Component p = c; p != null; p = p.getParent()) { + if (p instanceof Window) { + return (Window) p; + } + } + return null; + } + + /** + * Prints out a list of all components to System.out. + * + * @param aContainer the Container whose components will be listed. + */ + public static void dumpComponents(Container aContainer) { + dumpComponents(aContainer, ""); + } + + protected static void dumpComponents(Container aContainer, String padding) { + Component c = null; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + if (c instanceof javax.swing.JComponent) { + System.out.println(padding + c.getClass() + ": " + + ((javax.swing.JComponent) c).getAccessibleContext().getAccessibleName()); + } else { + System.out.println(padding + c.getClass() + ": " + c.getName()); + } + if (c instanceof Container) { + dumpComponents((Container) c, padding + " "); + } + } + } + + /** + * Gets a list of all children of a specified container, sorted by position. + * Components are sorted from top to bottom and then left to right. + * + * @param aContainer The container whose children are to be returned. + * @result A List containing the sorted components. + */ + public static List getSortedChildComponents(Container aContainer) { + List result = new ArrayList(getAllChildComponents(aContainer)); + Collections.sort(result, new PositionComparator(aContainer)); + return result; + } + + public static void dumpSortedChildComponents(Container aContainer) { + Component c = null; + Iterator it = getSortedChildComponents(aContainer).iterator(); + while (it.hasNext()) { + c = (Component) it.next(); + System.out.println(c.getLocation() + " : " + c.getClass()); + } + } + + public static void dumpNamedChildComponents(Container aContainer) { + Iterator it = getUniqueNameMap(getSortedChildComponents(aContainer)).values().iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } + + /** + * Generates a unique name for each object in a list and returns a map that maps + * the objects to the names. The name is based on the class of the object in the + * object list. + * + * @param anObjectList A List of objects. + * @return A Map that maps the objects in the list to the generated names. + */ + public static Map getUniqueNameMap(List anObjectList) { + Map namesToObjects = new HashMap(anObjectList.size(), 1F); + Map objectsToNames = new HashMap(anObjectList.size(), 1F); + + Object o = null; + String name = null; + int lastIndex = 0; + Iterator it = anObjectList.iterator(); + while (it.hasNext()) { + o = it.next(); + name = o.getClass().getName(); + lastIndex = name.lastIndexOf("."); + if (lastIndex != -1) { + name = name.substring(lastIndex + 1); + } + name = incrementString(name); + while (namesToObjects.get(name) != null) { + name = incrementString(name); + } + namesToObjects.put(name, o); + objectsToNames.put(o, name); + } + + return objectsToNames; + } + + /** + * Numerically increments a string. For example, "hello" becomes "hello1" and + * "hello1" becomes "hello2" while "hello999" becomes "hello1000". + * + * @param aString a String to be incremented. + * @return The incremented String. + */ + public static String incrementString(String aString) { + int i = aString.length() - 1; + while ((i >= 0) && (Character.isDigit(aString.charAt(i)))) { + i--; + } + + if (i == aString.length() - 1) { // no numerics at end of string, increment manually + return aString + "1"; + } + + String alpha = aString.substring(0, i + 1); + String numeric = aString.substring(i + 1); + numeric = Integer.toString(Integer.parseInt(numeric) + 1); + + return alpha + numeric; + + } + + /** + * Gets all children of the specified container. + * + * @param aContainer the Container to be searched. + * @return A Collection containing all of the child components of the container. + */ + public static Collection getAllChildComponents(Container aContainer) { + Collection result = new ArrayList(); + addAllChildComponents(aContainer, result); + return result; + } + + /** + * Adds all children of the specified container to the specified collection. + * + * @param aContainer the Container to be searched. + * @param aCollection the Collection to which the child components will be + * added. + */ + protected static void addAllChildComponents(Container aContainer, Collection aCollection) { + Component c = null; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + aCollection.add(c); + if (c instanceof Container) { + addAllChildComponents((Container) c, aCollection); + } + } + } + + /** + * Sets each child component's tooltip to show the name generated from + * getComponentNameMap(). (We're doing this so the tooltip authors can know how + * to reference the components.) + * + * @param aContainer the Container whose components will be labeled. + */ + public static void labelComponents(Container aContainer) { + Map nameToComponent = getNameToComponentMap(aContainer); + Map nameToName = new HashMap(nameToComponent.size(), 1F); + Iterator it = nameToComponent.keySet().iterator(); + String key; + while (it.hasNext()) { + key = it.next().toString(); + nameToName.put(key, key); + } + labelComponents(aContainer, nameToName); + } + + /** + * Sets each child component's tooltip to show a given string retrieved from a + * map using the component's generated name as a key. + * + * @param aContainer the Container whose components will be labeled. + * @param aNameMap a Map of generated names to string values. + */ + public static void labelComponents(Container aContainer, Map aNameMap) { + if (aNameMap == null) + return; + + String key; + Object o; + Iterator it = aNameMap.keySet().iterator(); + Map nameToComponent = getNameToComponentMap(aContainer); + while (it.hasNext()) { + key = it.next().toString(); + o = nameToComponent.get(key); + if (o instanceof javax.swing.JComponent) { + ((javax.swing.JComponent) o).setToolTipText(aNameMap.get(key).toString()); + } + } + } + + /** + * Generates a deterministically unique name for each component in a container. + * The name is based on the name of the class of the component followed by a + * number. Each class of component is numbered based on it's position in the + * container, sorted from top to bottom and left to right. + * + * @param aContainer the Container whose components will named. + * @return a Map that maps each component to its name. + */ + public static Map getComponentToNameMap(Container aContainer) { + return getUniqueNameMap(getSortedChildComponents(aContainer)); + } + + /** + * Maps a deterministically unique name to a component in a container. The name + * is based on the name of the class of the component followed by a number. Each + * class of component is numbered based on it's position in the container, + * sorted from top to bottom and left to right. + * + * @param aContainer the Container whose components will named. + * @return a Map that maps each component to its name. + */ + public static Map getNameToComponentMap(Container aContainer) { + Map componentToName = getComponentToNameMap(aContainer); + Map result = new HashMap(componentToName.size(), 1F); + Iterator it = componentToName.keySet().iterator(); + Object key; + while (it.hasNext()) { + key = it.next(); + result.put(componentToName.get(key), key); + } + return result; + } + + /** + * Sets the tooltips of all components in a container to the respective names of + * those components. (We're using this so the tooltip authors can know how to + * reference the components.) + * + * @param aContainer the Container whose components will be labeled. + */ + public static void nameComponents(Container aContainer) { + nameComponents(aContainer, ""); + } + + protected static void nameComponents(Container aContainer, String path) { + Component c = null; + String className = null; + int index = 0; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + className = c.getClass().getName(); + className = className.substring(className.lastIndexOf('.') + 1); + System.out.println(path + className); + if (c instanceof javax.swing.JComponent) { + // ((javax.swing.JComponent)c).setToolTipText( path + className + " (" + + // c.getName() + ")" ); + ((javax.swing.JComponent) c).setToolTipText(c.getName()); + } + if (c instanceof Container) { + nameComponents((Container) c, path + className + "."); + } + } + } + + /** + * Sets the enabled state of a container and all of its components. + * + * @param aContainer the Container whose components will be enabled. + * @param isEnabled True if enabled, false id disabled. + */ + public static void enableComponents(Container aContainer, boolean isEnabled) { + Component c = null; + String className = null; + int index = 0; + int count = aContainer.getComponentCount(); + for (int i = 0; i < count; i++) { + c = aContainer.getComponent(i); + if (c instanceof Container) { + enableComponents((Container) c, isEnabled); + } else { + c.setEnabled(isEnabled); + } + } + aContainer.setEnabled(isEnabled); + } } /* - * $Log$ - * Revision 1.2 2006/02/18 23:19:05 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.2 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.1.1.1 2000/12/21 15:51:55 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:51:55 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:46 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:46 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java index 8e9fae2..3512c2a 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DebuggingDelegate.java @@ -24,310 +24,200 @@ import net.wotonomy.control.EODataSource; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSNotification; - /** - * A display group delegate that prints messages for each - * of the delegate methods. - * - * @author michael@mpowers.net - * @author $Author: cgruber $ - * @version $Revision: 904 $ - */ - public class DebuggingDelegate implements EODisplayGroup.Delegate - { - /** - * Called when the specified data source fails - * to create an object for the specified display group. - */ - public void displayGroupCreateObjectFailed ( - EODisplayGroup aDisplayGroup, - EODataSource aDataSource ) - { - report( "displayGroupCreateObjectFailed", - aDisplayGroup, - new Object[] { aDataSource } ); - } - - /** - * Called after the specified display group's - * data source is changed. - */ - public void displayGroupDidChangeDataSource ( - EODisplayGroup aDisplayGroup ) - { - report( "displayGroupDidChangeDataSource", - aDisplayGroup, - new Object[] { } ); - } - - /** - * Called after the specified display group's - * selection has changed. - */ - public void displayGroupDidChangeSelectedObjects ( - EODisplayGroup aDisplayGroup ) - { - report( "displayGroupDidChangeSelectedObjects", - aDisplayGroup, - new Object[] { } ); - } - - /** - * Called after the specified display group's - * selection has changed. - */ - public void displayGroupDidChangeSelection ( - EODisplayGroup aDisplayGroup ) - { - report( "displayGroupDidChangeSelection", - aDisplayGroup, - new Object[] { } ); - } - - /** - * Called after the specified object display group's - * selection has changed. - */ - public void displayGroupDidDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - report( "displayGroupDidDeleteObject", - aDisplayGroup, - new Object[] { anObject } ); - } - - /** - * Called after the specified display group - * has fetched the specified object list. - */ - public void displayGroupDidFetchObjects ( - EODisplayGroup aDisplayGroup, - List anObjectList ) - { - report( "displayGroupDidFetchObjects", - aDisplayGroup, - new Object[] { anObjectList } ); - } - - /** - * Called after the specified display group - * has inserted the specified object into - * its internal object list. - */ - public void displayGroupDidInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - report( "displayGroupDidInsertObject", - aDisplayGroup, - new Object[] { anObject } ); - } - - /** - * Called after the specified display group - * has set the specified value for the specified - * object and key. - */ - public void displayGroupDidSetValueForObject ( - EODisplayGroup aDisplayGroup, - Object aValue, - Object anObject, - String aKey ) - { - report( "displayGroupDidSetValueForObject", - aDisplayGroup, - new Object[] { aValue, anObject, aKey } ); - } - - /** - * Called by the specified display group to - * determine what objects should be displayed - * for the objects in the specified list. - * @return An NSArray containing the objects - * to be displayed for the objects in the - * specified list. - */ - public NSArray displayGroupDisplayArrayForObjects ( - EODisplayGroup aDisplayGroup, - List aList ) - { - return get( "displayGroupDisplayArrayForObjects", - aDisplayGroup, - aList ); - } - - /** - * Called by the specified display group before - * it attempts to change the selection. - * This implementation returns true. - * @return True to allow the selection to change, - * false otherwise. - */ - public boolean displayGroupShouldChangeSelection ( - EODisplayGroup aDisplayGroup, - List aSelectionList ) - { - return ask( "displayGroupShouldChangeSelection", - aDisplayGroup, - new Object[] { aSelectionList } ); - } - - /** - * Called by the specified display group before - * it attempts to delete the specified object. - * This implementation returns true. - * @return True to allow the object to be deleted - * false to prevent the deletion. - */ - public boolean displayGroupShouldDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - return ask( "displayGroupShouldDeleteObject", - aDisplayGroup, - new Object[] { anObject } ); - } - - /** - * Called by the specified display group before - * it attempts display the specified alert to - * the user. - * This implementation returns true. - * @return True to allow the message to be - * displayed, false if you want to handle the - * alert yourself and suppress the display group's - * notification. - */ - public boolean displayGroupShouldDisplayAlert ( - EODisplayGroup aDisplayGroup, - String aTitle, - String aMessage ) - { - return ask( "displayGroupShouldDisplayAlert", - aDisplayGroup, - new Object[] { aTitle, aMessage } ); - } - - /** - * Called by the specified display group before - * it attempts fetch objects. - * This implementation returns true. - * @return True to allow the fetch to take place, - * false to prevent the fetch. - */ - public boolean displayGroupShouldFetch ( - EODisplayGroup aDisplayGroup ) - { - return ask( "displayGroupShouldFetch", - aDisplayGroup, - new Object[] { } ); - } - - /** - * Called by the specified display group before - * it attempts to insert the specified object. - * This implementation returns true. - * @return True to allow the object to be inserted - * false to prevent the insertion. - */ - public boolean displayGroupShouldInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject, - int anIndex ) - { - return ask( "displayGroupShouldInsertObject", - aDisplayGroup, - new Object[] { anObject, new Integer( anIndex ) } ); - } - - /** - * No idea what this might indicate, - * nor what the notification indicates. - * This implementation returns true. - */ - public boolean displayGroupShouldRedisplay ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ) - { - return ask( "displayGroupShouldRedisplay", - aDisplayGroup, - new Object[] { aNotification } ); - } - - /** - * No idea what this might indicate, - * nor what the notification indicates. - */ - public boolean displayGroupShouldRefetch ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ) - { - return ask( "displayGroupShouldRefetch", - aDisplayGroup, - new Object[] { aNotification } ); - } - - /** - * This method is called by all delegate methods that - * return void. - * This implementation calls System.out.println. - * Override to customize or replace the output. - */ - protected void report( String aTitle, - EODisplayGroup aDisplayGroup, - Object[] aParameterArray ) - { - String result = aTitle + " : " + aDisplayGroup; - for ( int i = 0; i < aParameterArray.length; i++ ) - { - result += " : " + aParameterArray[i]; - } - System.out.println( result ); - } - - /** - * This method is called by displayGroupDisplayArrayForObjects. - * This implementation calls report - * and returns a copy of the specified list. - */ - protected NSArray get( String aTitle, - EODisplayGroup aDisplayGroup, - List anObjectList ) - { - report( aTitle, aDisplayGroup, new Object[] { anObjectList } ); - return new NSArray( anObjectList ); - } - - /** - * This method is called by the methods that - * return a boolean. - * This implementation calls report and return true. - */ - protected boolean ask( String aTitle, - EODisplayGroup aDisplayGroup, - Object[] aParameterArray ) - { - report( aTitle, aDisplayGroup, aParameterArray ); - return true; - } - - - } +/** + * A display group delegate that prints messages for each of the delegate + * methods. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DebuggingDelegate implements EODisplayGroup.Delegate { + /** + * Called when the specified data source fails to create an object for the + * specified display group. + */ + public void displayGroupCreateObjectFailed(EODisplayGroup aDisplayGroup, EODataSource aDataSource) { + report("displayGroupCreateObjectFailed", aDisplayGroup, new Object[] { aDataSource }); + } + + /** + * Called after the specified display group's data source is changed. + */ + public void displayGroupDidChangeDataSource(EODisplayGroup aDisplayGroup) { + report("displayGroupDidChangeDataSource", aDisplayGroup, new Object[] {}); + } + + /** + * Called after the specified display group's selection has changed. + */ + public void displayGroupDidChangeSelectedObjects(EODisplayGroup aDisplayGroup) { + report("displayGroupDidChangeSelectedObjects", aDisplayGroup, new Object[] {}); + } + + /** + * Called after the specified display group's selection has changed. + */ + public void displayGroupDidChangeSelection(EODisplayGroup aDisplayGroup) { + report("displayGroupDidChangeSelection", aDisplayGroup, new Object[] {}); + } + + /** + * Called after the specified object display group's selection has changed. + */ + public void displayGroupDidDeleteObject(EODisplayGroup aDisplayGroup, Object anObject) { + report("displayGroupDidDeleteObject", aDisplayGroup, new Object[] { anObject }); + } + + /** + * Called after the specified display group has fetched the specified object + * list. + */ + public void displayGroupDidFetchObjects(EODisplayGroup aDisplayGroup, List anObjectList) { + report("displayGroupDidFetchObjects", aDisplayGroup, new Object[] { anObjectList }); + } + + /** + * Called after the specified display group has inserted the specified object + * into its internal object list. + */ + public void displayGroupDidInsertObject(EODisplayGroup aDisplayGroup, Object anObject) { + report("displayGroupDidInsertObject", aDisplayGroup, new Object[] { anObject }); + } + + /** + * Called after the specified display group has set the specified value for the + * specified object and key. + */ + public void displayGroupDidSetValueForObject(EODisplayGroup aDisplayGroup, Object aValue, Object anObject, + String aKey) { + report("displayGroupDidSetValueForObject", aDisplayGroup, new Object[] { aValue, anObject, aKey }); + } + + /** + * Called by the specified display group to determine what objects should be + * displayed for the objects in the specified list. + * + * @return An NSArray containing the objects to be displayed for the objects in + * the specified list. + */ + public NSArray displayGroupDisplayArrayForObjects(EODisplayGroup aDisplayGroup, List aList) { + return get("displayGroupDisplayArrayForObjects", aDisplayGroup, aList); + } + + /** + * Called by the specified display group before it attempts to change the + * selection. This implementation returns true. + * + * @return True to allow the selection to change, false otherwise. + */ + public boolean displayGroupShouldChangeSelection(EODisplayGroup aDisplayGroup, List aSelectionList) { + return ask("displayGroupShouldChangeSelection", aDisplayGroup, new Object[] { aSelectionList }); + } + + /** + * Called by the specified display group before it attempts to delete the + * specified object. This implementation returns true. + * + * @return True to allow the object to be deleted false to prevent the deletion. + */ + public boolean displayGroupShouldDeleteObject(EODisplayGroup aDisplayGroup, Object anObject) { + return ask("displayGroupShouldDeleteObject", aDisplayGroup, new Object[] { anObject }); + } + + /** + * Called by the specified display group before it attempts display the + * specified alert to the user. This implementation returns true. + * + * @return True to allow the message to be displayed, false if you want to + * handle the alert yourself and suppress the display group's + * notification. + */ + public boolean displayGroupShouldDisplayAlert(EODisplayGroup aDisplayGroup, String aTitle, String aMessage) { + return ask("displayGroupShouldDisplayAlert", aDisplayGroup, new Object[] { aTitle, aMessage }); + } + + /** + * Called by the specified display group before it attempts fetch objects. This + * implementation returns true. + * + * @return True to allow the fetch to take place, false to prevent the fetch. + */ + public boolean displayGroupShouldFetch(EODisplayGroup aDisplayGroup) { + return ask("displayGroupShouldFetch", aDisplayGroup, new Object[] {}); + } + + /** + * Called by the specified display group before it attempts to insert the + * specified object. This implementation returns true. + * + * @return True to allow the object to be inserted false to prevent the + * insertion. + */ + public boolean displayGroupShouldInsertObject(EODisplayGroup aDisplayGroup, Object anObject, int anIndex) { + return ask("displayGroupShouldInsertObject", aDisplayGroup, new Object[] { anObject, new Integer(anIndex) }); + } + + /** + * No idea what this might indicate, nor what the notification indicates. This + * implementation returns true. + */ + public boolean displayGroupShouldRedisplay(EODisplayGroup aDisplayGroup, NSNotification aNotification) { + return ask("displayGroupShouldRedisplay", aDisplayGroup, new Object[] { aNotification }); + } + + /** + * No idea what this might indicate, nor what the notification indicates. + */ + public boolean displayGroupShouldRefetch(EODisplayGroup aDisplayGroup, NSNotification aNotification) { + return ask("displayGroupShouldRefetch", aDisplayGroup, new Object[] { aNotification }); + } + + /** + * This method is called by all delegate methods that return void. This + * implementation calls System.out.println. Override to customize or replace the + * output. + */ + protected void report(String aTitle, EODisplayGroup aDisplayGroup, Object[] aParameterArray) { + String result = aTitle + " : " + aDisplayGroup; + for (int i = 0; i < aParameterArray.length; i++) { + result += " : " + aParameterArray[i]; + } + System.out.println(result); + } + + /** + * This method is called by displayGroupDisplayArrayForObjects. This + * implementation calls report and returns a copy of the specified list. + */ + protected NSArray get(String aTitle, EODisplayGroup aDisplayGroup, List anObjectList) { + report(aTitle, aDisplayGroup, new Object[] { anObjectList }); + return new NSArray(anObjectList); + } + + /** + * This method is called by the methods that return a boolean. This + * implementation calls report and return true. + */ + protected boolean ask(String aTitle, EODisplayGroup aDisplayGroup, Object[] aParameterArray) { + report(aTitle, aDisplayGroup, aParameterArray); + return true; + } + +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2001/01/24 14:37:24 mpowers - * Contributing a delegate useful for debugging. + * Revision 1.1 2001/01/24 14:37:24 mpowers Contributing a delegate useful for + * debugging. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java index ed572f4..e801406 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DelegateAdapter.java @@ -24,234 +24,168 @@ import net.wotonomy.control.EODataSource; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSNotification; - /** - * A convenience class for creating display group delegates. - * All methods of the delegate interface are implemented - * to do nothing. - * - * @author michael@mpowers.net - * @author $Author: cgruber $ - * @version $Revision: 904 $ - */ - public class DelegateAdapter implements EODisplayGroup.Delegate - { - /** - * Called when the specified data source fails - * to create an object for the specified display group. - */ - public void displayGroupCreateObjectFailed ( - EODisplayGroup aDisplayGroup, - EODataSource aDataSource ) - { - - } - - /** - * Called after the specified display group's - * data source is changed. - */ - public void displayGroupDidChangeDataSource ( - EODisplayGroup aDisplayGroup ) - { - - } - - /** - * Called after the specified display group's - * selection has changed. - */ - public void displayGroupDidChangeSelectedObjects ( - EODisplayGroup aDisplayGroup ) - { - - } - - /** - * Called after the specified display group's - * selection has changed. - */ - public void displayGroupDidChangeSelection ( - EODisplayGroup aDisplayGroup ) - { - - } - - /** - * Called after the specified object display group's - * selection has changed. - */ - public void displayGroupDidDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - - } - - /** - * Called after the specified display group - * has fetched the specified object list. - */ - public void displayGroupDidFetchObjects ( - EODisplayGroup aDisplayGroup, - List anObjectList ) - { - - } - - /** - * Called after the specified display group - * has inserted the specified object into - * its internal object list. - */ - public void displayGroupDidInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - - } - - /** - * Called after the specified display group - * has set the specified value for the specified - * object and key. - */ - public void displayGroupDidSetValueForObject ( - EODisplayGroup aDisplayGroup, - Object aValue, - Object anObject, - String aKey ) - { - - } - - /** - * Called by the specified display group to - * determine what objects should be displayed - * for the objects in the specified list. - * @return An NSArray containing the objects - * to be displayed for the objects in the - * specified list. - */ - public NSArray displayGroupDisplayArrayForObjects ( - EODisplayGroup aDisplayGroup, - List aList ) - { - return new NSArray( aList ); - } - - /** - * Called by the specified display group before - * it attempts to change the selection. - * This implementation returns true. - * @return True to allow the selection to change, - * false otherwise. - */ - public boolean displayGroupShouldChangeSelection ( - EODisplayGroup aDisplayGroup, - List aSelectionList ) - { - return true; - } - - /** - * Called by the specified display group before - * it attempts to delete the specified object. - * This implementation returns true. - * @return True to allow the object to be deleted - * false to prevent the deletion. - */ - public boolean displayGroupShouldDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ) - { - return true; - } - - /** - * Called by the specified display group before - * it attempts display the specified alert to - * the user. - * This implementation returns true. - * @return True to allow the message to be - * displayed, false if you want to handle the - * alert yourself and suppress the display group's - * notification. - */ - public boolean displayGroupShouldDisplayAlert ( - EODisplayGroup aDisplayGroup, - String aTitle, - String aMessage ) - { - return true; - } - - /** - * Called by the specified display group before - * it attempts fetch objects. - * This implementation returns true. - * @return True to allow the fetch to take place, - * false to prevent the fetch. - */ - public boolean displayGroupShouldFetch ( - EODisplayGroup aDisplayGroup ) - { - return true; - } - - /** - * Called by the specified display group before - * it attempts to insert the specified object. - * This implementation returns true. - * @return True to allow the object to be inserted - * false to prevent the insertion. - */ - public boolean displayGroupShouldInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject, - int anIndex ) - { - return true; - } - - /** - * No idea what this might indicate, - * nor what the notification indicates. - * This implementation returns true. - */ - public boolean displayGroupShouldRedisplay ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ) - { - return true; - } - - /** - * No idea what this might indicate, - * nor what the notification indicates. - */ - public boolean displayGroupShouldRefetch ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ) - { - return true; - } - - } +/** + * A convenience class for creating display group delegates. All methods of the + * delegate interface are implemented to do nothing. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DelegateAdapter implements EODisplayGroup.Delegate { + /** + * Called when the specified data source fails to create an object for the + * specified display group. + */ + public void displayGroupCreateObjectFailed(EODisplayGroup aDisplayGroup, EODataSource aDataSource) { + + } + + /** + * Called after the specified display group's data source is changed. + */ + public void displayGroupDidChangeDataSource(EODisplayGroup aDisplayGroup) { + + } + + /** + * Called after the specified display group's selection has changed. + */ + public void displayGroupDidChangeSelectedObjects(EODisplayGroup aDisplayGroup) { + + } + + /** + * Called after the specified display group's selection has changed. + */ + public void displayGroupDidChangeSelection(EODisplayGroup aDisplayGroup) { + + } + + /** + * Called after the specified object display group's selection has changed. + */ + public void displayGroupDidDeleteObject(EODisplayGroup aDisplayGroup, Object anObject) { + + } + + /** + * Called after the specified display group has fetched the specified object + * list. + */ + public void displayGroupDidFetchObjects(EODisplayGroup aDisplayGroup, List anObjectList) { + + } + + /** + * Called after the specified display group has inserted the specified object + * into its internal object list. + */ + public void displayGroupDidInsertObject(EODisplayGroup aDisplayGroup, Object anObject) { + + } + + /** + * Called after the specified display group has set the specified value for the + * specified object and key. + */ + public void displayGroupDidSetValueForObject(EODisplayGroup aDisplayGroup, Object aValue, Object anObject, + String aKey) { + + } + + /** + * Called by the specified display group to determine what objects should be + * displayed for the objects in the specified list. + * + * @return An NSArray containing the objects to be displayed for the objects in + * the specified list. + */ + public NSArray displayGroupDisplayArrayForObjects(EODisplayGroup aDisplayGroup, List aList) { + return new NSArray(aList); + } + + /** + * Called by the specified display group before it attempts to change the + * selection. This implementation returns true. + * + * @return True to allow the selection to change, false otherwise. + */ + public boolean displayGroupShouldChangeSelection(EODisplayGroup aDisplayGroup, List aSelectionList) { + return true; + } + + /** + * Called by the specified display group before it attempts to delete the + * specified object. This implementation returns true. + * + * @return True to allow the object to be deleted false to prevent the deletion. + */ + public boolean displayGroupShouldDeleteObject(EODisplayGroup aDisplayGroup, Object anObject) { + return true; + } + + /** + * Called by the specified display group before it attempts display the + * specified alert to the user. This implementation returns true. + * + * @return True to allow the message to be displayed, false if you want to + * handle the alert yourself and suppress the display group's + * notification. + */ + public boolean displayGroupShouldDisplayAlert(EODisplayGroup aDisplayGroup, String aTitle, String aMessage) { + return true; + } + + /** + * Called by the specified display group before it attempts fetch objects. This + * implementation returns true. + * + * @return True to allow the fetch to take place, false to prevent the fetch. + */ + public boolean displayGroupShouldFetch(EODisplayGroup aDisplayGroup) { + return true; + } + + /** + * Called by the specified display group before it attempts to insert the + * specified object. This implementation returns true. + * + * @return True to allow the object to be inserted false to prevent the + * insertion. + */ + public boolean displayGroupShouldInsertObject(EODisplayGroup aDisplayGroup, Object anObject, int anIndex) { + return true; + } + + /** + * No idea what this might indicate, nor what the notification indicates. This + * implementation returns true. + */ + public boolean displayGroupShouldRedisplay(EODisplayGroup aDisplayGroup, NSNotification aNotification) { + return true; + } + + /** + * No idea what this might indicate, nor what the notification indicates. + */ + public boolean displayGroupShouldRefetch(EODisplayGroup aDisplayGroup, NSNotification aNotification) { + return true; + } + +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.2 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.1 2001/01/24 14:23:17 mpowers - * Contributing DelegateAdapter. + * Revision 1.1 2001/01/24 14:23:17 mpowers Contributing DelegateAdapter. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java index 9fcbe34..d7dd8d8 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/DisplayGroup.java @@ -25,276 +25,236 @@ import net.wotonomy.control.EODataSource; import net.wotonomy.control.EOQualifier; /** -* DisplayGroup provides an abstraction of a user interface, -* comprising of an ordered collection of data objects, some -* of which are displayed, and of those some are selected. -* This class extends EODisplayGroup to provide java-friendly -* convenience methods. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class DisplayGroup extends EODisplayGroup -{ - /** - * Returns the current data source backing this display group, - * or null if no dataSource is currently used. - */ - public EODataSource getDataSource() - { - return super.dataSource(); - } - - /** - * Returns the current delegate for this display group, - * or null if no delegate is currently set. - */ - public Object getDelegate() - { - return delegate(); - } - - /** - * Returns the current string matching format. - * If not set, defaults to "%@*". - */ - public String getDefaultStringMatchFormat() - { - return defaultStringMatchFormat(); - } - - /** - * Returns the current string matching operator. - * If not set, defaults to "caseInsensitiveLike". - */ - public String getdefaultStringMatchOperator() - { - return defaultStringMatchOperator(); - } - - /** - * Returns a Map of default values that are applied - * to new objects that are inserted into the list. - */ - public Map getInsertedObjectDefaultValues() - { - return insertedObjectDefaultValues(); - } - - /** - * Returns the keys that were declared when read from - * an external resource file. - */ - public List getLocalKeys() - { - return localKeys(); - } - - /** - * Returns the List of sort orderings for this display group. - */ - public List getSortOrderings () - { - return sortOrderings(); - } - - /** - * Returns a qualifier that will be applied all the objects - * in this display group to determine which objects will - * be displayed. - */ - public EOQualifier getQualifier () - { - return qualifier(); - } - - /** - * Returns a new qualifier built from the three query - * value maps: greater than, equal to, and less than. - */ - public EOQualifier getQualifierFromQueryValues () - { - return qualifierFromQueryValues(); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values. - */ - public Map getQueryBindingValues() - { - return queryBindingValues(); - } - - /** - * Returns a Map containing the mappings of keys - * to operator values. - */ - public Map getQueryOperatorValues() - { - return queryOperatorValues(); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for equality. - */ - public Map getEqualToQueryValues() - { - return equalToQueryValues(); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for greater value. - */ - public Map getGreaterThanQueryValues() - { - return greaterThanQueryValues(); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for lesser value. - */ - public Map getLessThanQueryValues() - { - return lessThanQueryValues(); - } - - /** - * Returns the association that is currently being edited, - * or null if no editing is taking place. - */ - public EOAssociation getEditingAssociation () - { - return editingAssociation(); - } - - /** - * Returns a List of associations that are observing - * this display group. - */ - public List getObservingAssociations() - { - return observingAssociations(); - } - - /** - * Returns a List containing all objects managed by the display group. - * This includes those objects not visible due to disqualification. - */ - public List getAllObjects() - { - return allObjects(); - } - - /** - * Returns a List of all objects in the display group - * that are currently displayed by the associations. - * The list is a copy of the internal displayed object list. - */ - public List getDisplayedObjects() - { - return displayedObjects(); - } - - /** - * Returns the currently selected object, or null if - * there is no selection. - */ - public Object getSelectedObject() - { - return selectedObject(); - } - - /** - * Returns a List containing all selected objects, if any. - * Returns an empty list if no objects are selected. - */ - public List getSelectedObjects() - { - return selectedObjects(); - } - - /** - * Returns a List containing the indexes of all selected - * objects, if any. The list contains instances of - * java.lang.Number; call intValue() retrieve the index. - */ - public List getSelectionIndexes() - { - return selectionIndexes(); - } - - /** - * Returns the index of the changed object. If more than - * one object has changed, -1 is returned. - */ - public int getUpdatedObjectIndex() - { - return updatedObjectIndex(); - } - - /** - * Returns a value on the selected object for the specified key. - */ - public Object getSelectedObjectValueForKey ( String aKey ) - { - return selectedObjectValueForKey( aKey ); - } - - /** - * Returns the value for the specified key on the specified object. - */ - public Object getValueForObject ( Object anObject, String aKey ) - { - return valueForObject( anObject, aKey ); - } - - /** - * Calls valueForObject() for the object at the specified index. - */ - public Object getValueForObjectAtIndex ( int anIndex, String aKey ) - { - return valueForObjectAtIndex( anIndex, aKey ); - } - - /** - * Specifies the default string matching format for all - * display groups. - */ - public static String getGlobalDefaultStringMatchFormat () - { - return globalDefaultStringMatchFormat(); - } - - /** - * Specifies the default string matching operator for all - * display groups. - */ - public static String getGlobalDefaultStringMatchOperator () - { - return globalDefaultStringMatchOperator(); - } -} + * DisplayGroup provides an abstraction of a user interface, comprising of an + * ordered collection of data objects, some of which are displayed, and of those + * some are selected. This class extends EODisplayGroup to provide java-friendly + * convenience methods. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class DisplayGroup extends EODisplayGroup { + /** + * Returns the current data source backing this display group, or null if no + * dataSource is currently used. + */ + public EODataSource getDataSource() { + return super.dataSource(); + } + + /** + * Returns the current delegate for this display group, or null if no delegate + * is currently set. + */ + public Object getDelegate() { + return delegate(); + } + + /** + * Returns the current string matching format. If not set, defaults to "%@*". + */ + public String getDefaultStringMatchFormat() { + return defaultStringMatchFormat(); + } + + /** + * Returns the current string matching operator. If not set, defaults to + * "caseInsensitiveLike". + */ + public String getdefaultStringMatchOperator() { + return defaultStringMatchOperator(); + } + + /** + * Returns a Map of default values that are applied to new objects that are + * inserted into the list. + */ + public Map getInsertedObjectDefaultValues() { + return insertedObjectDefaultValues(); + } + + /** + * Returns the keys that were declared when read from an external resource file. + */ + public List getLocalKeys() { + return localKeys(); + } + + /** + * Returns the List of sort orderings for this display group. + */ + public List getSortOrderings() { + return sortOrderings(); + } + + /** + * Returns a qualifier that will be applied all the objects in this display + * group to determine which objects will be displayed. + */ + public EOQualifier getQualifier() { + return qualifier(); + } + + /** + * Returns a new qualifier built from the three query value maps: greater than, + * equal to, and less than. + */ + public EOQualifier getQualifierFromQueryValues() { + return qualifierFromQueryValues(); + } + + /** + * Returns a Map containing the mappings of keys to binding query values. + */ + public Map getQueryBindingValues() { + return queryBindingValues(); + } + + /** + * Returns a Map containing the mappings of keys to operator values. + */ + public Map getQueryOperatorValues() { + return queryOperatorValues(); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for equality. + */ + public Map getEqualToQueryValues() { + return equalToQueryValues(); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for greater value. + */ + public Map getGreaterThanQueryValues() { + return greaterThanQueryValues(); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for lesser value. + */ + public Map getLessThanQueryValues() { + return lessThanQueryValues(); + } + + /** + * Returns the association that is currently being edited, or null if no editing + * is taking place. + */ + public EOAssociation getEditingAssociation() { + return editingAssociation(); + } + + /** + * Returns a List of associations that are observing this display group. + */ + public List getObservingAssociations() { + return observingAssociations(); + } + + /** + * Returns a List containing all objects managed by the display group. This + * includes those objects not visible due to disqualification. + */ + public List getAllObjects() { + return allObjects(); + } + + /** + * Returns a List of all objects in the display group that are currently + * displayed by the associations. The list is a copy of the internal displayed + * object list. + */ + public List getDisplayedObjects() { + return displayedObjects(); + } + + /** + * Returns the currently selected object, or null if there is no selection. + */ + public Object getSelectedObject() { + return selectedObject(); + } + + /** + * Returns a List containing all selected objects, if any. Returns an empty list + * if no objects are selected. + */ + public List getSelectedObjects() { + return selectedObjects(); + } + + /** + * Returns a List containing the indexes of all selected objects, if any. The + * list contains instances of java.lang.Number; call intValue() retrieve the + * index. + */ + public List getSelectionIndexes() { + return selectionIndexes(); + } + + /** + * Returns the index of the changed object. If more than one object has changed, + * -1 is returned. + */ + public int getUpdatedObjectIndex() { + return updatedObjectIndex(); + } + + /** + * Returns a value on the selected object for the specified key. + */ + public Object getSelectedObjectValueForKey(String aKey) { + return selectedObjectValueForKey(aKey); + } + + /** + * Returns the value for the specified key on the specified object. + */ + public Object getValueForObject(Object anObject, String aKey) { + return valueForObject(anObject, aKey); + } + + /** + * Calls valueForObject() for the object at the specified index. + */ + public Object getValueForObjectAtIndex(int anIndex, String aKey) { + return valueForObjectAtIndex(anIndex, aKey); + } + + /** + * Specifies the default string matching format for all display groups. + */ + public static String getGlobalDefaultStringMatchFormat() { + return globalDefaultStringMatchFormat(); + } + + /** + * Specifies the default string matching operator for all display groups. + */ + public static String getGlobalDefaultStringMatchOperator() { + return globalDefaultStringMatchOperator(); + } +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2002/05/17 15:01:49 mpowers - * Implemented dynamic lookup of delegate methods so delegates no longer - * need to implement the DisplayGroup.Delegate interface. + * Revision 1.2 2002/05/17 15:01:49 mpowers Implemented dynamic lookup of + * delegate methods so delegates no longer need to implement the + * DisplayGroup.Delegate interface. * - * Revision 1.1 2002/03/26 21:24:43 mpowers - * Contributing DisplayGroup as convenience for people coming from java. + * Revision 1.1 2002/03/26 21:24:43 mpowers Contributing DisplayGroup as + * convenience for people coming from java. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java index 635c5b9..8fb0527 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EOAssociation.java @@ -27,36 +27,34 @@ import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; /** -* Associations observe DisplayGroups and associate -* a user interface component with one or more keys -* on the objects in the display group.

-* -* Associations are created with a ui component in -* the constructor. Then, one or more aspects are -* bound to display groups and/or property keys with -* the bindAspect() method. Finally, the association -* is initialized with the establishConnection() -* method.

-* -* Per the openstep convention, you do not need to -* retain a reference to the association after it is -* created; the association will be garbage-collected -* when the ui component is garbage-collected.

-* -* (Because java components don't have delegates like -* openstep components do, java-based associations -* will likely need to implement some sort of listener -* and add itself to the component's list of listeners -* so that the component will have a strong reference -* (and the only reference) to the association.)

-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class EOAssociation extends EODelayedObserver -{ - // aspect constants + * Associations observe DisplayGroups and associate a user interface component + * with one or more keys on the objects in the display group.
+ *
+ * + * Associations are created with a ui component in the constructor. Then, one or + * more aspects are bound to display groups and/or property keys with the + * bindAspect() method. Finally, the association is initialized with the + * establishConnection() method.
+ *
+ * + * Per the openstep convention, you do not need to retain a reference to the + * association after it is created; the association will be garbage-collected + * when the ui component is garbage-collected.
+ *
+ * + * (Because java components don't have delegates like openstep components do, + * java-based associations will likely need to implement some sort of listener + * and add itself to the component's list of listeners so that the component + * will have a strong reference (and the only reference) to the association.) + *
+ *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class EOAssociation extends EODelayedObserver { + // aspect constants public static final String ActionAspect = "action"; public static final String EnabledAspect = "enabled"; public static final String SourceAspect = "source"; @@ -77,489 +75,405 @@ public class EOAssociation extends EODelayedObserver public static final String ObjectsAspect = "objects"; // not in spec public static final String LabelAspect = "label"; // not in spec public static final String IconAspect = "icon"; // not in spec - + public static final String AttributeAspectSignature = "A"; public static final String NullAspectSignature = ""; - public static final String AttributeToOneAspectSignature = "A1"; + public static final String AttributeToOneAspectSignature = "A1"; public static final String ToOneAspectSignature = "1"; - public static final String AttributeToOneToManyAspectSignature = "A1M"; + public static final String AttributeToOneToManyAspectSignature = "A1M"; public static final String ToOneToManyAspectSignature = "1M"; public static final String AttributeToManyAspectSignature = "AM"; public static final String ToManyAspectSignature = "M"; - - protected Object control; - protected NSMutableDictionary aspectToGroup; - protected NSMutableDictionary aspectToKey; - - /** - * Default constructor. - */ - public EOAssociation () - { - aspectToGroup = new NSMutableDictionary(); - aspectToKey = new NSMutableDictionary(); - control = null; - } - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public EOAssociation ( Object anObject ) - { - this(); - control = anObject; - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.

    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - int size = aspects().count(); - NSMutableArray result = new NSMutableArray(); - for ( int i = 0; i < size; i++ ) - { - result.addObject( AttributeToOneToManyAspectSignature ); - } - return result; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - * - * TODO: Is this static in the WebObjects published API? If not, fix. - */ - public static NSArray aspects () - { - return new NSArray(); - } - - /** - * Returns all registered subclasses of EOAssociation - * for which usableWithObject with the specified object - * returns true. You should only call this method on - * the EOAssociation class. - */ - public static NSArray associationClassesForObject ( - Object anObject ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - * This implementation returns an empty list. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - - /** - * Binds the specified aspect of this association to the - * specified key on the specified display group. - */ - public void bindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey ) - { + + protected Object control; + protected NSMutableDictionary aspectToGroup; + protected NSMutableDictionary aspectToKey; + + /** + * Default constructor. + */ + public EOAssociation() { + aspectToGroup = new NSMutableDictionary(); + aspectToKey = new NSMutableDictionary(); + control = null; + } + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public EOAssociation(Object anObject) { + this(); + control = anObject; + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + int size = aspects().count(); + NSMutableArray result = new NSMutableArray(); + for (int i = 0; i < size; i++) { + result.addObject(AttributeToOneToManyAspectSignature); + } + return result; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + * + * TODO: Is this static in the WebObjects published API? If not, fix. + */ + public static NSArray aspects() { + return new NSArray(); + } + + /** + * Returns all registered subclasses of EOAssociation for which usableWithObject + * with the specified object returns true. You should only call this method on + * the EOAssociation class. + */ + public static NSArray associationClassesForObject(Object anObject) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. This + * implementation returns an empty list. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Binds the specified aspect of this association to the specified key on the + * specified display group. + */ + public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { // unattach old group, if any - EODisplayGroup oldGroup = displayGroupForAspect( anAspect ); - if ( oldGroup != null ) - { - EOObserverCenter.removeObserver( this, oldGroup ); + EODisplayGroup oldGroup = displayGroupForAspect(anAspect); + if (oldGroup != null) { + EOObserverCenter.removeObserver(this, oldGroup); } // attach new group - if ( aDisplayGroup != null ) - { - aspectToGroup.setObjectForKey( aDisplayGroup, anAspect ); - } - else - { - aspectToGroup.removeObjectForKey( anAspect ); + if (aDisplayGroup != null) { + aspectToGroup.setObjectForKey(aDisplayGroup, anAspect); + } else { + aspectToGroup.removeObjectForKey(anAspect); } // attach new key - if ( aKey != null ) - { - aspectToKey.setObjectForKey( aKey, anAspect ); + if (aKey != null) { + aspectToKey.setObjectForKey(aKey, anAspect); + } else { + aspectToKey.removeObjectForKey(anAspect); } - else - { - aspectToKey.removeObjectForKey( anAspect ); + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object, but remember to call this method. + * This implementation unregisters this association from observing each bound + * display group. + */ + public void breakConnection() { + discardPendingNotification(); + Enumeration e = aspectToGroup.objectEnumerator(); + while (e.hasMoreElements()) { + EOObserverCenter.removeObserver(this, e.nextElement()); } - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object, but remember to call this method. - * This implementation unregisters this association - * from observing each bound display group. - */ - public void breakConnection () - { - discardPendingNotification(); - Enumeration e = aspectToGroup.objectEnumerator(); - while ( e.hasMoreElements() ) - { - EOObserverCenter.removeObserver( this, e.nextElement() ); + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. This implementation returns + * false. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return false; + } + + /** + * Copies the binding for each aspect in this association that has a matching + * aspect in the specified association. + */ + public void copyMatchingBindingsFromAssociation(EOAssociation anAssociation) { + // FIXME: this is broken: aspects() returns EOAssociation.aspects() + // NOTE: This is actually quite crazy - should this even be static? -ceg + NSMutableArray ourAspects = new NSMutableArray(this.aspects()); + NSArray theirAspects = anAssociation.aspects(); + + String aspect; + Enumeration e = ourAspects.objectEnumerator(); + while (e.hasMoreElements()) { + aspect = e.nextElement().toString(); + if (theirAspects.containsObject(aspect)) { + this.bindAspect(aspect, anAssociation.displayGroupForAspect(aspect), + anAssociation.displayGroupKeyForAspect(aspect)); + } } - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - * This implementation returns false. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return false; - } - - /** - * Copies the binding for each aspect in this association - * that has a matching aspect in the specified association. - */ - public void copyMatchingBindingsFromAssociation ( - EOAssociation anAssociation ) - { - //FIXME: this is broken: aspects() returns EOAssociation.aspects() - //NOTE: This is actually quite crazy - should this even be static? -ceg - NSMutableArray ourAspects = new NSMutableArray( this.aspects() ); - NSArray theirAspects = anAssociation.aspects(); - - String aspect; - Enumeration e = ourAspects.objectEnumerator(); - while ( e.hasMoreElements() ) - { - aspect = e.nextElement().toString(); - if ( theirAspects.containsObject( aspect ) ) - { - this.bindAspect( - aspect, - anAssociation.displayGroupForAspect( aspect ), - anAssociation.displayGroupKeyForAspect( aspect ) ); - } - } - } - - /** - * Returns the display group that is bound the specified - * aspect, or null if no display group is currently - * bound to that aspect. - */ - public EODisplayGroup displayGroupForAspect ( String anAspect ) - { - return (EODisplayGroup) aspectToGroup.objectForKey( anAspect ); - } - - /** - * Returns the key for the display group bound to the - * specified aspect, or null if no display group is currently - * bound to that aspect. - */ - public String displayGroupKeyForAspect ( String anAspect ) - { - return (String) aspectToKey.objectForKey( anAspect ); - } - - /** - * The human-readable descriptive name for this association. - * This implementation returns the class name. - */ - public String displayName () // was static - can static method get class name? - { - String className = getClass().getName(); - int index = className.lastIndexOf( "." ); - if ( index == -1 ) return className; - return className.substring( index+1 ); - } - - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * This implementation returns true. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - return true; - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should populate - * their controlled object from the display group and begin - * listening for events from their controlled object here, - * but remember to call this method. Any existing value - * in the controlled object will be overwritten. - * This implementation registers this assocation to - * observer changes to each of the bound display groups. - */ - public void establishConnection () - { - Enumeration e = aspectToGroup.objectEnumerator(); - while ( e.hasMoreElements() ) - { - EOObserverCenter.addObserver( this, e.nextElement() ); + } + + /** + * Returns the display group that is bound the specified aspect, or null if no + * display group is currently bound to that aspect. + */ + public EODisplayGroup displayGroupForAspect(String anAspect) { + return (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + } + + /** + * Returns the key for the display group bound to the specified aspect, or null + * if no display group is currently bound to that aspect. + */ + public String displayGroupKeyForAspect(String anAspect) { + return (String) aspectToKey.objectForKey(anAspect); + } + + /** + * The human-readable descriptive name for this association. This implementation + * returns the class name. + */ + public String displayName() // was static - can static method get class name? + { + String className = getClass().getName(); + int index = className.lastIndexOf("."); + if (index == -1) + return className; + return className.substring(index + 1); + } + + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. This implementation returns true. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + return true; + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should populate their controlled object from the display group and + * begin listening for events from their controlled object here, but remember to + * call this method. Any existing value in the controlled object will be + * overwritten. This implementation registers this assocation to observer + * changes to each of the bound display groups. + */ + public void establishConnection() { + Enumeration e = aspectToGroup.objectEnumerator(); + while (e.hasMoreElements()) { + EOObserverCenter.addObserver(this, e.nextElement()); } - } - - /** - * Returns whether this class can control the specified - * object. This implementation returns false. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return false; - } - - /** - * Returns the object that is currently controlled by this - * association, or null if no object is currently controlled. - */ - public Object object () - { - return control; - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - * This implementation returns an empty list. - */ - public static NSArray objectKeysTaken () - { - return new NSArray(); - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - * This implementation returns null. - */ - public static String primaryAspect () - { - return null; - } - - /** - * Writes the specified value for the display group - * and key currently bound to the specified aspect. - * This implementation calls - * EODisplayGroup.setSelectedObjectValue(). - * @return True if the value was successfully set, - * otherwise false. - */ - public boolean setValueForAspect ( - Object aValue, - String anAspect ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return false; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return false; - - //FIXME: is this the right method to call - return group.setSelectedObjectValue( aValue, key ); - } - - - /** - * Writes the specified value for the display group - * and key currently bound to the specified aspect, - * at the specified index (assuming it's an indexed - * property). - * This implementation calls - * EODisplayGroup.setValueForObjectAtIndex(). - * @return True if the value was successfully set, - * otherwise false. - */ - public boolean setValueForAspectAtIndex ( - Object aValue, - String anAspect, - int anIndex ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return false; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return false; - - //FIXME: is this the right method to call? - return group.setValueForObjectAtIndex( aValue, anIndex, key ); - } - - /** - * Called by subclasses to notify that the user's input - * failed validation. Notifies the display group for - * the aspect, returning the result. - * This implementation calls - * EODisplayGroup.associationFailedToValidateValue() - * with the display group's selected object. - * @return True if the user should be allowed to continue, - * meaning that the display group with handle user notification, - * otherwise false meaning the caller should notify the user. - */ - public boolean shouldEndEditing ( - String anAspect, - String anInvalidInput, - String anErrorDescription ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return false; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return false; - return group.associationFailedToValidateValue( - this, anInvalidInput, key, - group.selectedObject(), //FIXME: is this correct? - anErrorDescription ); - } - - /** - * Called by subclasses to notify that the user's input - * failed validation. Notifies the display group for - * the aspect, returning the result. - * This implementation calls - * EODisplayGroup.associationFailedToValidateValue() - * with the object in the display group's displayed - * objects array at the specified index. - * @return True if the user should be allowed to continue, - * meaning that the display group with handle user notification, - * otherwise false meaning the caller should notify the user. - */ - public boolean shouldEndEditingAtIndex ( - String anAspect, - String anInvalidInput, - String anErrorDescription, - int anIndex ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return false; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return false; - return group.associationFailedToValidateValue( - this, anInvalidInput, key, - group.displayedObjects().objectAtIndex( anIndex ), - //FIXME: is this correct? - anErrorDescription ); - } - - /** - * Called when either the selection or the contents of - * an associated display group have changed. - * This implementation does nothing. - */ - public void subjectChanged () - { - // does nothing - } - - /** - * Returns the current value for the display group - * and key associated with the specified aspect. - */ - public Object valueForAspect ( String anAspect ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return null; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return null; - - //FIXME: is this correct? use selected object? - return group.valueForObject( group.selectedObject(), key ); - } - - /** - * Returns the current value for the display group - * and key associated with the specified aspect, - * and the specified index (assuming multiple values). - */ - public Object valueForAspectAtIndex ( - String anAspect, int anIndex ) - { - EODisplayGroup group = (EODisplayGroup) - aspectToGroup.objectForKey( anAspect ); - if ( group == null ) return null; - String key = (String) - aspectToKey.objectForKey( anAspect ); - if ( key == null ) return null; - - //FIXME: is this the right method to call? - return group.valueForObjectAtIndex( anIndex, key ); - } - + } + + /** + * Returns whether this class can control the specified object. This + * implementation returns false. + */ + public static boolean isUsableWithObject(Object anObject) { + return false; + } + + /** + * Returns the object that is currently controlled by this association, or null + * if no object is currently controlled. + */ + public Object object() { + return control; + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". This implementation + * returns an empty list. + */ + public static NSArray objectKeysTaken() { + return new NSArray(); + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. This implementation returns null. + */ + public static String primaryAspect() { + return null; + } + + /** + * Writes the specified value for the display group and key currently bound to + * the specified aspect. This implementation calls + * EODisplayGroup.setSelectedObjectValue(). + * + * @return True if the value was successfully set, otherwise false. + */ + public boolean setValueForAspect(Object aValue, String anAspect) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return false; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return false; + + // FIXME: is this the right method to call + return group.setSelectedObjectValue(aValue, key); + } + + /** + * Writes the specified value for the display group and key currently bound to + * the specified aspect, at the specified index (assuming it's an indexed + * property). This implementation calls + * EODisplayGroup.setValueForObjectAtIndex(). + * + * @return True if the value was successfully set, otherwise false. + */ + public boolean setValueForAspectAtIndex(Object aValue, String anAspect, int anIndex) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return false; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return false; + + // FIXME: is this the right method to call? + return group.setValueForObjectAtIndex(aValue, anIndex, key); + } + + /** + * Called by subclasses to notify that the user's input failed validation. + * Notifies the display group for the aspect, returning the result. This + * implementation calls EODisplayGroup.associationFailedToValidateValue() with + * the display group's selected object. + * + * @return True if the user should be allowed to continue, meaning that the + * display group with handle user notification, otherwise false meaning + * the caller should notify the user. + */ + public boolean shouldEndEditing(String anAspect, String anInvalidInput, String anErrorDescription) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return false; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return false; + return group.associationFailedToValidateValue(this, anInvalidInput, key, group.selectedObject(), // FIXME: is + // this + // correct? + anErrorDescription); + } + + /** + * Called by subclasses to notify that the user's input failed validation. + * Notifies the display group for the aspect, returning the result. This + * implementation calls EODisplayGroup.associationFailedToValidateValue() with + * the object in the display group's displayed objects array at the specified + * index. + * + * @return True if the user should be allowed to continue, meaning that the + * display group with handle user notification, otherwise false meaning + * the caller should notify the user. + */ + public boolean shouldEndEditingAtIndex(String anAspect, String anInvalidInput, String anErrorDescription, + int anIndex) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return false; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return false; + return group.associationFailedToValidateValue(this, anInvalidInput, key, + group.displayedObjects().objectAtIndex(anIndex), + // FIXME: is this correct? + anErrorDescription); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. This implementation does nothing. + */ + public void subjectChanged() { + // does nothing + } + + /** + * Returns the current value for the display group and key associated with the + * specified aspect. + */ + public Object valueForAspect(String anAspect) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return null; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return null; + + // FIXME: is this correct? use selected object? + return group.valueForObject(group.selectedObject(), key); + } + + /** + * Returns the current value for the display group and key associated with the + * specified aspect, and the specified index (assuming multiple values). + */ + public Object valueForAspectAtIndex(String anAspect, int anIndex) { + EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); + if (group == null) + return null; + String key = (String) aspectToKey.objectForKey(anAspect); + if (key == null) + return null; + + // FIXME: is this the right method to call? + return group.valueForObjectAtIndex(anIndex, key); + } + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2005/05/11 15:21:53 cgruber - * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * Revision 1.7 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.6 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.5 2003/06/06 20:29:45 mpowers - * Now dequeuing the association when connection is broken. - * Coalesced change events had been processed even after breaking connection. + * Revision 1.5 2003/06/06 20:29:45 mpowers Now dequeuing the association when + * connection is broken. Coalesced change events had been processed even after + * breaking connection. * - * Revision 1.4 2001/03/06 23:43:46 mpowers - * Implemented icon aspect for text association. + * Revision 1.4 2001/03/06 23:43:46 mpowers Implemented icon aspect for text + * association. * - * Revision 1.3 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.2 2001/01/25 17:46:11 mpowers - * Clarified usage and gc expectations. + * Revision 1.2 2001/01/25 17:46:11 mpowers Clarified usage and gc expectations. * - * Revision 1.1.1.1 2000/12/21 15:48:07 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:07 mpowers Contributing wotonomy. * - * Revision 1.11 2000/12/20 16:25:39 michael - * Added log to all files. + * Revision 1.11 2000/12/20 16:25:39 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java index ed65b1c..fd80713 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/EODisplayGroup.java @@ -51,2309 +51,1867 @@ import net.wotonomy.foundation.internal.Duplicator; import net.wotonomy.foundation.internal.WotonomyException; /** -* EODisplayGroup provides an abstraction of a user interface, -* comprising of an ordered collection of data objects, some -* of which are displayed, and of those some are selected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class EODisplayGroup extends Observable - implements EOObserving, EOEditingContext.Editor -{ - /** - * Notification sent when the display group is about to fetch. - */ - public static final String DisplayGroupWillFetchNotification - = "DisplayGroupWillFetchNotification"; - - private static boolean - globalDefaultForValidatesChangesImmediately = true; - private static String - globalDefaultStringMatchFormat = "caseInsensitiveLike"; - private static String - globalDefaultStringMatchOperator = "%@*"; - - protected NSMutableArray allObjects; - protected NSArray allObjectsProxy; - protected NSMutableArray displayedObjects; - protected NSArray displayedObjectsProxy; - protected NSMutableArray selectedObjects; - protected NSArray selectedObjectsProxy; - protected NSMutableArray selectedIndexes; - - private String defaultStringMatchOperator; - private String defaultStringMatchFormat; - - private boolean validatesChangesImmediately; - private Object delegate; - private EODataSource dataSource; - private EOAssociation editingAssociation; - private EOQualifier qualifier; - private NSMutableArray sortOrderings; - private NSArray sortOrderingsProxy; - - private NSArray localKeys; - private NSDictionary insertedObjectDefaultValues; - private boolean fetchesOnLoad; - private boolean selectsFirstObjectAfterFetch; - private boolean usesOptimisticRefresh; - private boolean inQueryMode; - - // change detection: package access for helper classes - boolean contentsChanged; - boolean selectionChanged; - int updatedObjectIndex; - - // this property is not in the spec - private boolean compareByReference = false; - - private EOObserving lastGroupObserver; - - /** - * Creates a new display group. - */ - public EODisplayGroup () - { - validatesChangesImmediately = - globalDefaultForValidatesChangesImmediately(); - defaultStringMatchOperator = - globalDefaultStringMatchFormat(); - defaultStringMatchFormat = - globalDefaultStringMatchOperator(); - - allObjects = new ObservableArray( this ); - allObjectsProxy = NSArray.arrayBackedByList( allObjects ); - displayedObjects = new NSMutableArray(); - displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects ); - selectedObjects = new NSMutableArray(); - selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects ); - sortOrderings = new NSMutableArray(); - sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings ); - selectedIndexes = new NSMutableArray(); - - delegate = null; - dataSource = null; - editingAssociation = null; - qualifier = null; - - localKeys = new NSArray(); // not implemented - insertedObjectDefaultValues = new NSDictionary(); - fetchesOnLoad = false; // not implemented - selectsFirstObjectAfterFetch = false; - usesOptimisticRefresh = false; - inQueryMode = false; // not implemented - - contentsChanged = false; - selectionChanged = false; - updatedObjectIndex = -1; - - // create our private delayed observer - lastGroupObserver = new LastGroupObserver( this ); - EOObserverCenter.addObserver( lastGroupObserver, this ); - } - - - - // specify optional data source - - /** - * Sets the data source that will be used by - * this display group. - */ - public void setDataSource ( EODataSource aDataSource ) - { - if ( ( dataSource != null ) - && ( dataSource.editingContext() != null ) ) - { - // un-register for notifications from existing parent store - NSNotificationCenter.defaultCenter().removeObserver( - this, null, dataSource.editingContext() ); - dataSource.editingContext().removeEditor( this ); - if ( dataSource.editingContext().messageHandler() == this ) - { - dataSource.editingContext().setMessageHandler( null ); - } - - } - - dataSource = aDataSource; - - if ( ( dataSource != null ) - && ( dataSource.editingContext() != null ) ) - { - // register for notifications from parent store - NSNotificationCenter.defaultCenter().addObserver( - this, new NSSelector( "objectsInvalidatedInEditingContext", - new Class[] { NSNotification.class } ), - null, dataSource.editingContext() ); - - // add ourselves as editor - dataSource.editingContext().addEditor( this ); - - // add ourselves as message handler if no such handler exists - if ( dataSource.editingContext().messageHandler() == null ) - { - dataSource.editingContext().setMessageHandler( this ); - } - } - } - - /** - * Returns the current data source backing this display group, - * or null if no dataSource is currently used. - */ - public EODataSource dataSource () - { - return dataSource; - } - - - - // specify optional delegate - - /** - * Sets the display group delegate that - * will be used by this display group. - */ - public void setDelegate ( Object aDelegate ) - { - delegate = aDelegate; - } - - /** - * Returns the current delegate for this display group, - * or null if no delegate is currently set. - */ - public Object delegate () - { - return delegate; - } - - - - // display group configuration - - /** - * Returns the current string matching format. - * If not set, defaults to "%@*". - */ - public String defaultStringMatchFormat () - { - return defaultStringMatchFormat; - } - - /** - * Returns the current string matching operator. - * If not set, defaults to "caseInsensitiveLike". - */ - public String defaultStringMatchOperator () - { - return defaultStringMatchOperator; - } - - /** - * Sets the display group and associations to edit a - * "query by example" query object. This method is - * used for target/action connections. - */ - public void enterQueryMode ( Object aSender ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether this display group should immediate - * fetch when loaded. - */ - public boolean fetchesOnLoad () - { - return fetchesOnLoad; - } - - /** - * Returns whether this display group is in "query by - * example" mode. - */ - public boolean inQueryMode () - { - return inQueryMode; - } - - /** - * Returns a Map of default values that are applied - * to new objects that are inserted into the list. - */ - public NSDictionary insertedObjectDefaultValues () - { - return insertedObjectDefaultValues; - } - - /** - * Returns the keys that were declared when read from - * an external resource file. - */ - public NSArray localKeys () - { - return localKeys; - } - - /** - * Sets whether this display group will select the - * first object in the list after a fetch. - */ - public boolean selectsFirstObjectAfterFetch () - { - return selectsFirstObjectAfterFetch; - } - - /** - * Sets the default string matching format that - * will be used by this display group. - */ - public void setDefaultStringMatchFormat ( String aFormat ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the default string matching operator that - * will be used by this display group. - */ - public void setDefaultStringMatchOperator ( String anOperator ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets whether this display group will fetch objects - * from its data source on load. - */ - public void setFetchesOnLoad ( boolean willFetch ) - { - fetchesOnLoad = willFetch; - } - - /** - * Sets whether this display group is in "query by example" - * mode. If true, all associations will bind to a special - * "example" object. - */ - public void setInQueryMode ( boolean isInQueryMode ) - { - inQueryMode = isInQueryMode; - } - - /** - * Sets the mapping that contains the values that will - * be applied to new objects inserted into the display group. - */ - public void setInsertedObjectDefaultValues ( Map aMap ) - { - insertedObjectDefaultValues = new NSDictionary( aMap ); - } - - /** - * Sets the keys that are declared when instantiated from - * an external resource file. - */ - public void setLocalKeys ( List aKeyList ) - { - localKeys = new NSArray( (Collection) aKeyList ); - } - - /** - * Sets whether the first object in the list will be - * selected after a fetch. - */ - public void setSelectsFirstObjectAfterFetch ( - boolean selectsFirst ) - { - selectsFirstObjectAfterFetch = selectsFirst; - } - - /** - * Sets the order of the keys by which this display group - * will be ordered after a fetch or after a call to - * updateDisplayedObjects(). The elements in the display - * group will be sorted first by the first key, within - * the first key, by the second key, and so on. - */ - public void setSortOrderings ( List aList ) - { - sortOrderings.removeAllObjects(); - - Object o; - Iterator it = aList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - // handle the convenience of specifying just a key - if ( ! ( o instanceof EOSortOrdering ) ) - { - o = new EOSortOrdering( - o.toString(), EOSortOrdering.CompareAscending ); - } - sortOrderings.add( o ); - } - } - - /** - * Sets whether only changed objects are refreshed (optimistic), - * or whether all objects are refreshed (pessimistic, default). - * By default, when the display group receives notification that - * one of its objects has changed, updateDisplayedObjects is called. - */ - public void setUsesOptimisticRefresh ( boolean isOptimistic ) - { - usesOptimisticRefresh = isOptimistic; - } - - /** - * Sets whether changes made by associations are validated - * immediately, or when changes are saved. - */ - public void setValidatesChangesImmediately ( - boolean validatesImmediately ) - { - validatesChangesImmediately = validatesImmediately; - } - - /** - * Returns a read-only List of sort orderings for this display group. - */ - public NSArray sortOrderings () - { - return sortOrderingsProxy; - } - - /** - * Returns whether this display group refreshes only - * the changed objects or all objects on refresh. - */ - public boolean usesOptimisticRefresh () - { - return usesOptimisticRefresh; - } - - /** - * Returns whether this display group validates changes - * immediately. Otherwise, validation should occur when - * changes are saved. Default is the global default, - * which is initially true. - */ - public boolean validatesChangesImmediately () - { - return validatesChangesImmediately; - } - - - // qualification - - /** - * Returns a qualifier that will be applied all the objects - * in this display group to determine which objects will - * be displayed. - */ - public EOQualifier qualifier () - { - return qualifier; - } - - /** - * Returns a new qualifier built from the three query - * value maps: greater than, equal to, and less than. - */ - public EOQualifier qualifierFromQueryValues () - { - //TODO: assemble qualifier from query values - - return new EOQualifier() - { - // use inner class until we actually implement one - public EOQualifier qualifierWithBindings( - Map aMap, - boolean requireAll ) - { - return null; - } - public Throwable - validateKeysWithRootClassDescription( Class aClass ) - { - return null; - } - public boolean evaluateWithObject(Object o) - { - return false; - } - }; - } - - /** - * Calls qualifierFromQueryValues(), applies the result - * to the data source, and calls fetch(). - */ - public void qualifyDataSource () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Calls qualifierFromQueryValues(), sets the qualifier - * with setQualifier(), and calls updateDisplayedObjects(). - */ - public void qualifyDisplayGroup () - { - setQualifier( qualifierFromQueryValues() ); - updateDisplayedObjects(); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values. - */ - public NSDictionary queryBindingValues () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to operator values. - */ - public NSDictionary queryOperatorValues () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the qualifier that will be used by - * updateDisplayedObjects() to filter displayed objects. - */ - public void setQualifier ( EOQualifier aQualifier ) - { - qualifier = aQualifier; - } - - /** - * Sets the mapping that contains the mappings of keys - * to binding values. - */ - public void setQueryBindingValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to operator values. - */ - public void setQueryOperatorValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - - - // qualifier query values - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for equality. - */ - public NSDictionary equalToQueryValues () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for greater value. - */ - public NSDictionary greaterThanQueryValues () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for lesser value. - */ - public NSDictionary lessThanQueryValues () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the Map that contains the mappings of keys - * to query values that will be used to test for equality. - */ - public void setEqualToQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to query values that will be used to test for greater value. - */ - public void setGreaterThanQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to query values that will be used to test for lesser value. - */ - public void setLessThanQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - - // interface to associations - - /** - * Called by an association when it begins editing. - */ - public void associationDidBeginEditing ( EOAssociation anAssociation ) - { // System.out.println( "EODisplayGroup.associationDidBeginEditing: " + anAssociation ); - if ( dataSource != null ) - { - if ( dataSource.editingContext() != null ) - { - dataSource.editingContext().setMessageHandler( this ); - } - } - editingAssociation = anAssociation; - } - - /** - * Called by an association when it is finished editing. - */ - public void associationDidEndEditing ( EOAssociation anAssociation ) - { // System.out.println( "EODisplayGroup.associationDidEndEditing: " + anAssociation ); - editingAssociation = null; - } - - /** - * Called by associations to determine whether the contents - * of any objects have been changed. Returns true if the - * contents have changed and not all observers have been - * notified. - */ - public boolean contentsChanged () - { - return contentsChanged; - } - - /** - * Called by associations to determine whether the - * selection has been changed. Returns true if the - * selection has changed and not all observers have - * been notified. - */ - public boolean selectionChanged () - { - return selectionChanged; - } - - /** - * Called by an association when a user-specified value fails the association's - * validation rules. This implementation returns true, unless the delegate - * prevents this. - * @return True to allow the association to handle user notification, - * otherwise return false to let the association know that the - * display group notified the user. - */ - public boolean associationFailedToValidateValue ( - EOAssociation anAssociation, - String aValue, - String aKey, - Object anObject, - String anErrorDescription ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { EODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Validation Failed", anErrorDescription } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - return true; - } - return false; - } - - /** - * Called by an association to determine whether it should enable - * a component that displayes a value for the specified key. - * @return true if an object is selected, or if - * the specified key is a query key. Otherwise false. - */ - public boolean enabledToSetSelectedObjectValueForKey ( String aKey ) - { - if ( ( aKey != null ) && ( aKey.startsWith( "@" ) ) ) return true; - return ( selectedObject() != null ); - } - - - // association management - - /** - * Returns the association that is currently being edited, - * or null if no editing is taking place. - */ - public EOAssociation editingAssociation () - { - return editingAssociation; - } - - /** - * Asks the association currently editing to stop editing. - * @returns true if editing was stopped, false if the - * association refused to stop editing (if a modal dialog - * is displayed or a value failed to validate). - */ - public boolean endEditing () - { - if ( editingAssociation == null ) return true; - return editingAssociation.endEditing(); - } - - /** - * Returns a read-only List of associations that are observing - * this display group. - */ - public NSArray observingAssociations () - { - NSArray observers = - EOObserverCenter.observersForObject( this ); - NSMutableArray result = new NSMutableArray(); - - Object o; - Enumeration e = observers.objectEnumerator(); - while ( e.hasMoreElements() ) - { - o = e.nextElement(); - if ( o instanceof EOAssociation ) - { - result.addObject( o ); - } - } - return result; - } - - - - // object management - - /** - * Returns a read-only List containing all objects managed by the display group. - * This includes those objects not visible due to disqualification. - */ - public NSArray allObjects () - { //System.out.println( "avoided allocation: allObjects" ); - return allObjectsProxy; - } - - /** - * Clears the current selection. - * @return True is the selection was cleared, - * False if the selection could not be cleared - * @see #setSelectionIndexes - */ - public boolean clearSelection () - { - Object result = notifyDelegate( - "displayGroupShouldChangeSelection", - new Class[] { EODisplayGroup.class, List.class }, - new Object[] { this, new NSArray( selectedObjects ) } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return false; - } - - selectionChanged = true; - willChange(); - - selectedObjects.removeAllObjects(); - selectedIndexes.removeAllObjects(); - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - - return true; - } - - /** - * Deletes the object at the specified index, - * notifying the delegate before and after the operation, - * and then updating the selection if needed. - * @return True if delete was successful, false if the - * object was not deleted. - */ - public boolean deleteObjectAtIndex ( int anIndex ) - { - Object target = displayedObjects.objectAtIndex( anIndex ); - - Object result = notifyDelegate( - "displayGroupShouldDeleteObject", - new Class[] { EODisplayGroup.class, Object.class }, - new Object[] { this, target } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return false; - } - - contentsChanged = true; - - deleteObjectAtIndexNoNotify( anIndex ); - - if ( dataSource != null ) - { - dataSource.deleteObject( target ); - } - - notifyDelegate( - "displayGroupDidDeleteObject", - new Class[] { EODisplayGroup.class, Object.class }, - new Object[] { this, target } ); - - return true; - } - - private void deleteObjectAtIndexNoNotify ( int anIndex ) - { - Object target = displayedObjects.objectAtIndex( anIndex ); - - int i; - - // remove from selected objects if necessary - i = indexOf( selectedObjects, target ); - if ( i != NSArray.NotFound ) - { - selectionChanged = true; - willChange(); // notify before removing - selectedObjects.removeObjectAtIndex( i ); - selectedIndexes.remove( new Integer( i ) ); // comps by value - } - else // notify - no selection change needed - { - willChange(); - } - - // remove from all objects - i = indexOf( allObjects, target ); - if ( i != NSArray.NotFound ) - { - allObjects.removeObjectAtIndex( i ); - } - else // otherwise should never happen - { + * EODisplayGroup provides an abstraction of a user interface, comprising of an + * ordered collection of data objects, some of which are displayed, and of those + * some are selected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class EODisplayGroup extends Observable implements EOObserving, EOEditingContext.Editor { + /** + * Notification sent when the display group is about to fetch. + */ + public static final String DisplayGroupWillFetchNotification = "DisplayGroupWillFetchNotification"; + + private static boolean globalDefaultForValidatesChangesImmediately = true; + private static String globalDefaultStringMatchFormat = "caseInsensitiveLike"; + private static String globalDefaultStringMatchOperator = "%@*"; + + protected NSMutableArray allObjects; + protected NSArray allObjectsProxy; + protected NSMutableArray displayedObjects; + protected NSArray displayedObjectsProxy; + protected NSMutableArray selectedObjects; + protected NSArray selectedObjectsProxy; + protected NSMutableArray selectedIndexes; + + private String defaultStringMatchOperator; + private String defaultStringMatchFormat; + + private boolean validatesChangesImmediately; + private Object delegate; + private EODataSource dataSource; + private EOAssociation editingAssociation; + private EOQualifier qualifier; + private NSMutableArray sortOrderings; + private NSArray sortOrderingsProxy; + + private NSArray localKeys; + private NSDictionary insertedObjectDefaultValues; + private boolean fetchesOnLoad; + private boolean selectsFirstObjectAfterFetch; + private boolean usesOptimisticRefresh; + private boolean inQueryMode; + + // change detection: package access for helper classes + boolean contentsChanged; + boolean selectionChanged; + int updatedObjectIndex; + + // this property is not in the spec + private boolean compareByReference = false; + + private EOObserving lastGroupObserver; + + /** + * Creates a new display group. + */ + public EODisplayGroup() { + validatesChangesImmediately = globalDefaultForValidatesChangesImmediately(); + defaultStringMatchOperator = globalDefaultStringMatchFormat(); + defaultStringMatchFormat = globalDefaultStringMatchOperator(); + + allObjects = new ObservableArray(this); + allObjectsProxy = NSArray.arrayBackedByList(allObjects); + displayedObjects = new NSMutableArray(); + displayedObjectsProxy = NSArray.arrayBackedByList(displayedObjects); + selectedObjects = new NSMutableArray(); + selectedObjectsProxy = NSArray.arrayBackedByList(selectedObjects); + sortOrderings = new NSMutableArray(); + sortOrderingsProxy = NSArray.arrayBackedByList(sortOrderings); + selectedIndexes = new NSMutableArray(); + + delegate = null; + dataSource = null; + editingAssociation = null; + qualifier = null; + + localKeys = new NSArray(); // not implemented + insertedObjectDefaultValues = new NSDictionary(); + fetchesOnLoad = false; // not implemented + selectsFirstObjectAfterFetch = false; + usesOptimisticRefresh = false; + inQueryMode = false; // not implemented + + contentsChanged = false; + selectionChanged = false; + updatedObjectIndex = -1; + + // create our private delayed observer + lastGroupObserver = new LastGroupObserver(this); + EOObserverCenter.addObserver(lastGroupObserver, this); + } + + // specify optional data source + + /** + * Sets the data source that will be used by this display group. + */ + public void setDataSource(EODataSource aDataSource) { + if ((dataSource != null) && (dataSource.editingContext() != null)) { + // un-register for notifications from existing parent store + NSNotificationCenter.defaultCenter().removeObserver(this, null, dataSource.editingContext()); + dataSource.editingContext().removeEditor(this); + if (dataSource.editingContext().messageHandler() == this) { + dataSource.editingContext().setMessageHandler(null); + } + + } + + dataSource = aDataSource; + + if ((dataSource != null) && (dataSource.editingContext() != null)) { + // register for notifications from parent store + NSNotificationCenter.defaultCenter().addObserver(this, + new NSSelector("objectsInvalidatedInEditingContext", new Class[] { NSNotification.class }), null, + dataSource.editingContext()); + + // add ourselves as editor + dataSource.editingContext().addEditor(this); + + // add ourselves as message handler if no such handler exists + if (dataSource.editingContext().messageHandler() == null) { + dataSource.editingContext().setMessageHandler(this); + } + } + } + + /** + * Returns the current data source backing this display group, or null if no + * dataSource is currently used. + */ + public EODataSource dataSource() { + return dataSource; + } + + // specify optional delegate + + /** + * Sets the display group delegate that will be used by this display group. + */ + public void setDelegate(Object aDelegate) { + delegate = aDelegate; + } + + /** + * Returns the current delegate for this display group, or null if no delegate + * is currently set. + */ + public Object delegate() { + return delegate; + } + + // display group configuration + + /** + * Returns the current string matching format. If not set, defaults to "%@*". + */ + public String defaultStringMatchFormat() { + return defaultStringMatchFormat; + } + + /** + * Returns the current string matching operator. If not set, defaults to + * "caseInsensitiveLike". + */ + public String defaultStringMatchOperator() { + return defaultStringMatchOperator; + } + + /** + * Sets the display group and associations to edit a "query by example" query + * object. This method is used for target/action connections. + */ + public void enterQueryMode(Object aSender) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether this display group should immediate fetch when loaded. + */ + public boolean fetchesOnLoad() { + return fetchesOnLoad; + } + + /** + * Returns whether this display group is in "query by example" mode. + */ + public boolean inQueryMode() { + return inQueryMode; + } + + /** + * Returns a Map of default values that are applied to new objects that are + * inserted into the list. + */ + public NSDictionary insertedObjectDefaultValues() { + return insertedObjectDefaultValues; + } + + /** + * Returns the keys that were declared when read from an external resource file. + */ + public NSArray localKeys() { + return localKeys; + } + + /** + * Sets whether this display group will select the first object in the list + * after a fetch. + */ + public boolean selectsFirstObjectAfterFetch() { + return selectsFirstObjectAfterFetch; + } + + /** + * Sets the default string matching format that will be used by this display + * group. + */ + public void setDefaultStringMatchFormat(String aFormat) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the default string matching operator that will be used by this display + * group. + */ + public void setDefaultStringMatchOperator(String anOperator) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets whether this display group will fetch objects from its data source on + * load. + */ + public void setFetchesOnLoad(boolean willFetch) { + fetchesOnLoad = willFetch; + } + + /** + * Sets whether this display group is in "query by example" mode. If true, all + * associations will bind to a special "example" object. + */ + public void setInQueryMode(boolean isInQueryMode) { + inQueryMode = isInQueryMode; + } + + /** + * Sets the mapping that contains the values that will be applied to new objects + * inserted into the display group. + */ + public void setInsertedObjectDefaultValues(Map aMap) { + insertedObjectDefaultValues = new NSDictionary(aMap); + } + + /** + * Sets the keys that are declared when instantiated from an external resource + * file. + */ + public void setLocalKeys(List aKeyList) { + localKeys = new NSArray((Collection) aKeyList); + } + + /** + * Sets whether the first object in the list will be selected after a fetch. + */ + public void setSelectsFirstObjectAfterFetch(boolean selectsFirst) { + selectsFirstObjectAfterFetch = selectsFirst; + } + + /** + * Sets the order of the keys by which this display group will be ordered after + * a fetch or after a call to updateDisplayedObjects(). The elements in the + * display group will be sorted first by the first key, within the first key, by + * the second key, and so on. + */ + public void setSortOrderings(List aList) { + sortOrderings.removeAllObjects(); + + Object o; + Iterator it = aList.iterator(); + while (it.hasNext()) { + o = it.next(); + // handle the convenience of specifying just a key + if (!(o instanceof EOSortOrdering)) { + o = new EOSortOrdering(o.toString(), EOSortOrdering.CompareAscending); + } + sortOrderings.add(o); + } + } + + /** + * Sets whether only changed objects are refreshed (optimistic), or whether all + * objects are refreshed (pessimistic, default). By default, when the display + * group receives notification that one of its objects has changed, + * updateDisplayedObjects is called. + */ + public void setUsesOptimisticRefresh(boolean isOptimistic) { + usesOptimisticRefresh = isOptimistic; + } + + /** + * Sets whether changes made by associations are validated immediately, or when + * changes are saved. + */ + public void setValidatesChangesImmediately(boolean validatesImmediately) { + validatesChangesImmediately = validatesImmediately; + } + + /** + * Returns a read-only List of sort orderings for this display group. + */ + public NSArray sortOrderings() { + return sortOrderingsProxy; + } + + /** + * Returns whether this display group refreshes only the changed objects or all + * objects on refresh. + */ + public boolean usesOptimisticRefresh() { + return usesOptimisticRefresh; + } + + /** + * Returns whether this display group validates changes immediately. Otherwise, + * validation should occur when changes are saved. Default is the global + * default, which is initially true. + */ + public boolean validatesChangesImmediately() { + return validatesChangesImmediately; + } + + // qualification + + /** + * Returns a qualifier that will be applied all the objects in this display + * group to determine which objects will be displayed. + */ + public EOQualifier qualifier() { + return qualifier; + } + + /** + * Returns a new qualifier built from the three query value maps: greater than, + * equal to, and less than. + */ + public EOQualifier qualifierFromQueryValues() { + // TODO: assemble qualifier from query values + + return new EOQualifier() { + // use inner class until we actually implement one + public EOQualifier qualifierWithBindings(Map aMap, boolean requireAll) { + return null; + } + + public Throwable validateKeysWithRootClassDescription(Class aClass) { + return null; + } + + public boolean evaluateWithObject(Object o) { + return false; + } + }; + } + + /** + * Calls qualifierFromQueryValues(), applies the result to the data source, and + * calls fetch(). + */ + public void qualifyDataSource() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Calls qualifierFromQueryValues(), sets the qualifier with setQualifier(), and + * calls updateDisplayedObjects(). + */ + public void qualifyDisplayGroup() { + setQualifier(qualifierFromQueryValues()); + updateDisplayedObjects(); + } + + /** + * Returns a Map containing the mappings of keys to binding query values. + */ + public NSDictionary queryBindingValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to operator values. + */ + public NSDictionary queryOperatorValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the qualifier that will be used by updateDisplayedObjects() to filter + * displayed objects. + */ + public void setQualifier(EOQualifier aQualifier) { + qualifier = aQualifier; + } + + /** + * Sets the mapping that contains the mappings of keys to binding values. + */ + public void setQueryBindingValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to operator values. + */ + public void setQueryOperatorValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + // qualifier query values + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for equality. + */ + public NSDictionary equalToQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for greater value. + */ + public NSDictionary greaterThanQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for lesser value. + */ + public NSDictionary lessThanQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the Map that contains the mappings of keys to query values that will be + * used to test for equality. + */ + public void setEqualToQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to query values that will + * be used to test for greater value. + */ + public void setGreaterThanQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to query values that will + * be used to test for lesser value. + */ + public void setLessThanQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + // interface to associations + + /** + * Called by an association when it begins editing. + */ + public void associationDidBeginEditing(EOAssociation anAssociation) { // System.out.println( + // "EODisplayGroup.associationDidBeginEditing: + // " + anAssociation ); + if (dataSource != null) { + if (dataSource.editingContext() != null) { + dataSource.editingContext().setMessageHandler(this); + } + } + editingAssociation = anAssociation; + } + + /** + * Called by an association when it is finished editing. + */ + public void associationDidEndEditing(EOAssociation anAssociation) { // System.out.println( + // "EODisplayGroup.associationDidEndEditing: " + + // anAssociation ); + editingAssociation = null; + } + + /** + * Called by associations to determine whether the contents of any objects have + * been changed. Returns true if the contents have changed and not all observers + * have been notified. + */ + public boolean contentsChanged() { + return contentsChanged; + } + + /** + * Called by associations to determine whether the selection has been changed. + * Returns true if the selection has changed and not all observers have been + * notified. + */ + public boolean selectionChanged() { + return selectionChanged; + } + + /** + * Called by an association when a user-specified value fails the association's + * validation rules. This implementation returns true, unless the delegate + * prevents this. + * + * @return True to allow the association to handle user notification, otherwise + * return false to let the association know that the display group + * notified the user. + */ + public boolean associationFailedToValidateValue(EOAssociation anAssociation, String aValue, String aKey, + Object anObject, String anErrorDescription) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Validation Failed", anErrorDescription }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + return true; + } + return false; + } + + /** + * Called by an association to determine whether it should enable a component + * that displayes a value for the specified key. + * + * @return true if an object is selected, or if the specified key is a query + * key. Otherwise false. + */ + public boolean enabledToSetSelectedObjectValueForKey(String aKey) { + if ((aKey != null) && (aKey.startsWith("@"))) + return true; + return (selectedObject() != null); + } + + // association management + + /** + * Returns the association that is currently being edited, or null if no editing + * is taking place. + */ + public EOAssociation editingAssociation() { + return editingAssociation; + } + + /** + * Asks the association currently editing to stop editing. + * + * @returns true if editing was stopped, false if the association refused to + * stop editing (if a modal dialog is displayed or a value failed to + * validate). + */ + public boolean endEditing() { + if (editingAssociation == null) + return true; + return editingAssociation.endEditing(); + } + + /** + * Returns a read-only List of associations that are observing this display + * group. + */ + public NSArray observingAssociations() { + NSArray observers = EOObserverCenter.observersForObject(this); + NSMutableArray result = new NSMutableArray(); + + Object o; + Enumeration e = observers.objectEnumerator(); + while (e.hasMoreElements()) { + o = e.nextElement(); + if (o instanceof EOAssociation) { + result.addObject(o); + } + } + return result; + } + + // object management + + /** + * Returns a read-only List containing all objects managed by the display group. + * This includes those objects not visible due to disqualification. + */ + public NSArray allObjects() { // System.out.println( "avoided allocation: allObjects" ); + return allObjectsProxy; + } + + /** + * Clears the current selection. + * + * @return True is the selection was cleared, False if the selection could not + * be cleared + * @see #setSelectionIndexes + */ + public boolean clearSelection() { + Object result = notifyDelegate("displayGroupShouldChangeSelection", + new Class[] { EODisplayGroup.class, List.class }, new Object[] { this, new NSArray(selectedObjects) }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return false; + } + + selectionChanged = true; + willChange(); + + selectedObjects.removeAllObjects(); + selectedIndexes.removeAllObjects(); + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { EODisplayGroup.class }, new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { EODisplayGroup.class }, + new Object[] { this }); + + return true; + } + + /** + * Deletes the object at the specified index, notifying the delegate before and + * after the operation, and then updating the selection if needed. + * + * @return True if delete was successful, false if the object was not deleted. + */ + public boolean deleteObjectAtIndex(int anIndex) { + Object target = displayedObjects.objectAtIndex(anIndex); + + Object result = notifyDelegate("displayGroupShouldDeleteObject", + new Class[] { EODisplayGroup.class, Object.class }, new Object[] { this, target }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return false; + } + + contentsChanged = true; + + deleteObjectAtIndexNoNotify(anIndex); + + if (dataSource != null) { + dataSource.deleteObject(target); + } + + notifyDelegate("displayGroupDidDeleteObject", new Class[] { EODisplayGroup.class, Object.class }, + new Object[] { this, target }); + + return true; + } + + private void deleteObjectAtIndexNoNotify(int anIndex) { + Object target = displayedObjects.objectAtIndex(anIndex); + + int i; + + // remove from selected objects if necessary + i = indexOf(selectedObjects, target); + if (i != NSArray.NotFound) { + selectionChanged = true; + willChange(); // notify before removing + selectedObjects.removeObjectAtIndex(i); + selectedIndexes.remove(new Integer(i)); // comps by value + } else // notify - no selection change needed + { + willChange(); + } + + // remove from all objects + i = indexOf(allObjects, target); + if (i != NSArray.NotFound) { + allObjects.removeObjectAtIndex(i); + } else // otherwise should never happen + { // throw new WotonomyException( // "Displayed object not found in allObjects" ); - } - - // remove from displayed objects - displayedObjects.removeObjectAtIndex( anIndex ); - } - - /** - * Deletes the currently selected objects. - * This implementation calls deleteObjectAtIndex() for - * each index in the selection list, immediately returning - * false if any delete operation fails. - * @return True if all selected objects were deleted, - * false if any deletion failed. - */ - public boolean deleteSelection () - { - int i; - boolean result = true; - - Enumeration e = new NSArray( selectedObjects ).objectEnumerator(); - while ( e.hasMoreElements() ) - { - i = indexOf( displayedObjects, e.nextElement() ); - if ( i == NSArray.NotFound ) - { - // should never happen - throw new WotonomyException( - "Selected object not found in displayedObjects" ); - } - result = result && deleteObjectAtIndex( i ); - } - - return result; - } - - /** - * Returns a read-only List of all objects in the display group - * that are currently displayed by the associations. - */ - public NSArray displayedObjects () - { // System.out.println( "avoided allocation: displayedObjects" ); - return displayedObjectsProxy; - } - - /** - * Requests a list of objects from the DataSource - * and calls setObjectArray to populate the list. - * More specifically, calls endEditing(), asks the - * delegate, fetches the objects, notifies the delegate, - * and populates the list. - */ - public boolean fetch () - { - endEditing(); - - if ( dataSource == null ) - { - return false; - } - - Object result = notifyDelegate( - "displayGroupShouldFetch", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return false; - } - - NSNotificationCenter.defaultCenter().postNotification( - DisplayGroupWillFetchNotification, this, new NSDictionary() ); - - NSArray objectList = dataSource.fetchObjects(); - - notifyDelegate( - "displayGroupDidFetchObjects", - new Class[] { EODisplayGroup.class, List.class }, - new Object[] { this, objectList } ); - - if ( selectsFirstObjectAfterFetch ) - { - //note: there's a good chance this logic ought to be in master-detail assoc: - // we're doing this because changes in the master object trigger a refetch - // on the child display group which annoyingly changes the selection. - NSArray original = new NSArray( allObjects ); - setObjectArray( objectList ); - if ( displayedObjects.size() > 0 - && !original.equals( allObjects ) ) // don't change if no change - { - setSelectionIndexes( new NSArray( new Integer( 0 ) ) ); - } - } - else - { - setObjectArray( objectList ); - } - - return true; - } - - /** - * Creates a new object at the specified index. - * Calls insertObjectAtIndex() with the result - * from sending createObject() to the data source. - * Presents a JOptionPane if the create fails, unless - * the delegate implements displayGroupCreateObjectFailed. - * @return the newly created object. - */ - public Object insertNewObjectAtIndex ( int anIndex ) - { - Object result = null; - if ( dataSource != null ) - { - result = dataSource.createObject(); - } - if ( result != null ) - { - if ( insertedObjectDefaultValues != null ) - { - Duplicator.writePropertiesForObject( - insertedObjectDefaultValues, result ); - } - insertObjectAtIndex( result, anIndex ); - } - else // create failed - { - if ( delegate() != null ) - { - NSSelector selector = new NSSelector( - "displayGroupCreateObjectFailed", - new Class[] { EODisplayGroup.class, EODataSource.class } ); - if ( selector.implementedByObject( delegate() ) ) - { - try - { - selector.invoke( delegate(), new Object[] { this, dataSource } ); - return result; - } - catch ( Exception exc ) - { - System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" ); - exc.printStackTrace(); - } - } - } - - // no delegate or delegate does not implement displayGroupCreateObjectFailed - - String message = "Data source could not create new object"; - Object delegateResult = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { EODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", message } ); - if ( ( delegateResult == null ) || ( Boolean.TRUE.equals( delegateResult ) ) ) - { - JOptionPane.showMessageDialog( null, message ); - } - } - return result; - } - - /** - * Inserts the specified object into the list at - * the specified index. - */ - public void insertObjectAtIndex ( Object anObject, int anIndex ) - { - Object result = notifyDelegate( - "displayGroupShouldInsertObject", - new Class[] { EODisplayGroup.class, Object.class, int.class }, - new Object[] { this, anObject, new Integer(anIndex) } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return; - } - - contentsChanged = true; - updatedObjectIndex = anIndex; - willChange(); - - - // add to all objects - if ( anIndex == displayedObjects.size() ) - { - allObjects.addObject( anObject ); - } - else // insert before same object - { - Object target = displayedObjects.objectAtIndex( anIndex ); - int targetIndex = indexOf( allObjects, target ); - if ( targetIndex != NSArray.NotFound ) - { - allObjects.insertObjectAtIndex( anObject, targetIndex ); - } - else // should never happen - { - throw new WotonomyException( - "Could not find displayed object in all objects list: " - + target ); - } - } - - // add to displayed objects - displayedObjects.insertObjectAtIndex( anObject, anIndex ); - - if ( dataSource != null ) - { - if ( dataSource instanceof OrderedDataSource ) - { - ((OrderedDataSource)dataSource).insertObjectAtIndex( - anObject, anIndex ); - } - else - { - dataSource.insertObject( anObject ); - } - } - - notifyDelegate( - "displayGroupDidInsertObject", - new Class[] { EODisplayGroup.class, Object.class }, - new Object[] { this, anObject } ); - } - - /** - * Sets contentsChanged to true and notifies all observers. - */ - public void redisplay () - { - contentsChanged = true; - willChange(); - } - - /** - * Sets the selection to the next displayed object after the current - * selection. If the last object is selected, or if no object - * is selected, then the first object becomes selected. - * If multiple items are selected, the first selected item is - * considered the selected item for the purposes of this method. - * Does not call redisplay(). - * @return true if an object was selected. - */ - public boolean selectNext () - { - int count = displayedObjects.count(); - if ( count == 0 ) return false; - if ( count == 1 ) - { - selectObject( displayedObjects.objectAtIndex( 0 ) ); - return true; - } - - int i = -1; - Object selectedObject = selectedObject(); - if ( selectedObject != null ) - { - i = indexOf( displayedObjects, selectedObject ); - } - if ( i == NSArray.NotFound ) i = -1; - - // select next object - i++; - if ( i != displayedObjects.count() ) - { - // set to next object - selectedObject = displayedObjects.objectAtIndex( i ); - } - else // out of range - { - // set to null - selectedObject = displayedObjects.objectAtIndex( 0 ); - } - - return selectObject( selectedObject ); - } - - /** - * Sets the selection to the specified object. - * If the specified object is null or does not exist - * in the list of displayed objects, the selection - * will be cleared. - * @return true if the object was selected. - */ - public boolean selectObject ( Object anObject ) - { - if ( ( anObject == null ) || - ( indexOf( displayedObjects, anObject ) - == NSArray.NotFound ) ) - { - clearSelection(); - return false; - } - - selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) ); - return true; - } - - /** - * Sets the selection to the specified objects. - * If the specified list is null or if none of the objects - * in the list exist in the list of displayed objects, the - * selection will be cleared. - * @return true if all specified objects were selected. - */ - public boolean selectObjectsIdenticalTo ( List anObjectList ) - { - // optimization: check for resetting of selection - if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) ) - { - boolean identical = true; - int size = selectedObjects.size(); - for ( int i = 0; ( i < size ) && identical; i++ ) - { - // compare by reference - if ( anObjectList.get( i ) != selectedObjects.get( i ) ) - { - identical = false; - } - else if ( displayedObjects.indexOfIdenticalObject( - anObjectList.get( i ) ) == NSArray.NotFound ) - { - identical = false; - } - } - if ( identical ) - { - return true; - } - } - - Object result = notifyDelegate( - "displayGroupShouldChangeSelection", - new Class[] { EODisplayGroup.class, List.class }, - new Object[] { this, anObjectList } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - // need to notify the calling component - // to revert back to the previous selection - selectionChanged = true; - willChange(); - return false; - } - - int i; - selectionChanged = true; - willChange(); - Object o; - selectedObjects.removeAllObjects(); - selectedIndexes.removeAllObjects(); - Iterator it = anObjectList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - if ( ( i = displayedObjects.indexOfIdenticalObject( o ) ) - != NSArray.NotFound ) - { - selectedObjects.addObject( o ); - selectedIndexes.addObject( new Integer( i ) ); - } - } - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - - return true; - } - - /** - * Sets the selection to the previous displayed object before the current - * selection. If the first object is selected, or if no object - * is selected, then the last object becomes selected. - * If multiple items are selected, the first selected item is - * considered the selected item for the purposes of this method. - * Does not call redisplay(). - * @return true if an object was selected. - */ - public boolean selectPrevious () - { - int i = displayedObjects.count(); - if ( i == 0 ) return false; - if ( i == 1 ) - { - selectObject( displayedObjects.objectAtIndex( 0 ) ); - return true; - } - - Object selectedObject = selectedObject(); - if ( selectedObject != null ) - { - i = indexOf( displayedObjects, selectedObject ); - } - if ( i == NSArray.NotFound ) i = displayedObjects.count(); - - // select next object - i--; - if ( i < 0 ) - { - // out of range - select last object - i = displayedObjects.count() - 1; - } - - return selectObject( displayedObjects.objectAtIndex( i ) ); - } - - /** - * Returns the currently selected object, or null if - * there is no selection. - */ - public Object selectedObject () - { - if ( selectedObjects.count() == 0 ) - { - return null; - } - return selectedObjects.objectAtIndex( 0 ); - } - - /** - * Returns a read-only List containing all selected objects, if any. - * Returns an empty list if no objects are selected. - */ - public NSArray selectedObjects () - { // System.out.println( "avoided allocation: selectedObjects" ); - return selectedObjectsProxy; - } - - /** - * Returns a read-only List containing the indexes of all selected - * objects, if any. The list contains instances of - * java.lang.Number; call intValue() to retrieve the index. - */ - public NSArray selectionIndexes () - { + } + + // remove from displayed objects + displayedObjects.removeObjectAtIndex(anIndex); + } + + /** + * Deletes the currently selected objects. This implementation calls + * deleteObjectAtIndex() for each index in the selection list, immediately + * returning false if any delete operation fails. + * + * @return True if all selected objects were deleted, false if any deletion + * failed. + */ + public boolean deleteSelection() { + int i; + boolean result = true; + + Enumeration e = new NSArray(selectedObjects).objectEnumerator(); + while (e.hasMoreElements()) { + i = indexOf(displayedObjects, e.nextElement()); + if (i == NSArray.NotFound) { + // should never happen + throw new WotonomyException("Selected object not found in displayedObjects"); + } + result = result && deleteObjectAtIndex(i); + } + + return result; + } + + /** + * Returns a read-only List of all objects in the display group that are + * currently displayed by the associations. + */ + public NSArray displayedObjects() { // System.out.println( "avoided allocation: displayedObjects" ); + return displayedObjectsProxy; + } + + /** + * Requests a list of objects from the DataSource and calls setObjectArray to + * populate the list. More specifically, calls endEditing(), asks the delegate, + * fetches the objects, notifies the delegate, and populates the list. + */ + public boolean fetch() { + endEditing(); + + if (dataSource == null) { + return false; + } + + Object result = notifyDelegate("displayGroupShouldFetch", new Class[] { EODisplayGroup.class }, + new Object[] { this }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return false; + } + + NSNotificationCenter.defaultCenter().postNotification(DisplayGroupWillFetchNotification, this, + new NSDictionary()); + + NSArray objectList = dataSource.fetchObjects(); + + notifyDelegate("displayGroupDidFetchObjects", new Class[] { EODisplayGroup.class, List.class }, + new Object[] { this, objectList }); + + if (selectsFirstObjectAfterFetch) { + // note: there's a good chance this logic ought to be in master-detail assoc: + // we're doing this because changes in the master object trigger a refetch + // on the child display group which annoyingly changes the selection. + NSArray original = new NSArray(allObjects); + setObjectArray(objectList); + if (displayedObjects.size() > 0 && !original.equals(allObjects)) // don't change if no change + { + setSelectionIndexes(new NSArray(new Integer(0))); + } + } else { + setObjectArray(objectList); + } + + return true; + } + + /** + * Creates a new object at the specified index. Calls insertObjectAtIndex() with + * the result from sending createObject() to the data source. Presents a + * JOptionPane if the create fails, unless the delegate implements + * displayGroupCreateObjectFailed. + * + * @return the newly created object. + */ + public Object insertNewObjectAtIndex(int anIndex) { + Object result = null; + if (dataSource != null) { + result = dataSource.createObject(); + } + if (result != null) { + if (insertedObjectDefaultValues != null) { + Duplicator.writePropertiesForObject(insertedObjectDefaultValues, result); + } + insertObjectAtIndex(result, anIndex); + } else // create failed + { + if (delegate() != null) { + NSSelector selector = new NSSelector("displayGroupCreateObjectFailed", + new Class[] { EODisplayGroup.class, EODataSource.class }); + if (selector.implementedByObject(delegate())) { + try { + selector.invoke(delegate(), new Object[] { this, dataSource }); + return result; + } catch (Exception exc) { + System.err.println("Error notifying delegate: displayGroupCreateObjectFailed"); + exc.printStackTrace(); + } + } + } + + // no delegate or delegate does not implement displayGroupCreateObjectFailed + + String message = "Data source could not create new object"; + Object delegateResult = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", message }); + if ((delegateResult == null) || (Boolean.TRUE.equals(delegateResult))) { + JOptionPane.showMessageDialog(null, message); + } + } + return result; + } + + /** + * Inserts the specified object into the list at the specified index. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + Object result = notifyDelegate("displayGroupShouldInsertObject", + new Class[] { EODisplayGroup.class, Object.class, int.class }, + new Object[] { this, anObject, new Integer(anIndex) }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return; + } + + contentsChanged = true; + updatedObjectIndex = anIndex; + willChange(); + + // add to all objects + if (anIndex == displayedObjects.size()) { + allObjects.addObject(anObject); + } else // insert before same object + { + Object target = displayedObjects.objectAtIndex(anIndex); + int targetIndex = indexOf(allObjects, target); + if (targetIndex != NSArray.NotFound) { + allObjects.insertObjectAtIndex(anObject, targetIndex); + } else // should never happen + { + throw new WotonomyException("Could not find displayed object in all objects list: " + target); + } + } + + // add to displayed objects + displayedObjects.insertObjectAtIndex(anObject, anIndex); + + if (dataSource != null) { + if (dataSource instanceof OrderedDataSource) { + ((OrderedDataSource) dataSource).insertObjectAtIndex(anObject, anIndex); + } else { + dataSource.insertObject(anObject); + } + } + + notifyDelegate("displayGroupDidInsertObject", new Class[] { EODisplayGroup.class, Object.class }, + new Object[] { this, anObject }); + } + + /** + * Sets contentsChanged to true and notifies all observers. + */ + public void redisplay() { + contentsChanged = true; + willChange(); + } + + /** + * Sets the selection to the next displayed object after the current selection. + * If the last object is selected, or if no object is selected, then the first + * object becomes selected. If multiple items are selected, the first selected + * item is considered the selected item for the purposes of this method. Does + * not call redisplay(). + * + * @return true if an object was selected. + */ + public boolean selectNext() { + int count = displayedObjects.count(); + if (count == 0) + return false; + if (count == 1) { + selectObject(displayedObjects.objectAtIndex(0)); + return true; + } + + int i = -1; + Object selectedObject = selectedObject(); + if (selectedObject != null) { + i = indexOf(displayedObjects, selectedObject); + } + if (i == NSArray.NotFound) + i = -1; + + // select next object + i++; + if (i != displayedObjects.count()) { + // set to next object + selectedObject = displayedObjects.objectAtIndex(i); + } else // out of range + { + // set to null + selectedObject = displayedObjects.objectAtIndex(0); + } + + return selectObject(selectedObject); + } + + /** + * Sets the selection to the specified object. If the specified object is null + * or does not exist in the list of displayed objects, the selection will be + * cleared. + * + * @return true if the object was selected. + */ + public boolean selectObject(Object anObject) { + if ((anObject == null) || (indexOf(displayedObjects, anObject) == NSArray.NotFound)) { + clearSelection(); + return false; + } + + selectObjectsIdenticalTo(new NSArray(new Object[] { anObject })); + return true; + } + + /** + * Sets the selection to the specified objects. If the specified list is null or + * if none of the objects in the list exist in the list of displayed objects, + * the selection will be cleared. + * + * @return true if all specified objects were selected. + */ + public boolean selectObjectsIdenticalTo(List anObjectList) { + // optimization: check for resetting of selection + if ((anObjectList != null) && (selectedObjects.size() == anObjectList.size())) { + boolean identical = true; + int size = selectedObjects.size(); + for (int i = 0; (i < size) && identical; i++) { + // compare by reference + if (anObjectList.get(i) != selectedObjects.get(i)) { + identical = false; + } else if (displayedObjects.indexOfIdenticalObject(anObjectList.get(i)) == NSArray.NotFound) { + identical = false; + } + } + if (identical) { + return true; + } + } + + Object result = notifyDelegate("displayGroupShouldChangeSelection", + new Class[] { EODisplayGroup.class, List.class }, new Object[] { this, anObjectList }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + // need to notify the calling component + // to revert back to the previous selection + selectionChanged = true; + willChange(); + return false; + } + + int i; + selectionChanged = true; + willChange(); + Object o; + selectedObjects.removeAllObjects(); + selectedIndexes.removeAllObjects(); + Iterator it = anObjectList.iterator(); + while (it.hasNext()) { + o = it.next(); + if ((i = displayedObjects.indexOfIdenticalObject(o)) != NSArray.NotFound) { + selectedObjects.addObject(o); + selectedIndexes.addObject(new Integer(i)); + } + } + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { EODisplayGroup.class }, new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { EODisplayGroup.class }, + new Object[] { this }); + + return true; + } + + /** + * Sets the selection to the previous displayed object before the current + * selection. If the first object is selected, or if no object is selected, then + * the last object becomes selected. If multiple items are selected, the first + * selected item is considered the selected item for the purposes of this + * method. Does not call redisplay(). + * + * @return true if an object was selected. + */ + public boolean selectPrevious() { + int i = displayedObjects.count(); + if (i == 0) + return false; + if (i == 1) { + selectObject(displayedObjects.objectAtIndex(0)); + return true; + } + + Object selectedObject = selectedObject(); + if (selectedObject != null) { + i = indexOf(displayedObjects, selectedObject); + } + if (i == NSArray.NotFound) + i = displayedObjects.count(); + + // select next object + i--; + if (i < 0) { + // out of range - select last object + i = displayedObjects.count() - 1; + } + + return selectObject(displayedObjects.objectAtIndex(i)); + } + + /** + * Returns the currently selected object, or null if there is no selection. + */ + public Object selectedObject() { + if (selectedObjects.count() == 0) { + return null; + } + return selectedObjects.objectAtIndex(0); + } + + /** + * Returns a read-only List containing all selected objects, if any. Returns an + * empty list if no objects are selected. + */ + public NSArray selectedObjects() { // System.out.println( "avoided allocation: selectedObjects" ); + return selectedObjectsProxy; + } + + /** + * Returns a read-only List containing the indexes of all selected objects, if + * any. The list contains instances of java.lang.Number; call intValue() to + * retrieve the index. + */ + public NSArray selectionIndexes() { // return selectedIndexes; - int i; - NSMutableArray result = new NSMutableArray(); - Enumeration e = selectedObjects.objectEnumerator(); - while ( e.hasMoreElements() ) - { - i = indexOf( displayedObjects, e.nextElement() ); - if ( i != NSArray.NotFound ) - { - result.addObject( new Integer( i ) ); - } - else - { - System.err.println( - "Should never happen: selected objects not in displayed objects" ); - new RuntimeException().printStackTrace( System.err ); - } - } - return result; - } - - /** - * Sets the objects managed by this display group. - * updateDisplayedObjects() is called to filter the - * display objects. The previous selection will be - * maintained if possible. The data source is not - * notified. - */ - public void setObjectArray ( List anObjectList ) - { - if ( anObjectList == null ) anObjectList = new NSArray(); - - Object result = notifyDelegate( - "displayGroupDisplayArrayForObjects", - new Class[] { EODisplayGroup.class, List.class }, - new Object[] { this, anObjectList } ); - if ( result != null ) - { - anObjectList = (List) result; - } - - contentsChanged = true; - willChange(); - - NSArray oldSelectedObjects = new NSArray( selectedObjects ); // copy - - // reset allObjects to new list - allObjects.removeAllObjects(); - allObjects.addObjectsFromArray( anObjectList ); - - // update the displayed object list - updateDisplayedObjects(); - - // restore the selection if possible - selectObjectsIdenticalTo( oldSelectedObjects ); - } - - /** - * Sets the currently selected object, or clears the - * selection if the object is not found or is null. - * Note: it's not clear how this differs from - * selectObject in the spec. It is recommended that - * you call selectObject for now. - */ - public void setSelectedObject ( Object anObject ) - { - selectObject( anObject ); - } - - /** - * Sets the current selection to the specified objects. - * The previous selection is cleared, and any objects - * in the display group that are in the specified list - * are then selected. If no items in the specified list - * are found in the display group, then the selection is - * effectively cleared. - * Note: it's not clear how this differs from - * selectObjectsIdenticalTo in the spec. - * It is recommended that you call that method for now. - */ - public void setSelectedObjects ( List aList ) - { - selectObjectsIdenticalTo( aList ); - } - - /** - * Sets the current selection to the objects at the - * specified indexes. Items in the list are assumed - * to be instances of java.lang.Number. - * The previous selection is cleared, and any objects - * in the display group that are in the specified list - * are then selected. If no items in the specified list - * are found in the display group, then the selection is - * effectively cleared. - */ - public boolean setSelectionIndexes ( List aList ) - { - Object o; - int index; - NSMutableArray objects = new NSMutableArray(); - Iterator it = aList.iterator(); - while ( it.hasNext() ) - { - index = ((Number)it.next()).intValue(); - if ( index < displayedObjects.count() ) - { - o = displayedObjects.objectAtIndex( index ); - if ( o != null ) - { - objects.add( o ); - } - } - } - return selectObjectsIdenticalTo( objects ); - } - - /** - * Applies the qualifier to all objects and sorts - * the results to update the list of displayed objects. - * Observing associations are notified to reflect the changes. - */ - public void updateDisplayedObjects () - { - contentsChanged = true; - updatedObjectIndex = -1; - willChange(); - - displayedObjects.removeAllObjects(); - - displayedObjects.addObjectsFromArray( allObjects ); - - // apply qualifier, if any - if ( qualifier() != null ) - { - EOQualifier.filterArrayWithQualifier( - displayedObjects, qualifier() ); - } - - // apply sort orderings, if any - NSArray orderings = sortOrderings(); - if ( orderings != null ) - { - if ( orderings.count() > 0 ) - { - selectionChanged = true; - willChange(); - EOSortOrdering.sortArrayUsingKeyOrderArray( - displayedObjects, orderings ); - } - } - - // make sure the selectedObjects is a subset of displayedObjects - int i; - Object o; - Iterator it = new LinkedList( selectedObjects ).iterator(); - boolean removeflag = false; - selectedIndexes.removeAllObjects(); - while ( it.hasNext() ) - { - o = it.next(); - if ( ( i = displayedObjects.indexOfIdenticalObject( o ) ) - == NSArray.NotFound ) - { - selectedObjects.removeIdenticalObject( o ); - removeflag = true; - } - else - { - selectedIndexes.addObject( new Integer( i ) ); - } - } - - //Note: it is important to put the - //selectionChanged = true line below remove. - if (removeflag) - { - selectionChanged = true; - willChange(); - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { EODisplayGroup.class }, - new Object[] { this } ); - } - } - - /** - * Returns the index of the changed object. If more than - * one object has changed, -1 is returned. - */ - public int updatedObjectIndex () - { - return updatedObjectIndex; - } - - // getting and setting values in objects - - /** - * Returns a value on the selected object for the specified key. - */ - public Object selectedObjectValueForKey ( String aKey ) - { - Object selectedObject = selectedObject(); - if ( selectedObject == null ) return null; - return valueForObject( selectedObject, aKey ); - } - - /** - * Sets the specified value for the specified key on - * all selected objects. - */ - public boolean setSelectedObjectValue ( - Object aValue, String aKey ) - { - Object selectedObject = selectedObject(); - if ( selectedObject == null ) return false; - return setValueForObject( aValue, selectedObject, aKey ); - } - - /** - * Sets the specified value for the specified key on - * the specified object. Validations may be triggered, - * and error dialogs may appear to the user. - * @return True if the value was set successfully, - * false if the value could not be set and the update - * operation should not continue. - */ - public boolean setValueForObject ( - Object aValue, Object anObject, String aKey ) - { - // notify object's observers: - // this includes us, and will notify our observers - EOObserverCenter.notifyObserversObjectWillChange( anObject ); - - //TODO: if key is null, need to remove old object - // and add new object instead of simply replacing it. - - try - { - if ( anObject instanceof EOKeyValueCoding ) - { - ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey ); - } - else - { - EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey ); - } - } - catch ( RuntimeException exc ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { EODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", exc.getMessage() } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - throw exc; - } - return false; - } - - notifyDelegate( - "displayGroupDidSetValueForObject", - new Class[] { EODisplayGroup.class, Object.class, Object.class, String.class }, - new Object[] { this, aValue, anObject, aKey } ); - - return true; - } - - /** - * Calls setValueForObject() for the object at - * the specified index. - */ - public boolean setValueForObjectAtIndex ( - Object aValue, int anIndex, String aKey ) - { - return setValueForObject( - aValue, displayedObjects.objectAtIndex( anIndex ), aKey ); - } - - /** - * Returns the value for the specified key on the specified object. - */ - public Object valueForObject ( Object anObject, String aKey ) - { - // empty string is considered the identity property - if ( aKey == null ) return anObject; - if ( aKey.equals( "" ) ) return anObject; - - try - { - if ( anObject instanceof EOKeyValueCoding ) - { - return ((EOKeyValueCoding)anObject).valueForKey( aKey ); - } - else - { - return EOKeyValueCodingSupport.valueForKey( anObject, aKey ); - } - } - catch ( RuntimeException exc ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { EODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", exc.getMessage() } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - throw exc; - } - return null; - } - } - - /** - * Calls valueForObject() for the object at the specified index. - */ - public Object valueForObjectAtIndex ( int anIndex, String aKey ) - { - Object o = displayedObjects.objectAtIndex( anIndex ); - return valueForObject( o, aKey ); - } - - /** - * Prints out the list of displayed objects. - */ - public String toString() - { - return displayedObjects.toString(); - } - - - /** - * Handles notifications from the data source's editing context, - * looking for InvalidatedAllObjectsInStoreNotification and - * ObjectsChangedInEditingContextNotification, refetching in - * the former case and updating displayed objects in the latter. - * Note: This method is not in the public specification. - */ - public void objectsInvalidatedInEditingContext( NSNotification aNotification ) - { - if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification - .equals( aNotification.name() ) ) - { - Object result = notifyDelegate( - "displayGroupShouldRefetch", - new Class[] { EODisplayGroup.class, NSNotification.class }, - new Object[] { this, aNotification } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - fetch(); - } - } - else - if ( EOEditingContext.ObjectsChangedInEditingContextNotification - .equals( aNotification.name() ) ) - { - Object result = notifyDelegate( - "displayGroupShouldRedisplay", - new Class[] { EODisplayGroup.class, NSNotification.class }, - new Object[] { this, aNotification } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - int index; - Enumeration e; - boolean didChange = false; - NSDictionary userInfo = aNotification.userInfo(); - - // inserts are ignored - - // mark updated objects as updated - NSArray updates = (NSArray) userInfo.objectForKey( - EOObjectStore.UpdatedKey ); - e = updates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - index = indexOf( displayedObjects, e.nextElement() ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "EODisplayGroup: updated: " + index ); - if ( ! didChange ) - { - didChange = true; - contentsChanged = true; - willChange(); - updatedObjectIndex = index; - } - else - { - updatedObjectIndex = -1; - } - } - } - - // treat invalidated objects as updated - NSArray invalidates = (NSArray) userInfo.objectForKey( - EOObjectStore.InvalidatedKey ); - e = invalidates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - index = indexOf( displayedObjects, e.nextElement() ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "EODisplayGroup: invalidated: " + index ); - if ( ! didChange ) - { - didChange = true; - contentsChanged = true; - willChange(); - updatedObjectIndex = index; - } - else - { - updatedObjectIndex = -1; - } - } - } - - // remove deletes from display group if they exist - NSArray deletes = (NSArray) userInfo.objectForKey( - EOObjectStore.DeletedKey ); - e = deletes.objectEnumerator(); - Object o; - while ( e.hasMoreElements() ) - { - o = e.nextElement(); - index = indexOf( displayedObjects, o ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "EODisplayGroup: deleted: " + o ); - deleteObjectAtIndexNoNotify( index ); - } - } - - if ( !usesOptimisticRefresh() ) - { - updateDisplayedObjects(); - } - } - } - - } - - // static methods - - /** - * Specifies the default behavior for whether changes - * should be validated immediately for all display groups. - */ - public static boolean - globalDefaultForValidatesChangesImmediately () - { - return globalDefaultForValidatesChangesImmediately; - } - - /** - * Specifies the default string matching format for all - * display groups. - */ - public static String globalDefaultStringMatchFormat () - { - return globalDefaultStringMatchFormat; - } - - /** - * Specifies the default string matching operator for all - * display groups. - */ - public static String globalDefaultStringMatchOperator () - { - return globalDefaultStringMatchOperator; - } - - /** - * Sets the default behavior for validating changes - * for all display groups. - */ - public static void - setGlobalDefaultForValidatesChangesImmediately ( - boolean validatesImmediately ) - { - globalDefaultForValidatesChangesImmediately = - validatesImmediately; - } - - /** - * Sets the default string matching format that - * will be used by all display groups. - */ - public static void - setGlobalDefaultStringMatchFormat ( String aFormat ) - { - globalDefaultStringMatchFormat = aFormat; - } - - /** - * Sets the default string matching operator that - * will be used by all display groups. - */ - public static void - setGlobalDefaultStringMatchOperator ( String anOperator ) - { - globalDefaultStringMatchOperator = anOperator; - } - - /** - * Needed because we don't inherit from NSObject. - * Calls EOObserverCenter.notifyObserversObjectWillChange. - */ - protected void willChange() - { - EOObserverCenter.notifyObserversObjectWillChange( this ); - } - - /** - * Called by LastGroupObserver to clear flags. - */ - protected void processRecentChanges() - { - contentsChanged = false; - selectionChanged = false; - } - - /** - * Returns the index of the specified object in the - * specified NSArray, comparing by value or by reference - * as determined by the private instance variable - * compareByReference. If not found, returns NSArray.NotFound. - */ - private int indexOf( NSArray anArray, Object anObject ) - { - if ( compareByReference ) - { - return anArray.indexOfIdenticalObject( anObject ); - } - else - { - return anArray.indexOf( anObject ); - } - } - - // interface EOObserving - - /** - * Receives notifications of changes from objects that - * are managed by this display group. This implementation - * sets updatedObjectIndex and contentsChanged as appropriate. - */ - public void objectWillChange(Object anObject) - { - int index = indexOf( displayedObjects, anObject ); - if ( index != NSArray.NotFound ) - { - updatedObjectIndex = index; - contentsChanged = true; - willChange(); - } - } - - // interface EOEditingContext.Editor - - /** - * Called before the editing context begins to save changes. - * This implementation calls endEditing(). - */ - public void editingContextWillSaveChanges( - EOEditingContext anEditingContext ) - { - endEditing(); - } - - /** - * Called to determine whether this editor has changes - * that have not been committed to the object in the context. - */ - public boolean editorHasChangesForEditingContext( - EOEditingContext anEditingContext ) - { - return ( editingAssociation() != null ); - } - - // interface EOEditingContext.MessageHandler - - /** - * Called to display a message for an error that occurred - * in the specified editing context. If the delegate allows, - * this implementation presents an informational JOptionPane. - * Override to customize. - */ - public void editingContextPresentErrorMessage( - EOEditingContext anEditingContext, - String aMessage ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { EODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", aMessage } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - JOptionPane.showMessageDialog( null, 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. - * This implementation presents an JOptionPane allowing the user - * to specify whether to continue. Override to customize. - */ - public boolean editingContextShouldContinueFetching( - EOEditingContext anEditingContext, - int count, - int limit, - EOObjectStore anObjectStore ) - { - return ( JOptionPane.showConfirmDialog( null, - "Fetch limit reached: do you wish to continue?", - "Continue?", - JOptionPane.YES_NO_OPTION ) == JOptionPane.YES_OPTION ); - } - - /** - * 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; - } - - /** - * DisplayGroups can delegate important decisions to a Delegate. - * Note that DisplayGroup doesn't require its delegates to implement - * this interface: rather, this interface defines the methods that - * DisplayGroup 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 when the specified data source fails - * to create an object for the specified display group. - */ - void displayGroupCreateObjectFailed ( - EODisplayGroup aDisplayGroup, - EODataSource aDataSource ); - - /** - * Called after the specified display group's - * data source is changed. - */ - void displayGroupDidChangeDataSource ( - EODisplayGroup aDisplayGroup ); - - /** - * Called after a change occurs in the specified - * display group's selected objects. - */ - void displayGroupDidChangeSelectedObjects ( - EODisplayGroup aDisplayGroup ); - - /** - * Called after the specified display group's - * selection has changed. - */ - void displayGroupDidChangeSelection ( - EODisplayGroup aDisplayGroup ); - - /** - * Called after the specified display group has - * deleted the specified object. - */ - void displayGroupDidDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called after the specified display group - * has fetched the specified object list. - */ - void displayGroupDidFetchObjects ( - EODisplayGroup aDisplayGroup, - List anObjectList ); - - /** - * Called after the specified display group - * has inserted the specified object into - * its internal object list. - */ - void displayGroupDidInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called after the specified display group - * has set the specified value for the specified - * object and key. - */ - void displayGroupDidSetValueForObject ( - EODisplayGroup aDisplayGroup, - Object aValue, - Object anObject, - String aKey ); - - /** - * Called by the specified display group to - * determine what objects should be displayed - * for the objects in the specified list. - * @return An NSArray containing the objects - * to be displayed for the objects in the - * specified list. - */ - NSArray displayGroupDisplayArrayForObjects ( - EODisplayGroup aDisplayGroup, - List aList ); - - /** - * Called by the specified display group before - * it attempts to change the selection. - * @return True to allow the selection to change, - * false otherwise. - */ - boolean displayGroupShouldChangeSelection ( - EODisplayGroup aDisplayGroup, - List aSelectionList ); - - /** - * Called by the specified display group before - * it attempts to delete the specified object. - * @return True to allow the object to be deleted - * false to prevent the deletion. - */ - boolean displayGroupShouldDeleteObject ( - EODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called by the specified display group before - * it attempts display the specified alert to - * the user. - * @return True to allow the message to be - * displayed, false if you want to handle the - * alert yourself and suppress the display group's - * notification. - */ - boolean displayGroupShouldDisplayAlert ( - EODisplayGroup aDisplayGroup, - String aTitle, - String aMessage ); - - /** - * Called by the specified display group before - * it attempts fetch objects. - * @return True to allow the fetch to take place, - * false to prevent the fetch. - */ - boolean displayGroupShouldFetch ( - EODisplayGroup aDisplayGroup ); - - /** - * Called by the specified display group before - * it attempts to insert the specified object. - * @return True to allow the object to be inserted - * false to prevent the insertion. - */ - boolean displayGroupShouldInsertObject ( - EODisplayGroup aDisplayGroup, - Object anObject, - int anIndex ); - - /** - * Called by the specified display group when - * it receives the specified - * ObjectsChangedInEditingContextNotification. - * @return True to allow the display group to - * update the display (recommended), false - * to prevent the update. - */ - boolean displayGroupShouldRedisplay ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ); - - /** - * Called by the specified display group when - * it receives the specified - * InvalidatedAllObjectsInStoreNotification. - * @return True to allow the display group to - * refetch (recommended), false to prevent the - * refetch. - */ - boolean displayGroupShouldRefetch ( - EODisplayGroup aDisplayGroup, - NSNotification aNotification ); - - } + int i; + NSMutableArray result = new NSMutableArray(); + Enumeration e = selectedObjects.objectEnumerator(); + while (e.hasMoreElements()) { + i = indexOf(displayedObjects, e.nextElement()); + if (i != NSArray.NotFound) { + result.addObject(new Integer(i)); + } else { + System.err.println("Should never happen: selected objects not in displayed objects"); + new RuntimeException().printStackTrace(System.err); + } + } + return result; + } + + /** + * Sets the objects managed by this display group. updateDisplayedObjects() is + * called to filter the display objects. The previous selection will be + * maintained if possible. The data source is not notified. + */ + public void setObjectArray(List anObjectList) { + if (anObjectList == null) + anObjectList = new NSArray(); + + Object result = notifyDelegate("displayGroupDisplayArrayForObjects", + new Class[] { EODisplayGroup.class, List.class }, new Object[] { this, anObjectList }); + if (result != null) { + anObjectList = (List) result; + } + + contentsChanged = true; + willChange(); + + NSArray oldSelectedObjects = new NSArray(selectedObjects); // copy + + // reset allObjects to new list + allObjects.removeAllObjects(); + allObjects.addObjectsFromArray(anObjectList); + + // update the displayed object list + updateDisplayedObjects(); + + // restore the selection if possible + selectObjectsIdenticalTo(oldSelectedObjects); + } + + /** + * Sets the currently selected object, or clears the selection if the object is + * not found or is null. Note: it's not clear how this differs from selectObject + * in the spec. It is recommended that you call selectObject for now. + */ + public void setSelectedObject(Object anObject) { + selectObject(anObject); + } + + /** + * Sets the current selection to the specified objects. The previous selection + * is cleared, and any objects in the display group that are in the specified + * list are then selected. If no items in the specified list are found in the + * display group, then the selection is effectively cleared. Note: it's not + * clear how this differs from selectObjectsIdenticalTo in the spec. It is + * recommended that you call that method for now. + */ + public void setSelectedObjects(List aList) { + selectObjectsIdenticalTo(aList); + } + + /** + * Sets the current selection to the objects at the specified indexes. Items in + * the list are assumed to be instances of java.lang.Number. The previous + * selection is cleared, and any objects in the display group that are in the + * specified list are then selected. If no items in the specified list are found + * in the display group, then the selection is effectively cleared. + */ + public boolean setSelectionIndexes(List aList) { + Object o; + int index; + NSMutableArray objects = new NSMutableArray(); + Iterator it = aList.iterator(); + while (it.hasNext()) { + index = ((Number) it.next()).intValue(); + if (index < displayedObjects.count()) { + o = displayedObjects.objectAtIndex(index); + if (o != null) { + objects.add(o); + } + } + } + return selectObjectsIdenticalTo(objects); + } + + /** + * Applies the qualifier to all objects and sorts the results to update the list + * of displayed objects. Observing associations are notified to reflect the + * changes. + */ + public void updateDisplayedObjects() { + contentsChanged = true; + updatedObjectIndex = -1; + willChange(); + + displayedObjects.removeAllObjects(); + + displayedObjects.addObjectsFromArray(allObjects); + + // apply qualifier, if any + if (qualifier() != null) { + EOQualifier.filterArrayWithQualifier(displayedObjects, qualifier()); + } + + // apply sort orderings, if any + NSArray orderings = sortOrderings(); + if (orderings != null) { + if (orderings.count() > 0) { + selectionChanged = true; + willChange(); + EOSortOrdering.sortArrayUsingKeyOrderArray(displayedObjects, orderings); + } + } + + // make sure the selectedObjects is a subset of displayedObjects + int i; + Object o; + Iterator it = new LinkedList(selectedObjects).iterator(); + boolean removeflag = false; + selectedIndexes.removeAllObjects(); + while (it.hasNext()) { + o = it.next(); + if ((i = displayedObjects.indexOfIdenticalObject(o)) == NSArray.NotFound) { + selectedObjects.removeIdenticalObject(o); + removeflag = true; + } else { + selectedIndexes.addObject(new Integer(i)); + } + } + + // Note: it is important to put the + // selectionChanged = true line below remove. + if (removeflag) { + selectionChanged = true; + willChange(); + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { EODisplayGroup.class }, + new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { EODisplayGroup.class }, + new Object[] { this }); + } + } + + /** + * Returns the index of the changed object. If more than one object has changed, + * -1 is returned. + */ + public int updatedObjectIndex() { + return updatedObjectIndex; + } + + // getting and setting values in objects + + /** + * Returns a value on the selected object for the specified key. + */ + public Object selectedObjectValueForKey(String aKey) { + Object selectedObject = selectedObject(); + if (selectedObject == null) + return null; + return valueForObject(selectedObject, aKey); + } + + /** + * Sets the specified value for the specified key on all selected objects. + */ + public boolean setSelectedObjectValue(Object aValue, String aKey) { + Object selectedObject = selectedObject(); + if (selectedObject == null) + return false; + return setValueForObject(aValue, selectedObject, aKey); + } + + /** + * Sets the specified value for the specified key on the specified object. + * Validations may be triggered, and error dialogs may appear to the user. + * + * @return True if the value was set successfully, false if the value could not + * be set and the update operation should not continue. + */ + public boolean setValueForObject(Object aValue, Object anObject, String aKey) { + // notify object's observers: + // this includes us, and will notify our observers + EOObserverCenter.notifyObserversObjectWillChange(anObject); + + // TODO: if key is null, need to remove old object + // and add new object instead of simply replacing it. + + try { + if (anObject instanceof EOKeyValueCoding) { + ((EOKeyValueCoding) anObject).takeValueForKey(aValue, aKey); + } else { + EOKeyValueCodingSupport.takeValueForKey(anObject, aValue, aKey); + } + } catch (RuntimeException exc) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", exc.getMessage() }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + throw exc; + } + return false; + } + + notifyDelegate("displayGroupDidSetValueForObject", + new Class[] { EODisplayGroup.class, Object.class, Object.class, String.class }, + new Object[] { this, aValue, anObject, aKey }); + + return true; + } + + /** + * Calls setValueForObject() for the object at the specified index. + */ + public boolean setValueForObjectAtIndex(Object aValue, int anIndex, String aKey) { + return setValueForObject(aValue, displayedObjects.objectAtIndex(anIndex), aKey); + } + + /** + * Returns the value for the specified key on the specified object. + */ + public Object valueForObject(Object anObject, String aKey) { + // empty string is considered the identity property + if (aKey == null) + return anObject; + if (aKey.equals("")) + return anObject; + + try { + if (anObject instanceof EOKeyValueCoding) { + return ((EOKeyValueCoding) anObject).valueForKey(aKey); + } else { + return EOKeyValueCodingSupport.valueForKey(anObject, aKey); + } + } catch (RuntimeException exc) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", exc.getMessage() }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + throw exc; + } + return null; + } + } + + /** + * Calls valueForObject() for the object at the specified index. + */ + public Object valueForObjectAtIndex(int anIndex, String aKey) { + Object o = displayedObjects.objectAtIndex(anIndex); + return valueForObject(o, aKey); + } + + /** + * Prints out the list of displayed objects. + */ + public String toString() { + return displayedObjects.toString(); + } + + /** + * Handles notifications from the data source's editing context, looking for + * InvalidatedAllObjectsInStoreNotification and + * ObjectsChangedInEditingContextNotification, refetching in the former case and + * updating displayed objects in the latter. Note: This method is not in the + * public specification. + */ + public void objectsInvalidatedInEditingContext(NSNotification aNotification) { + if (EOObjectStore.InvalidatedAllObjectsInStoreNotification.equals(aNotification.name())) { + Object result = notifyDelegate("displayGroupShouldRefetch", + new Class[] { EODisplayGroup.class, NSNotification.class }, new Object[] { this, aNotification }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + fetch(); + } + } else if (EOEditingContext.ObjectsChangedInEditingContextNotification.equals(aNotification.name())) { + Object result = notifyDelegate("displayGroupShouldRedisplay", + new Class[] { EODisplayGroup.class, NSNotification.class }, new Object[] { this, aNotification }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + int index; + Enumeration e; + boolean didChange = false; + NSDictionary userInfo = aNotification.userInfo(); + + // inserts are ignored + + // mark updated objects as updated + NSArray updates = (NSArray) userInfo.objectForKey(EOObjectStore.UpdatedKey); + e = updates.objectEnumerator(); + while (e.hasMoreElements()) { + index = indexOf(displayedObjects, e.nextElement()); + if (index != NSArray.NotFound) { + // System.out.println( "EODisplayGroup: updated: " + index ); + if (!didChange) { + didChange = true; + contentsChanged = true; + willChange(); + updatedObjectIndex = index; + } else { + updatedObjectIndex = -1; + } + } + } + + // treat invalidated objects as updated + NSArray invalidates = (NSArray) userInfo.objectForKey(EOObjectStore.InvalidatedKey); + e = invalidates.objectEnumerator(); + while (e.hasMoreElements()) { + index = indexOf(displayedObjects, e.nextElement()); + if (index != NSArray.NotFound) { + // System.out.println( "EODisplayGroup: invalidated: " + index ); + if (!didChange) { + didChange = true; + contentsChanged = true; + willChange(); + updatedObjectIndex = index; + } else { + updatedObjectIndex = -1; + } + } + } + + // remove deletes from display group if they exist + NSArray deletes = (NSArray) userInfo.objectForKey(EOObjectStore.DeletedKey); + e = deletes.objectEnumerator(); + Object o; + while (e.hasMoreElements()) { + o = e.nextElement(); + index = indexOf(displayedObjects, o); + if (index != NSArray.NotFound) { + // System.out.println( "EODisplayGroup: deleted: " + o ); + deleteObjectAtIndexNoNotify(index); + } + } + + if (!usesOptimisticRefresh()) { + updateDisplayedObjects(); + } + } + } + + } + + // static methods + + /** + * Specifies the default behavior for whether changes should be validated + * immediately for all display groups. + */ + public static boolean globalDefaultForValidatesChangesImmediately() { + return globalDefaultForValidatesChangesImmediately; + } + + /** + * Specifies the default string matching format for all display groups. + */ + public static String globalDefaultStringMatchFormat() { + return globalDefaultStringMatchFormat; + } + + /** + * Specifies the default string matching operator for all display groups. + */ + public static String globalDefaultStringMatchOperator() { + return globalDefaultStringMatchOperator; + } + + /** + * Sets the default behavior for validating changes for all display groups. + */ + public static void setGlobalDefaultForValidatesChangesImmediately(boolean validatesImmediately) { + globalDefaultForValidatesChangesImmediately = validatesImmediately; + } + + /** + * Sets the default string matching format that will be used by all display + * groups. + */ + public static void setGlobalDefaultStringMatchFormat(String aFormat) { + globalDefaultStringMatchFormat = aFormat; + } + + /** + * Sets the default string matching operator that will be used by all display + * groups. + */ + public static void setGlobalDefaultStringMatchOperator(String anOperator) { + globalDefaultStringMatchOperator = anOperator; + } + + /** + * Needed because we don't inherit from NSObject. Calls + * EOObserverCenter.notifyObserversObjectWillChange. + */ + protected void willChange() { + EOObserverCenter.notifyObserversObjectWillChange(this); + } + + /** + * Called by LastGroupObserver to clear flags. + */ + protected void processRecentChanges() { + contentsChanged = false; + selectionChanged = false; + } + + /** + * Returns the index of the specified object in the specified NSArray, comparing + * by value or by reference as determined by the private instance variable + * compareByReference. If not found, returns NSArray.NotFound. + */ + private int indexOf(NSArray anArray, Object anObject) { + if (compareByReference) { + return anArray.indexOfIdenticalObject(anObject); + } else { + return anArray.indexOf(anObject); + } + } + + // interface EOObserving + + /** + * Receives notifications of changes from objects that are managed by this + * display group. This implementation sets updatedObjectIndex and + * contentsChanged as appropriate. + */ + public void objectWillChange(Object anObject) { + int index = indexOf(displayedObjects, anObject); + if (index != NSArray.NotFound) { + updatedObjectIndex = index; + contentsChanged = true; + willChange(); + } + } + + // interface EOEditingContext.Editor + + /** + * Called before the editing context begins to save changes. This implementation + * calls endEditing(). + */ + public void editingContextWillSaveChanges(EOEditingContext anEditingContext) { + endEditing(); + } + + /** + * Called to determine whether this editor has changes that have not been + * committed to the object in the context. + */ + public boolean editorHasChangesForEditingContext(EOEditingContext anEditingContext) { + return (editingAssociation() != null); + } + + // interface EOEditingContext.MessageHandler + + /** + * Called to display a message for an error that occurred in the specified + * editing context. If the delegate allows, this implementation presents an + * informational JOptionPane. Override to customize. + */ + public void editingContextPresentErrorMessage(EOEditingContext anEditingContext, String aMessage) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { EODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", aMessage }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + JOptionPane.showMessageDialog(null, 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. This implementation presents an + * JOptionPane allowing the user to specify whether to continue. Override to + * customize. + */ + public boolean editingContextShouldContinueFetching(EOEditingContext anEditingContext, int count, int limit, + EOObjectStore anObjectStore) { + return (JOptionPane.showConfirmDialog(null, "Fetch limit reached: do you wish to continue?", "Continue?", + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION); + } + + /** + * 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; + } + + /** + * DisplayGroups can delegate important decisions to a Delegate. Note that + * DisplayGroup doesn't require its delegates to implement this interface: + * rather, this interface defines the methods that DisplayGroup 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 when the specified data source fails to create an object for the + * specified display group. + */ + void displayGroupCreateObjectFailed(EODisplayGroup aDisplayGroup, EODataSource aDataSource); + + /** + * Called after the specified display group's data source is changed. + */ + void displayGroupDidChangeDataSource(EODisplayGroup aDisplayGroup); + + /** + * Called after a change occurs in the specified display group's selected + * objects. + */ + void displayGroupDidChangeSelectedObjects(EODisplayGroup aDisplayGroup); + + /** + * Called after the specified display group's selection has changed. + */ + void displayGroupDidChangeSelection(EODisplayGroup aDisplayGroup); + + /** + * Called after the specified display group has deleted the specified object. + */ + void displayGroupDidDeleteObject(EODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called after the specified display group has fetched the specified object + * list. + */ + void displayGroupDidFetchObjects(EODisplayGroup aDisplayGroup, List anObjectList); + + /** + * Called after the specified display group has inserted the specified object + * into its internal object list. + */ + void displayGroupDidInsertObject(EODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called after the specified display group has set the specified value for the + * specified object and key. + */ + void displayGroupDidSetValueForObject(EODisplayGroup aDisplayGroup, Object aValue, Object anObject, + String aKey); + + /** + * Called by the specified display group to determine what objects should be + * displayed for the objects in the specified list. + * + * @return An NSArray containing the objects to be displayed for the objects in + * the specified list. + */ + NSArray displayGroupDisplayArrayForObjects(EODisplayGroup aDisplayGroup, List aList); + + /** + * Called by the specified display group before it attempts to change the + * selection. + * + * @return True to allow the selection to change, false otherwise. + */ + boolean displayGroupShouldChangeSelection(EODisplayGroup aDisplayGroup, List aSelectionList); + + /** + * Called by the specified display group before it attempts to delete the + * specified object. + * + * @return True to allow the object to be deleted false to prevent the deletion. + */ + boolean displayGroupShouldDeleteObject(EODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called by the specified display group before it attempts display the + * specified alert to the user. + * + * @return True to allow the message to be displayed, false if you want to + * handle the alert yourself and suppress the display group's + * notification. + */ + boolean displayGroupShouldDisplayAlert(EODisplayGroup aDisplayGroup, String aTitle, String aMessage); + + /** + * Called by the specified display group before it attempts fetch objects. + * + * @return True to allow the fetch to take place, false to prevent the fetch. + */ + boolean displayGroupShouldFetch(EODisplayGroup aDisplayGroup); + + /** + * Called by the specified display group before it attempts to insert the + * specified object. + * + * @return True to allow the object to be inserted false to prevent the + * insertion. + */ + boolean displayGroupShouldInsertObject(EODisplayGroup aDisplayGroup, Object anObject, int anIndex); + + /** + * Called by the specified display group when it receives the specified + * ObjectsChangedInEditingContextNotification. + * + * @return True to allow the display group to update the display (recommended), + * false to prevent the update. + */ + boolean displayGroupShouldRedisplay(EODisplayGroup aDisplayGroup, NSNotification aNotification); + + /** + * Called by the specified display group when it receives the specified + * InvalidatedAllObjectsInStoreNotification. + * + * @return True to allow the display group to refetch (recommended), false to + * prevent the refetch. + */ + boolean displayGroupShouldRefetch(EODisplayGroup aDisplayGroup, NSNotification aNotification); + + } } - /** - * A private class that will serve to clear the contentsChanged - * and selectionChanged flags after all Associations have been - * notified. - */ - class LastGroupObserver extends EODelayedObserver - { - Reference ref; - - public LastGroupObserver( EODisplayGroup aDisplayGroup ) - { - ref = new WeakReference( aDisplayGroup ); - } - - /** - * We want to be informed last, after all Associations - * have been notified to changes in the DisplayGroup. - */ - public int priority() - { - return ObserverPrioritySixth; - } - - /** - * After all Associations have been notified, - * clear the contentsChanged and selectionChanged flags. - */ - public void subjectChanged () - { - EODisplayGroup group = (EODisplayGroup) ref.get(); - if ( group != null ) - { - group.processRecentChanges(); - } - } - } +/** + * A private class that will serve to clear the contentsChanged and + * selectionChanged flags after all Associations have been notified. + */ +class LastGroupObserver extends EODelayedObserver { + Reference ref; + + public LastGroupObserver(EODisplayGroup aDisplayGroup) { + ref = new WeakReference(aDisplayGroup); + } + + /** + * We want to be informed last, after all Associations have been notified to + * changes in the DisplayGroup. + */ + public int priority() { + return ObserverPrioritySixth; + } + + /** + * After all Associations have been notified, clear the contentsChanged and + * selectionChanged flags. + */ + public void subjectChanged() { + EODisplayGroup group = (EODisplayGroup) ref.get(); + if (group != null) { + group.processRecentChanges(); + } + } +} /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.51 2004/01/28 18:35:40 mpowers - * Slight optimization: only comparing list in fetch if we have to. + * Revision 1.51 2004/01/28 18:35:40 mpowers Slight optimization: only comparing + * list in fetch if we have to. * - * Revision 1.50 2004/01/27 20:42:30 mpowers - * No longer reselecting first after fetch if contents are identical. + * Revision 1.50 2004/01/27 20:42:30 mpowers No longer reselecting first after + * fetch if contents are identical. * - * Revision 1.49 2003/12/18 11:37:45 mpowers - * Now calling qualifier internally. + * Revision 1.49 2003/12/18 11:37:45 mpowers Now calling qualifier internally. * - * Revision 1.48 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.48 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.47 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.47 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.46 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.46 2002/10/24 21:15:36 mpowers New implementations of NSArray and + * subclasses. * - * Revision 1.45 2002/10/24 18:20:20 mpowers - * Because NSArray is read-only, we are returning our internal representations - * to callers of allObjects(), displayedObjects(), and selectedObjects(). + * Revision 1.45 2002/10/24 18:20:20 mpowers Because NSArray is read-only, we + * are returning our internal representations to callers of allObjects(), + * displayedObjects(), and selectedObjects(). * - * Revision 1.44 2002/08/06 18:20:25 mpowers - * Now posting DisplayGroupWillFetch notifications before fetch. - * Implemented support for usesOptimisticRefresh. - * No longer supporting inserted/updated/deleted lists: not part of spec. + * Revision 1.44 2002/08/06 18:20:25 mpowers Now posting DisplayGroupWillFetch + * notifications before fetch. Implemented support for usesOptimisticRefresh. No + * longer supporting inserted/updated/deleted lists: not part of spec. * - * Revision 1.43 2002/05/17 15:01:49 mpowers - * Implemented dynamic lookup of delegate methods so delegates no longer - * need to implement the DisplayGroup.Delegate interface. + * Revision 1.43 2002/05/17 15:01:49 mpowers Implemented dynamic lookup of + * delegate methods so delegates no longer need to implement the + * DisplayGroup.Delegate interface. * - * Revision 1.42 2002/03/26 21:46:06 mpowers - * Contributing EditingContext as a java-friendly convenience. + * Revision 1.42 2002/03/26 21:46:06 mpowers Contributing EditingContext as a + * java-friendly convenience. * - * Revision 1.41 2002/03/11 03:17:56 mpowers - * Provided control point for coalesced changes. + * Revision 1.41 2002/03/11 03:17:56 mpowers Provided control point for + * coalesced changes. * - * Revision 1.40 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.40 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.39 2002/02/19 22:26:04 mpowers - * Implemented EOEditingContext.MessageHandler support. + * Revision 1.39 2002/02/19 22:26:04 mpowers Implemented + * EOEditingContext.MessageHandler support. * - * Revision 1.38 2002/02/19 16:37:38 mpowers - * Implemented support for EOEditingContext.Editor + * Revision 1.38 2002/02/19 16:37:38 mpowers Implemented support for + * EOEditingContext.Editor * - * Revision 1.37 2001/12/11 22:17:48 mpowers - * Now properly handling exceptions in valueForObject. - * No longer trying to retain selection based only on index. + * Revision 1.37 2001/12/11 22:17:48 mpowers Now properly handling exceptions in + * valueForObject. No longer trying to retain selection based only on index. * - * Revision 1.36 2001/11/08 21:42:00 mpowers - * Now we know what to do with shouldRefetch and shouldRedisplay. + * Revision 1.36 2001/11/08 21:42:00 mpowers Now we know what to do with + * shouldRefetch and shouldRedisplay. * - * Revision 1.35 2001/11/04 18:26:58 mpowers - * Fixed bug where exceptions were not properly reported when updating - * a value and the display group did not have a delegate. + * Revision 1.35 2001/11/04 18:26:58 mpowers Fixed bug where exceptions were not + * properly reported when updating a value and the display group did not have a + * delegate. * - * Revision 1.34 2001/11/02 20:59:36 mpowers - * Now correctly ensuring selected objects are a subset of displayed objects. + * Revision 1.34 2001/11/02 20:59:36 mpowers Now correctly ensuring selected + * objects are a subset of displayed objects. * - * Revision 1.33 2001/10/30 22:56:45 mpowers - * Added support for EOQualifier. + * Revision 1.33 2001/10/30 22:56:45 mpowers Added support for EOQualifier. * - * Revision 1.32 2001/10/23 22:27:53 mpowers - * Now running at ObserverPrioritySixth. + * Revision 1.32 2001/10/23 22:27:53 mpowers Now running at + * ObserverPrioritySixth. * - * Revision 1.31 2001/10/23 18:45:05 mpowers - * Rolling back changes. + * Revision 1.31 2001/10/23 18:45:05 mpowers Rolling back changes. * - * Revision 1.28 2001/08/22 19:23:41 mpowers - * No longer asserting objects in all objects list. + * Revision 1.28 2001/08/22 19:23:41 mpowers No longer asserting objects in all + * objects list. * - * Revision 1.27 2001/07/30 16:17:01 mpowers - * Minor code cleanup. + * Revision 1.27 2001/07/30 16:17:01 mpowers Minor code cleanup. * - * Revision 1.26 2001/07/10 22:49:07 mpowers - * Fixed bug in optimization for selectObjectsIdenticalTo (found by Dongzhi). + * Revision 1.26 2001/07/10 22:49:07 mpowers Fixed bug in optimization for + * selectObjectsIdenticalTo (found by Dongzhi). * - * Revision 1.25 2001/06/19 15:40:21 mpowers - * Now only changing the selection if the new selection is different - * from the old. + * Revision 1.25 2001/06/19 15:40:21 mpowers Now only changing the selection if + * the new selection is different from the old. * - * Revision 1.24 2001/05/24 17:36:15 mpowers - * Fixed problem with selectedObjectsIdenticalTo: it was using compare - * by value instead of compare by reference. + * Revision 1.24 2001/05/24 17:36:15 mpowers Fixed problem with + * selectedObjectsIdenticalTo: it was using compare by value instead of compare + * by reference. * - * Revision 1.23 2001/05/18 21:09:19 mpowers - * Now throwing exceptions if the delegate cannot handle error from update. + * Revision 1.23 2001/05/18 21:09:19 mpowers Now throwing exceptions if the + * delegate cannot handle error from update. * - * Revision 1.22 2001/05/14 15:26:12 mpowers - * Now checking for null delegate before and after selection change. + * Revision 1.22 2001/05/14 15:26:12 mpowers Now checking for null delegate + * before and after selection change. * - * Revision 1.21 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.21 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.20 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. + * Revision 1.20 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. * - * Revision 1.19 2001/04/13 16:38:09 mpowers - * Alpha3 release. + * Revision 1.19 2001/04/13 16:38:09 mpowers Alpha3 release. * - * Revision 1.18 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.18 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.17 2001/03/29 03:31:13 mpowers - * No longer using Introspector. + * Revision 1.17 2001/03/29 03:31:13 mpowers No longer using Introspector. * - * Revision 1.16 2001/02/27 03:32:18 mpowers - * Implemented default values for new objects. + * Revision 1.16 2001/02/27 03:32:18 mpowers Implemented default values for new + * objects. * - * Revision 1.15 2001/02/27 02:11:17 mpowers - * Now throwing exception when cloning fails. - * Removed debugging printlns. + * Revision 1.15 2001/02/27 02:11:17 mpowers Now throwing exception when cloning + * fails. Removed debugging printlns. * - * Revision 1.14 2001/02/26 22:41:51 mpowers - * Implemented null placeholder classes. - * Duplicator now uses NSNull. - * No longer catching base exception class. + * Revision 1.14 2001/02/26 22:41:51 mpowers Implemented null placeholder + * classes. Duplicator now uses NSNull. No longer catching base exception class. * - * Revision 1.13 2001/02/26 15:53:22 mpowers - * Fine-tuning notification firing. + * Revision 1.13 2001/02/26 15:53:22 mpowers Fine-tuning notification firing. * Child display groups now update properly after parent save or invalidate. * - * Revision 1.12 2001/02/22 20:55:06 mpowers - * Implemented notification handling. + * Revision 1.12 2001/02/22 20:55:06 mpowers Implemented notification handling. * - * Revision 1.11 2001/02/21 20:40:42 mpowers - * setObjectArray now falls back to index when trying to retain the - * same selection. + * Revision 1.11 2001/02/21 20:40:42 mpowers setObjectArray now falls back to + * index when trying to retain the same selection. * - * Revision 1.10 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.10 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.9 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.9 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.8 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.8 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.7 2001/01/24 16:35:37 mpowers - * Improved documentation on TreeAssociation. - * SortOrderings are now inherited from parent nodes. - * Updates after sorting are still lost on TreeController. + * Revision 1.7 2001/01/24 16:35:37 mpowers Improved documentation on + * TreeAssociation. SortOrderings are now inherited from parent nodes. Updates + * after sorting are still lost on TreeController. * - * Revision 1.6 2001/01/24 14:23:05 mpowers - * Added support for OrderedDataSource. + * Revision 1.6 2001/01/24 14:23:05 mpowers Added support for OrderedDataSource. * - * Revision 1.5 2001/01/12 17:21:37 mpowers - * Implicit creation of EOSortOrderings now happens in setSortOrderings. + * Revision 1.5 2001/01/12 17:21:37 mpowers Implicit creation of EOSortOrderings + * now happens in setSortOrderings. * - * Revision 1.4 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.4 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.3 2001/01/10 22:49:44 mpowers - * Implemented similarly named selection methods instead of - * throwing exceptions. + * Revision 1.3 2001/01/10 22:49:44 mpowers Implemented similarly named + * selection methods instead of throwing exceptions. * - * Revision 1.2 2001/01/09 20:12:52 mpowers - * Moved inner classes to package access. + * Revision 1.2 2001/01/09 20:12:52 mpowers Moved inner classes to package + * access. * - * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers Contributing wotonomy. * - * Revision 1.21 2000/12/20 16:25:39 michael - * Added log to all files. + * Revision 1.21 2000/12/20 16:25:39 michael Added log to all files. * - * Revision 1.20 2000/12/15 15:04:42 michael - * Added doc. + * Revision 1.20 2000/12/15 15:04:42 michael Added doc. * - * Revision 1.19 2000/12/11 13:32:48 michael - * Finish the much better TreeAssociation implementation. - * TreeAssociation now has no gui dependencies. + * Revision 1.19 2000/12/11 13:32:48 michael Finish the much better + * TreeAssociation implementation. TreeAssociation now has no gui dependencies. * - * Revision 1.18 2000/12/05 17:41:46 michael - * Broadcasts selection change after delegate refuses selection change - * so the initiating association gets refreshed. + * Revision 1.18 2000/12/05 17:41:46 michael Broadcasts selection change after + * delegate refuses selection change so the initiating association gets + * refreshed. * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java index 3a4ff08..3310f19 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/GenericAssociation.java @@ -28,346 +28,282 @@ import net.wotonomy.control.EOObserverCenter; import net.wotonomy.foundation.NSArray; /** -* GenericAssociation binds one or more properties on an -* observable object to a display group. The controlled -* object is expected to use the ObserverCenter and will -* be observed by this association.

-* -* Bindings for this association are generic: the -* name of the aspect will be treated as a property key -* on the displayed object(s) and synchronized with the -* bound property on the controlled object.

-* -* NOTE: because we cannot assume that the controlled -* object will retain a reference to this association, -* you must explicitly retain a reference to prevent -* the association from getting garbage collected. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class GenericAssociation extends EOAssociation -{ - protected boolean objectModified; - protected Set aspectsModified; - - static final NSArray aspects = - new NSArray( new Object[] { - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - } ); - - /** - * Constructor specifying the object to be controlled by this - * association. Does not establish connection. - */ - public GenericAssociation ( Object anObject ) - { - super( anObject ); - objectModified = false; - aspectsModified = new HashSet(); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return true; - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ""; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return true; - } - - /** - * Establishes a connection between this association - * and the controlled object. This implementation - * registers with ObserverCenter for change notifications - * from the controlled object. - */ - public void establishConnection () - { - EOObserverCenter.addObserver( this, object() ); - super.establishConnection(); - + * GenericAssociation binds one or more properties on an observable object to a + * display group. The controlled object is expected to use the ObserverCenter + * and will be observed by this association.
+ *
+ * + * Bindings for this association are generic: the name of the aspect will + * be treated as a property key on the displayed object(s) and synchronized with + * the bound property on the controlled object.
+ *
+ * + * NOTE: because we cannot assume that the controlled object will retain a + * reference to this association, you must explicitly retain a reference to + * prevent the association from getting garbage collected. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class GenericAssociation extends EOAssociation { + protected boolean objectModified; + protected Set aspectsModified; + + static final NSArray aspects = new NSArray(new Object[] {}); + static final NSArray aspectSignatures = new NSArray(new Object[] {}); + static final NSArray objectKeysTaken = new NSArray(new Object[] {}); + + /** + * Constructor specifying the object to be controlled by this association. Does + * not establish connection. + */ + public GenericAssociation(Object anObject) { + super(anObject); + objectModified = false; + aspectsModified = new HashSet(); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return true; + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ""; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return true; + } + + /** + * Establishes a connection between this association and the controlled object. + * This implementation registers with ObserverCenter for change notifications + * from the controlled object. + */ + public void establishConnection() { + EOObserverCenter.addObserver(this, object()); + super.establishConnection(); + // forces update from bindings subjectChanged(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - EOObserverCenter.removeObserver( this, object() ); - super.breakConnection(); - } - - /** - * Overridden to track which observed object is changing. - */ - public void objectWillChange( Object anObject ) - { - if ( object() == anObject ) - { - objectModified = true; - } - else - { - aspectsModified.add( aspectToGroup.allKeysForObject( anObject ) ); - } - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { - String aspect; - String key; - Object value; - EODisplayGroup displayGroup; - Iterator iterator; - - iterator = aspectsModified.iterator(); - while ( iterator.hasNext() ) - { - aspect = (String) iterator.next(); - key = displayGroupKeyForAspect( aspect ); - displayGroup = displayGroupForAspect( aspect ); - - value = readValueFromDisplayGroupForKey( displayGroup, key ); - writeValueForKey( object(), value, key ); - } - aspectsModified.clear(); - - if ( objectModified ) - { - iterator = aspectToGroup.keySet().iterator(); - while ( iterator.hasNext() ) - { - aspect = (String) iterator.next(); - key = displayGroupKeyForAspect( aspect ); - value = readValueForKey( object(), key ); - displayGroup = displayGroupForAspect( aspect ); - displayGroup.setSelectedObjectValue( value, key ); - } - } - } - - protected Object readValueForKey( Object object, String key ) - { - if ( object instanceof EOKeyValueCoding ) - { - return ((EOKeyValueCoding)object).valueForKey( key ); - } - return EOKeyValueCodingSupport.valueForKey( object, key ); - } - - protected void writeValueForKey( Object object, Object value, String key ) - { - if ( object instanceof EOKeyValueCoding ) - { - ((EOKeyValueCoding)object).takeValueForKey( value, key ); - } - else - { - EOKeyValueCodingSupport.takeValueForKey( object, value, key ); - } - } - - protected Object readValueFromDisplayGroupForKey( - EODisplayGroup displayGroup, String key ) - { - Object value; - - if ( displayGroup.selectedObjects().size() > 1 ) - { - // if there're more than one object selected, set - // the value to blank for all of them. - Object previousValue; - - Iterator indexIterator = displayGroup.selectionIndexes(). - iterator(); - - // get value for the first selected object. - int initialIndex = ( (Integer)indexIterator.next() ).intValue(); - previousValue = displayGroup.valueForObjectAtIndex( - initialIndex, key ); - value = null; - - // go through the rest of the selected objects, compare each - // value with the previous one. continue comparing if two - // values are equal, break the while loop if they're different. - // the final value will be the common value of all selected objects - // if there is one, or be blank if there is not. - while ( indexIterator.hasNext() ) - { - int index = ( (Integer)indexIterator.next() ).intValue(); - Object currentValue = displayGroup.valueForObjectAtIndex( - index, key ); - if ( currentValue != null && !currentValue.equals( previousValue ) ) - { - value = null; - break; - } - else - { - // currentValue is the same as the previous one - value = currentValue; - } - - } // end while - - } else { - - value = displayGroup.selectedObjectValueForKey( key ); - } // end checking size of displayGroup - - return value; - } - + } + /** - * Writes the value currently in the component - * to the selected object in the display group - * bound to the value aspect. - * @return false if there were problems validating, - * or true to continue. - */ - protected boolean writeValueForAspect( Object value, String aspect ) - { - EODisplayGroup displayGroup = - displayGroupForAspect( aspect ); - if ( displayGroup != null ) - { - String key = displayGroupKeyForAspect( aspect ); - - boolean returnValue = true; - Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); - while ( selectedIterator.hasNext() ) - { - int index = ( (Integer)selectedIterator.next() ).intValue(); - - if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) ) - { - returnValue = false; - } - } - return returnValue; + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + EOObserverCenter.removeObserver(this, object()); + super.breakConnection(); + } + + /** + * Overridden to track which observed object is changing. + */ + public void objectWillChange(Object anObject) { + if (object() == anObject) { + objectModified = true; + } else { + aspectsModified.add(aspectToGroup.allKeysForObject(anObject)); + } + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { + String aspect; + String key; + Object value; + EODisplayGroup displayGroup; + Iterator iterator; + + iterator = aspectsModified.iterator(); + while (iterator.hasNext()) { + aspect = (String) iterator.next(); + key = displayGroupKeyForAspect(aspect); + displayGroup = displayGroupForAspect(aspect); + + value = readValueFromDisplayGroupForKey(displayGroup, key); + writeValueForKey(object(), value, key); + } + aspectsModified.clear(); + + if (objectModified) { + iterator = aspectToGroup.keySet().iterator(); + while (iterator.hasNext()) { + aspect = (String) iterator.next(); + key = displayGroupKeyForAspect(aspect); + value = readValueForKey(object(), key); + displayGroup = displayGroupForAspect(aspect); + displayGroup.setSelectedObjectValue(value, key); + } + } + } + + protected Object readValueForKey(Object object, String key) { + if (object instanceof EOKeyValueCoding) { + return ((EOKeyValueCoding) object).valueForKey(key); + } + return EOKeyValueCodingSupport.valueForKey(object, key); + } + + protected void writeValueForKey(Object object, Object value, String key) { + if (object instanceof EOKeyValueCoding) { + ((EOKeyValueCoding) object).takeValueForKey(value, key); + } else { + EOKeyValueCodingSupport.takeValueForKey(object, value, key); + } + } + + protected Object readValueFromDisplayGroupForKey(EODisplayGroup displayGroup, String key) { + Object value; + + if (displayGroup.selectedObjects().size() > 1) { + // if there're more than one object selected, set + // the value to blank for all of them. + Object previousValue; + + Iterator indexIterator = displayGroup.selectionIndexes().iterator(); + + // get value for the first selected object. + int initialIndex = ((Integer) indexIterator.next()).intValue(); + previousValue = displayGroup.valueForObjectAtIndex(initialIndex, key); + value = null; + + // go through the rest of the selected objects, compare each + // value with the previous one. continue comparing if two + // values are equal, break the while loop if they're different. + // the final value will be the common value of all selected objects + // if there is one, or be blank if there is not. + while (indexIterator.hasNext()) { + int index = ((Integer) indexIterator.next()).intValue(); + Object currentValue = displayGroup.valueForObjectAtIndex(index, key); + if (currentValue != null && !currentValue.equals(previousValue)) { + value = null; + break; + } else { + // currentValue is the same as the previous one + value = currentValue; + } + + } // end while + + } else { + + value = displayGroup.selectedObjectValueForKey(key); + } // end checking size of displayGroup + + return value; + } + + /** + * Writes the value currently in the component to the selected object in the + * display group bound to the value aspect. + * + * @return false if there were problems validating, or true to continue. + */ + protected boolean writeValueForAspect(Object value, String aspect) { + EODisplayGroup displayGroup = displayGroupForAspect(aspect); + if (displayGroup != null) { + String key = displayGroupKeyForAspect(aspect); + + boolean returnValue = true; + Iterator selectedIterator = displayGroup.selectionIndexes().iterator(); + while (selectedIterator.hasNext()) { + int index = ((Integer) selectedIterator.next()).intValue(); + + if (!displayGroup.setValueForObjectAtIndex(value, index, key)) { + returnValue = false; + } + } + return returnValue; } return false; } - /** - * Forces this association to cause the object to - * stop editing and validate the user's input. - * @return false if there were problems validating, - * or true to continue. - */ - public boolean endEditing () - { - return false; + /** + * Forces this association to cause the object to stop editing and validate the + * user's input. + * + * @return false if there were problems validating, or true to continue. + */ + public boolean endEditing() { + return false; //! return writeValueToDisplayGroup(); - } - + } + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2001/11/08 19:51:24 mpowers - * Draft implementation. + * Revision 1.2 2001/11/08 19:51:24 mpowers Draft implementation. * - * Revision 1.1 2001/11/02 23:15:04 mpowers - * Contributing "generic association". + * Revision 1.1 2001/11/02 23:15:04 mpowers Contributing "generic association". * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java index 2aea8d3..54cd04e 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MasterDetailAssociation.java @@ -27,380 +27,303 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSMutableArray; /** -* MasterDetailAssociation binds a display group to a property -* on the selected object of another display group. -* Bindings are: -*
    -*
  • parent: The property on the selected object of the -* bound display group that is expected to be an indexed property.
  • -*
-* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 904 $ -*/ -public class MasterDetailAssociation extends EOAssociation -{ - static final NSArray aspects = - new NSArray( new Object[] { - ParentAspect - } ); - static final NSArray aspectSignatures = - new NSArray( new Object[] { - AttributeToOneAspectSignature - } ); - static final NSArray objectKeysTaken = - new NSArray( new Object[] { - "allObjects" - } ); - - /** - * Used to be notified of changes to objects in the - * controlled display group. requalify() should place - * all objects fetched into the controlled group into - * this array. - * Otherwise, the parent object is only marked as - * changed for inserts and deletes. - */ - protected NSMutableArray observableArray; - - /** - * Constructor expecting an EODisplayGroup. - * If the controlled display group does not have a data source, - * a new PropertyDataSource will be used. - */ - public MasterDetailAssociation ( Object anObject ) - { - super( anObject ); - observableArray = new ObservableArray( this ); - } - - /** - * Returns a List of aspect signatures whose contents - * correspond with the aspects list. Each element is - * a string whose characters represent a capability of - * the corresponding aspect.
    - *
  • "A" attribute: the aspect can be bound to - * an attribute.
  • - *
  • "1" to-one: the aspect can be bound to a - * property that returns a single object.
  • - *
  • "M" to-one: the aspect can be bound to a - * property that returns multiple objects.
  • - *
- * An empty signature "" means that the aspect can - * bind without needing a key. - * This implementation returns "A1M" for each - * element in the aspects array. - */ - public static NSArray aspectSignatures () - { - return aspectSignatures; - } - - /** - * Returns a List that describes the aspects supported - * by this class. Each element in the list is the string - * name of the aspect. This implementation returns an - * empty list. - */ - public static NSArray aspects () - { - return aspects; - } - - /** - * Returns a List of EOAssociation subclasses that, - * for the objects that are usable for this association, - * are less suitable than this association. - */ - public static NSArray associationClassesSuperseded () - { - return new NSArray(); - } - - /** - * Returns whether this class can control the specified - * object. - */ - public static boolean isUsableWithObject ( Object anObject ) - { - return ( anObject instanceof EODisplayGroup ); - } - - /** - * Returns a List of properties of the controlled object - * that are controlled by this class. For example, - * "stringValue", or "selected". - */ - public static NSArray objectKeysTaken () - { - return objectKeysTaken; - } - - /** - * Returns the aspect that is considered primary - * or default. This is typically "value" or somesuch. - */ - public static String primaryAspect () - { - return ParentAspect; - } - - /** - * Returns whether this association can bind to the - * specified display group on the specified key for - * the specified aspect. - */ - public boolean canBindAspect ( - String anAspect, EODisplayGroup aDisplayGroup, String aKey) - { - return ( aspects.containsObject( anAspect ) ); - } - - /** - * Establishes a connection between this association - * and the controlled object. Subclasses should begin - * listening for events from their controlled object here. - */ - public void establishConnection () - { - //NOTE: if nothing refers to this assocation, it gets gc'd. - // otherwise, this is not needed. - component().addObserver( this ); - - EODisplayGroup displayGroup = - displayGroupForAspect( ParentAspect ); - String key = - displayGroupKeyForAspect( ParentAspect ); - - // obtain and qualify new data source from existing source if necessary - if ( component().dataSource() == null ) - { - if ( ( displayGroup != null ) && ( displayGroup.dataSource() != null ) ) - { - component().setDataSource( - displayGroup.dataSource(). - dataSourceQualifiedByKey( key ) ); - } - } - - // set up proxy data source if necessary - if ( component().dataSource() == null ) - { - // get context and class desc from master group - EOEditingContext editingContext = null; - EOClassDescription classDesc = null; - if ( displayGroup != null ) - { - EODataSource dataSource = displayGroup.dataSource(); - if ( dataSource != null ) - { - editingContext = dataSource.editingContext(); - EOClassDescription parentDesc = dataSource.classDescriptionForObjects(); - if ( parentDesc != null ) - { - classDesc = parentDesc.classDescriptionForDestinationKey( key ); - } - } - } - - //FIXME: should this be called DetailDataSource? - component().setDataSource( - new PropertyDataSource( editingContext, classDesc ) ); - } - - super.establishConnection(); + * MasterDetailAssociation binds a display group to a property on the selected + * object of another display group. Bindings are: + *
    + *
  • parent: The property on the selected object of the bound display group + * that is expected to be an indexed property.
  • + *
+ * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 904 $ + */ +public class MasterDetailAssociation extends EOAssociation { + static final NSArray aspects = new NSArray(new Object[] { ParentAspect }); + static final NSArray aspectSignatures = new NSArray(new Object[] { AttributeToOneAspectSignature }); + static final NSArray objectKeysTaken = new NSArray(new Object[] { "allObjects" }); + + /** + * Used to be notified of changes to objects in the controlled display group. + * requalify() should place all objects fetched into the controlled group into + * this array. Otherwise, the parent object is only marked as changed for + * inserts and deletes. + */ + protected NSMutableArray observableArray; + + /** + * Constructor expecting an EODisplayGroup. If the controlled display group does + * not have a data source, a new PropertyDataSource will be used. + */ + public MasterDetailAssociation(Object anObject) { + super(anObject); + observableArray = new ObservableArray(this); + } + + /** + * Returns a List of aspect signatures whose contents correspond with the + * aspects list. Each element is a string whose characters represent a + * capability of the corresponding aspect. + *
    + *
  • "A" attribute: the aspect can be bound to an attribute.
  • + *
  • "1" to-one: the aspect can be bound to a property that returns a single + * object.
  • + *
  • "M" to-one: the aspect can be bound to a property that returns multiple + * objects.
  • + *
+ * An empty signature "" means that the aspect can bind without needing a key. + * This implementation returns "A1M" for each element in the aspects array. + */ + public static NSArray aspectSignatures() { + return aspectSignatures; + } + + /** + * Returns a List that describes the aspects supported by this class. Each + * element in the list is the string name of the aspect. This implementation + * returns an empty list. + */ + public static NSArray aspects() { + return aspects; + } + + /** + * Returns a List of EOAssociation subclasses that, for the objects that are + * usable for this association, are less suitable than this association. + */ + public static NSArray associationClassesSuperseded() { + return new NSArray(); + } + + /** + * Returns whether this class can control the specified object. + */ + public static boolean isUsableWithObject(Object anObject) { + return (anObject instanceof EODisplayGroup); + } + + /** + * Returns a List of properties of the controlled object that are controlled by + * this class. For example, "stringValue", or "selected". + */ + public static NSArray objectKeysTaken() { + return objectKeysTaken; + } + + /** + * Returns the aspect that is considered primary or default. This is typically + * "value" or somesuch. + */ + public static String primaryAspect() { + return ParentAspect; + } + + /** + * Returns whether this association can bind to the specified display group on + * the specified key for the specified aspect. + */ + public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { + return (aspects.containsObject(anAspect)); + } + + /** + * Establishes a connection between this association and the controlled object. + * Subclasses should begin listening for events from their controlled object + * here. + */ + public void establishConnection() { + // NOTE: if nothing refers to this assocation, it gets gc'd. + // otherwise, this is not needed. + component().addObserver(this); + + EODisplayGroup displayGroup = displayGroupForAspect(ParentAspect); + String key = displayGroupKeyForAspect(ParentAspect); + + // obtain and qualify new data source from existing source if necessary + if (component().dataSource() == null) { + if ((displayGroup != null) && (displayGroup.dataSource() != null)) { + component().setDataSource(displayGroup.dataSource().dataSourceQualifiedByKey(key)); + } + } + + // set up proxy data source if necessary + if (component().dataSource() == null) { + // get context and class desc from master group + EOEditingContext editingContext = null; + EOClassDescription classDesc = null; + if (displayGroup != null) { + EODataSource dataSource = displayGroup.dataSource(); + if (dataSource != null) { + editingContext = dataSource.editingContext(); + EOClassDescription parentDesc = dataSource.classDescriptionForObjects(); + if (parentDesc != null) { + classDesc = parentDesc.classDescriptionForDestinationKey(key); + } + } + } + + // FIXME: should this be called DetailDataSource? + component().setDataSource(new PropertyDataSource(editingContext, classDesc)); + } + + super.establishConnection(); requalify(); - } - - /** - * Breaks the connection between this association and - * its object. Override to stop listening for events - * from the object. - */ - public void breakConnection () - { - //NOTE: if nothing refers to this assocation, it gets gc'd. - // otherwise, this is not needed. - component().deleteObserver( this ); - - super.breakConnection(); - } - - /** - * Called when either the selection or the contents - * of an associated display group have changed. - */ - public void subjectChanged () - { + } + + /** + * Breaks the connection between this association and its object. Override to + * stop listening for events from the object. + */ + public void breakConnection() { + // NOTE: if nothing refers to this assocation, it gets gc'd. + // otherwise, this is not needed. + component().deleteObserver(this); + + super.breakConnection(); + } + + /** + * Called when either the selection or the contents of an associated display + * group have changed. + */ + public void subjectChanged() { EODisplayGroup displayGroup; // parent aspect - displayGroup = displayGroupForAspect( ParentAspect ); - if ( displayGroup != null ) - { - if ( displayGroup.selectionChanged() ) - { - requalify(); + displayGroup = displayGroupForAspect(ParentAspect); + if (displayGroup != null) { + if (displayGroup.selectionChanged()) { + requalify(); + } else if (displayGroup.contentsChanged()) { + requalify(); } - else - if ( displayGroup.contentsChanged() ) - { - requalify(); + } + } + + /** + * Overridden to intercept notifications of changes to objects in the controlled + * display group and broadcast a change on the parent group's selected object. + * All other notifications are passed to the super implementation. + */ + public void objectWillChange(Object anObject) { + // if child display group is notifying + if (!(anObject instanceof EODisplayGroup)) { + // mark parent group's object as changed + EODisplayGroup displayGroup = displayGroupForAspect(ParentAspect); + if (displayGroup != null) { + Object selected = displayGroup.selectedObject(); + if (selected != null) { + // only notify if childrenKey is an attribute of parentDesc + // (and therefore not a toOne or toMany relationship) + EOClassDescription parentDesc = EOClassDescription.classDescriptionForClass(selected.getClass()); + String key = displayGroupKeyForAspect(ParentAspect); + if (key != null) { + int idx = key.indexOf('.'); + if (idx != -1) + key = key.substring(0, idx); + if (parentDesc.attributeKeys().contains(key)) { + // only notify if we are an attribute key + EOObserverCenter.notifyObserversObjectWillChange(selected); + } + } + } } + } else // display group is notifying + { + // call super so subjectChanged will be called + super.objectWillChange(anObject); } - } - - /** - * Overridden to intercept notifications of changes to objects - * in the controlled display group and broadcast a change on the - * parent group's selected object. All other notifications are - * passed to the super implementation. - */ - public void objectWillChange ( Object anObject ) - { - // if child display group is notifying - if ( ! ( anObject instanceof EODisplayGroup ) ) - { - // mark parent group's object as changed - EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect ); - if ( displayGroup != null ) - { - Object selected = displayGroup.selectedObject(); - if ( selected != null ) - { - // only notify if childrenKey is an attribute of parentDesc - // (and therefore not a toOne or toMany relationship) - EOClassDescription parentDesc = - EOClassDescription.classDescriptionForClass( - selected.getClass() ); - String key = displayGroupKeyForAspect( ParentAspect ); - if ( key != null ) - { - int idx = key.indexOf( '.' ); - if ( idx != -1 ) key = key.substring( 0, idx ); - if ( parentDesc.attributeKeys().contains( key ) ) - { - // only notify if we are an attribute key - EOObserverCenter.notifyObserversObjectWillChange( selected ); - } - } - } - } - } - else // display group is notifying - { - // call super so subjectChanged will be called - super.objectWillChange( anObject ); - } - } - - /** - * Called by subjectChanged() to requalify the controlled - * display group with the selected object and the bound key. - */ - protected void requalify() - { + } + + /** + * Called by subjectChanged() to requalify the controlled display group with the + * selected object and the bound key. + */ + protected void requalify() { EODisplayGroup component = component(); - EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect ); - String key = displayGroupKeyForAspect( ParentAspect ); - - if ( ( displayGroup.selectedObject() != null ) - && ( component.dataSource() != null ) ) - { - component.dataSource().qualifyWithRelationshipKey( - key, displayGroup.selectedObject() ); + EODisplayGroup displayGroup = displayGroupForAspect(ParentAspect); + String key = displayGroupKeyForAspect(ParentAspect); + + if ((displayGroup.selectedObject() != null) && (component.dataSource() != null)) { + component.dataSource().qualifyWithRelationshipKey(key, displayGroup.selectedObject()); component.fetch(); - observableArray.setArray( component.allObjects() ); - } - else // no selection or no data source, clear + observableArray.setArray(component.allObjects()); + } else // no selection or no data source, clear { - component.setObjectArray( null ); - observableArray.removeAllObjects(); + component.setObjectArray(null); + observableArray.removeAllObjects(); } - component.updateDisplayedObjects(); + component.updateDisplayedObjects(); + } + + /** + * This implementation returns ObserverPrioritySecond so that master detail + * assocations are notified before other associations. + */ + public int priority() { + return ObserverPrioritySecond; } - - /** - * This implementation returns ObserverPrioritySecond - * so that master detail assocations are notified before - * other associations. - */ - public int priority () - { - return ObserverPrioritySecond; - } // convenience - private EODisplayGroup component() - { - return (EODisplayGroup) object(); - } - + private EODisplayGroup component() { + return (EODisplayGroup) object(); + } + } /* - * $Log$ - * Revision 1.2 2006/02/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.14 2004/02/04 20:00:49 mpowers - * Improved change notification for dotted key paths. + * Revision 1.14 2004/02/04 20:00:49 mpowers Improved change notification for + * dotted key paths. * - * Revision 1.13 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.13 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.12 2001/10/26 18:39:05 mpowers - * Now a delayed observer with a higher priority, so that it is processed - * before other associations. + * Revision 1.12 2001/10/26 18:39:05 mpowers Now a delayed observer with a + * higher priority, so that it is processed before other associations. * - * Revision 1.11 2001/06/26 21:39:33 mpowers - * Added check for null component data source before requalifying. + * Revision 1.11 2001/06/26 21:39:33 mpowers Added check for null component data + * source before requalifying. * - * Revision 1.10 2001/05/21 14:04:15 mpowers - * No longer changing a detail group's data source if it's already specified. + * Revision 1.10 2001/05/21 14:04:15 mpowers No longer changing a detail group's + * data source if it's already specified. * - * Revision 1.9 2001/05/18 21:08:46 mpowers - * Now calling updateDisplayedObjects on detail after master changes. + * Revision 1.9 2001/05/18 21:08:46 mpowers Now calling updateDisplayedObjects + * on detail after master changes. * - * Revision 1.8 2001/05/14 15:26:42 mpowers - * Modified logic for controlled groups that have no data source already set. + * Revision 1.8 2001/05/14 15:26:42 mpowers Modified logic for controlled groups + * that have no data source already set. * - * Revision 1.7 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.7 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.6 2001/04/29 02:29:31 mpowers - * Debugging relationship faulting. + * Revision 1.6 2001/04/29 02:29:31 mpowers Debugging relationship faulting. * - * Revision 1.4 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.4 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.3 2001/01/18 16:57:18 mpowers - * Fixed problem with losing connection: the association was getting - * garbage collected because nothing referred to it. All other associations - * make themselves listeners of their controlled object, and that has been - * the only thing keeping them from getting gc'd. This will need to be fixed. + * Revision 1.3 2001/01/18 16:57:18 mpowers Fixed problem with losing + * connection: the association was getting garbage collected because nothing + * referred to it. All other associations make themselves listeners of their + * controlled object, and that has been the only thing keeping them from getting + * gc'd. This will need to be fixed. * - * Revision 1.2 2001/01/17 23:06:09 mpowers - * TreeAssociation now modifies the contents of the children display - * group rather than adding items to the titles display group. + * Revision 1.2 2001/01/17 23:06:09 mpowers TreeAssociation now modifies the + * contents of the children display group rather than adding items to the titles + * display group. * - * Revision 1.1.1.1 2000/12/21 15:48:23 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:23 mpowers Contributing wotonomy. * - * Revision 1.5 2000/12/20 16:25:40 michael - * Added log to all files. + * Revision 1.5 2000/12/20 16:25:40 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java index aeac376..b81f9e2 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/MirrorDetailAssociation.java @@ -17,90 +17,76 @@ License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui; + import net.wotonomy.foundation.NSArray; /** -* This master detail association synchronizes the contents -* and selection of the master group into the detail group. -*/ -public class MirrorDetailAssociation extends MasterDetailAssociation{ + * This master detail association synchronizes the contents and selection of the + * master group into the detail group. + */ +public class MirrorDetailAssociation extends MasterDetailAssociation { - /** - * Standard constructor specifying the detail display group. - * @param displayGroup the detail display group of this - * Master-Detail Association - */ - public MirrorDetailAssociation(EODisplayGroup displayGroup){ - super(displayGroup); - } + /** + * Standard constructor specifying the detail display group. + * + * @param displayGroup the detail display group of this Master-Detail + * Association + */ + public MirrorDetailAssociation(EODisplayGroup displayGroup) { + super(displayGroup); + } - /** - * Called by subjectChanged() to requalify the controlled - * display group with the indexed object and the bound key. - * This implementation ignores both and sets the object array - * of the detail group to the displayed objects of the master - * and sets the selection to match. - */ - protected void requalify() - { - EODisplayGroup detail = (EODisplayGroup) object(); - EODisplayGroup master = - displayGroupForAspect( ParentAspect ); + /** + * Called by subjectChanged() to requalify the controlled display group with the + * indexed object and the bound key. This implementation ignores both and sets + * the object array of the detail group to the displayed objects of the master + * and sets the selection to match. + */ + protected void requalify() { + EODisplayGroup detail = (EODisplayGroup) object(); + EODisplayGroup master = displayGroupForAspect(ParentAspect); - if ( master != null ) - { - NSArray masterObjects = master.displayedObjects(); - NSArray detailObjects = detail.displayedObjects(); - int size = masterObjects.size(); - boolean different = false; - - // see if lists contain the same object instances - if ( size == detailObjects.size() ) - { - for ( int i = 0; i < size; i++ ) - { - if ( masterObjects.objectAtIndex(i) - != detailObjects.objectAtIndex(i) ) - { - different = true; - break; - } - } - } - else // different sizes - { - different = true; - } - - // if different, sync contents and selection with master - if ( different ) - { - detail.setObjectArray( masterObjects ); - detail.setSelectionIndexes( master.selectionIndexes() ); - } - else // if selection changed, sync selection with master - if ( master.selectionChanged() ) - { - detail.setSelectionIndexes( master.selectionIndexes() ); - } - } - else // no bound display group, clear - { - detail.setObjectArray( null ); - } - } + if (master != null) { + NSArray masterObjects = master.displayedObjects(); + NSArray detailObjects = detail.displayedObjects(); + int size = masterObjects.size(); + boolean different = false; + + // see if lists contain the same object instances + if (size == detailObjects.size()) { + for (int i = 0; i < size; i++) { + if (masterObjects.objectAtIndex(i) != detailObjects.objectAtIndex(i)) { + different = true; + break; + } + } + } else // different sizes + { + different = true; + } + + // if different, sync contents and selection with master + if (different) { + detail.setObjectArray(masterObjects); + detail.setSelectionIndexes(master.selectionIndexes()); + } else // if selection changed, sync selection with master + if (master.selectionChanged()) { + detail.setSelectionIndexes(master.selectionIndexes()); + } + } else // no bound display group, clear + { + detail.setObjectArray(null); + } + } } /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/07/05 22:13:28 mpowers - * Now only updating if master has actually changed. + * Revision 1.2 2001/07/05 22:13:28 mpowers Now only updating if master has + * actually changed. * - * Revision 1.1 2001/05/29 19:57:47 mpowers - * Added some neglected files. + * Revision 1.1 2001/05/29 19:57:47 mpowers Added some neglected files. * * */ - diff --git a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java index a398e97..e18ed3b 100644 --- a/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java +++ b/projects/net.wotonomy.ui/src/main/java/net/wotonomy/ui/ObservableArray.java @@ -28,322 +28,272 @@ 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; - } - + * 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 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 ); - } + * 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 ); - } + * 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 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 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 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 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() ); - } - } + * 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; - + * 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() ) - { + 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 ); + 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() ); + 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--; + 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 ) ); + 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 ); - } + * 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 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 ); - } + * 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 ); - } - + * 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 ); - } + * 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 ); - } + * 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/18 23:14:35 cgruber - * Update imports and maven dependencies. + * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven + * dependencies. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 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.3 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.2 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.2 2002/10/24 21:15:36 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/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. + * Revision 1.1 2001/01/24 14:37:24 mpowers Contributing a delegate useful for + * debugging. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java index cef1372..1c91115 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java @@ -28,324 +28,272 @@ 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; - } - + * 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 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 ); - } + * 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 ); - } + * 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 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 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 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 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() ); - } - } + * 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; - + * 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() ) - { + 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 ); + 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() ); + 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--; + 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 ) ); + 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 ); - } + * 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 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 ); - } + * 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 ); - } - + * 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 ); - } + * 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 ); - } + * 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/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/18 23:31:32 mpowers - * Needed for WODisplayGroup. + * Revision 1.1 2003/01/18 23:31:32 mpowers Needed for WODisplayGroup. * - * Revision 1.2 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.2 2002/10/24 21:15:36 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/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. + * Revision 1.1 2001/01/24 14:37:24 mpowers Contributing a delegate useful for + * debugging. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java index 41f77f5..ba608a4 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java @@ -59,7 +59,7 @@ * * [Additional notices, if required by prior licensing conditions] * - */ + */ // excellent class borrowed from Apache Commons project: //package org.apache.commons.httpclient; @@ -80,30 +80,39 @@ import sun.security.action.GetPropertyAction; /** * The interface for the URI(Uniform Resource Identifiers) version of RFC 2396. * This class has the purpose of supportting of parsing a URI reference to - * extend any specific protocols, the character encoding of the protocol to - * be transported and the charset of the document. + * extend any specific protocols, the character encoding of the protocol to be + * transported and the charset of the document. *

* A URI is always in an "escaped" form, since escaping or unescaping a - * completed URI might change its semantics. + * completed URI might change its semantics. *

- * Implementers should be careful not to escape or unescape the same string - * more than once, since unescaping an already unescaped string might lead to - * misinterpreting a percent data character as another escaped character, - * or vice versa in the case of escaping an already escaped string. + * Implementers should be careful not to escape or unescape the same string more + * than once, since unescaping an already unescaped string might lead to + * misinterpreting a percent data character as another escaped character, or + * vice versa in the case of escaping an already escaped string. *

* In order to avoid these problems, data types used as follows: - *

+ * 

+ *

+ * + *
  *   URI character sequence: char
  *   octet sequence: byte
  *   original character sequence: String
- * 

+ *

+ * + *
+ *

* - * So, a URI is a sequence of characters as an array of a char type, which - * is not always represented as a sequence of octets as an array of byte. + * So, a URI is a sequence of characters as an array of a char type, which is + * not always represented as a sequence of octets as an array of byte. *

* * URI Syntactic Components - *

+ * 

+ *

+ * + *
  * - In general, written as follows:
  *   Absolute URI = <scheme>:<scheme-specific-part>
  *   Generic URI = <scheme>://<authority><path>?<query>
@@ -113,9 +122,13 @@ import sun.security.action.GetPropertyAction;
  *   hier_part     = ( net_path | abs_path ) [ "?" query ]
  *   net_path      = "//" authority [ abs_path ]
  *   abs_path      = "/"  path_segments
- * 

+ *

+ * + *
+ *

* * The following examples illustrate URI that are in common use. + * *

  * ftp://ftp.is.co.za/rfc/rfc1808.txt
  *    -- ftp scheme for File Transfer Protocol services
@@ -130,11 +143,14 @@ import sun.security.action.GetPropertyAction;
  * telnet://melvyl.ucop.edu/
  *    -- telnet scheme for interactive services via the TELNET Protocol
  * 
+ * * Please, notice that there are many modifications from URL(RFC 1738) and * relative URL(RFC 1808). *

* The expressions for a URI - *

+ * 

+ * + *

  * For escaped URI forms
  *  - URI(char[]) // constructor
  *  - char[] getRawXxx() // method
@@ -144,3321 +160,3489 @@ import sun.security.action.GetPropertyAction;
  * For unescaped URI forms
  *  - URI(String) // constructor
  *  - String getXXX() // method
- * 

+ *

+ *

* * @author Sung-Gu - * @version $Revision: 905 $ $Date: 2002/03/14 15:14:01 + * @version $Revision: 905 $ $Date: 2002/03/14 15:14:01 */ class URI implements Cloneable, Comparable, Serializable { - - // ----------------------------------------------------------- Constructors - - protected URI() { - } - - /** - * Construct a URI as an escaped form of a character array. - * An URI can be placed within double-quotes or angle brackets like - * "http://test.com/" and <http://test.com/> - * - * @param escaped the URI character sequence - * @exception IOException - * @throws NullPointerException if escaped is null - */ - public URI(char[] escaped) throws IOException { - parseUriReference(new String(escaped), true); - } - - - /** - * Construct a URI from the given string. - *

-     *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-     * 

- * An URI can be placed within double-quotes or angle brackets like - * "http://test.com/" and <http://test.com/> - * - * @param original the string to be represented to URI character sequence - * It is one of absoluteURI and relativeURI. - * @exception IOException - */ - public URI(String original) throws IOException { - parseUriReference(original, false); - } - - /** - * Construct a URI from a URL. - * - * @param url a valid URL. - * @throws IOException - * @since 2.0 - */ - public URI(URL url) throws IOException { - this(url.toString()); - } - - - /** - * Construct a general URI from the given components. - *

-     *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-     *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
-     *   opaque_part   = uric_no_slash *uric
-     * 

- * It's for absolute URI = <scheme>:<scheme-specific-part># - * <fragment>. - * - * @param scheme the scheme string - * @param scheme_specific_part scheme_specific_part - * @param fragment the fragment string - * @exception IOException - */ - public URI(String scheme, String scheme_specific_part, String fragment) - throws IOException { - - // validate and contruct the URI character sequence - if (scheme == null) { - throw new IOException(/*IOException.PARSING,*/ "URI: scheme required"); - } - char[] s = scheme.toLowerCase().toCharArray(); - if (validate(s, URI.scheme)) { - _scheme = s; // is_absoluteURI - } else { - throw new IOException(/*IOException.PARSING,*/ "URI: incorrect scheme"); - } - _opaque = encode(scheme_specific_part, allowed_opaque_part); - // Set flag - _is_opaque_part = true; - setUriReference(); - } - - - /** - * Construct a general URI from the given components. - *

-     *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-     *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
-     *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
-     *   hier_part     = ( net_path | abs_path ) [ "?" query ]
-     * 

- * It's for absolute URI = <scheme>:<path>?<query>#< - * fragment> and relative URI = <path>?<query>#<fragment - * >. - * - * @param scheme the scheme string - * @param authority the authority string - * @param path the path string - * @param query the query string - * @param fragment the fragment string - * @exception IOException - */ - public URI(String scheme, String authority, String path, String query, - String fragment) throws IOException { - - // validate and contruct the URI character sequence - StringBuffer buff = new StringBuffer(); - if (scheme != null) { - buff.append(scheme); - buff.append(':'); - } - if (authority != null) { - buff.append("//"); - buff.append(authority); - } - if (path != null) { // accept empty path - if ((scheme != null || authority != null) - && !path.startsWith("/")) { - throw new IOException(/*IOException.PARSING*,*/ - "URI: abs_path requested"); - } - buff.append(path); - } - if (query != null) { - buff.append('?'); - buff.append(query); - } - if (fragment != null) { - buff.append('#'); - buff.append(fragment); - } - parseUriReference(buff.toString(), false); - } - - - /** - * Construct a general URI from the given components. - * - * @param scheme the scheme string - * @param userinfo the userinfo string - * @param host the host string - * @param port the port number - * @exception IOException - */ - public URI(String scheme, String userinfo, String host, int port) - throws IOException { - - this(scheme, userinfo, host, port, null, null, null); - } - - - /** - * Construct a general URI from the given components. - * - * @param scheme the scheme string - * @param userinfo the userinfo string - * @param host the host string - * @param port the port number - * @param path the path string - * @exception IOException - */ - public URI(String scheme, String userinfo, String host, int port, - String path) throws IOException { - - this(scheme, userinfo, host, port, path, null, null); - } - - - /** - * Construct a general URI from the given components. - * - * @param scheme the scheme string - * @param userinfo the userinfo string - * @param host the host string - * @param port the port number - * @param path the path string - * @param query the query string - * @exception IOException - */ - public URI(String scheme, String userinfo, String host, int port, - String path, String query) throws IOException { - - this(scheme, userinfo, host, port, path, query, null); - } - - - /** - * Construct a general URI from the given components. - * - * @param scheme the scheme string - * @param userinfo the userinfo string - * @param host the host string - * @param port the port number - * @param path the path string - * @param query the query string - * @param fragment the fragment string - * @exception IOException - */ - public URI(String scheme, String userinfo, String host, int port, - String path, String query, String fragment) throws IOException { - - this(scheme, (host == null) ? null : - ((userinfo != null) ? userinfo + '@' : "") + host + - ((port != -1) ? ":" + port : ""), path, query, fragment); - } - - - /** - * Construct a general URI from the given components. - * - * @param scheme the scheme string - * @param host the host string - * @param path the path string - * @param fragment the fragment string - * @exception IOException - */ - public URI(String scheme, String host, String path, String fragment) - throws IOException { - - this(scheme, host, path, null, fragment); - } - - - /** - * Construct a general URI with the given relative URI string. - * - * @param base the base URI - * @param relative the relative URI string - * @exception IOException - */ - public URI(URI base, String relative) throws IOException { - this(base, new URI(relative)); - } - - - /** - * Construct a general URI with the given relative URI. - *

-     *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-     *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
-     * 

- * Resolving Relative References to Absolute Form. - * - * Examples of Resolving Relative URI References - * - * Within an object with a well-defined base URI of - *

-     *   http://a/b/c/d;p?q
-     * 

- * the relative URI would be resolved as follows: - * - * Normal Examples - * - *

-     *   g:h           =  g:h
-     *   g             =  http://a/b/c/g
-     *   ./g           =  http://a/b/c/g
-     *   g/            =  http://a/b/c/g/
-     *   /g            =  http://a/g
-     *   //g           =  http://g
-     *   ?y            =  http://a/b/c/?y
-     *   g?y           =  http://a/b/c/g?y
-     *   #s            =  (current document)#s
-     *   g#s           =  http://a/b/c/g#s
-     *   g?y#s         =  http://a/b/c/g?y#s
-     *   ;x            =  http://a/b/c/;x
-     *   g;x           =  http://a/b/c/g;x
-     *   g;x?y#s       =  http://a/b/c/g;x?y#s
-     *   .             =  http://a/b/c/
-     *   ./            =  http://a/b/c/
-     *   ..            =  http://a/b/
-     *   ../           =  http://a/b/
-     *   ../g          =  http://a/b/g
-     *   ../..         =  http://a/
-     *   ../../        =  http://a/ 
-     *   ../../g       =  http://a/g
-     * 

- * - * Some URI schemes do not allow a hierarchical syntax matching the - * syntax, and thus cannot use relative references. - * - * @param base the base URI - * @param relative the relative URI - * @exception IOException - */ - public URI(URI base, URI relative) throws IOException { - - if (base._scheme == null) { - throw new IOException(/* IOException.PARSING,*/ "URI: base URI required"); - } - if (base._scheme != null) { - this._scheme = base._scheme; - this._authority = base._authority; - } - if (base._is_opaque_part || relative._is_opaque_part) { - this._scheme = base._scheme; - this._is_opaque_part = relative._is_opaque_part; - this._opaque = relative._opaque; - this._fragment = relative._fragment; - this.setUriReference(); - return; - } - if (relative._scheme != null) { - this._scheme = relative._scheme; - this._is_net_path = relative._is_net_path; - this._authority = relative._authority; - if (relative._is_server) { - this._userinfo = relative._userinfo; - this._host = relative._host; - this._port = relative._port; - } else if (relative._is_reg_name) { - this._is_reg_name = relative._is_reg_name; - } - this._is_abs_path = relative._is_abs_path; - this._is_rel_path = relative._is_rel_path; - this._path = relative._path; - } else if (base._authority != null && relative._scheme == null) { - this._is_net_path = base._is_net_path; - this._authority = base._authority; - if (base._is_server) { - this._userinfo = base._userinfo; - this._host = base._host; - this._port = base._port; - } else if (base._is_reg_name) { - this._is_reg_name = base._is_reg_name; - } - } - if (relative._authority != null) { - this._is_net_path = relative._is_net_path; - this._authority = relative._authority; - if (relative._is_server) { - this._is_server = relative._is_server; - this._userinfo = relative._userinfo; - this._host = relative._host; - this._port = relative._port; - } else if (relative._is_reg_name) { - this._is_reg_name = relative._is_reg_name; - } - this._is_abs_path = relative._is_abs_path; - this._is_rel_path = relative._is_rel_path; - this._path = relative._path; - } - // resolve the path - if (relative._scheme == null && relative._authority == null || - equals(base._scheme, relative._scheme)) { - this._path = resolvePath(base._path, relative._path); - } - // base._query removed - if (relative._query != null) { - this._query = relative._query; - } - // base._fragment removed - if (relative._fragment != null) { - this._fragment = relative._fragment; - } - this.setUriReference(); - } - - // --------------------------------------------------- Instance Variables - - static final long serialVersionUID = 604752400577948726L; - - - /** - * This Uniform Resource Identifier (URI). - * The URI is always in an "escaped" form, since escaping or unescaping - * a completed URI might change its semantics. - */ - protected char[] _uri = null; - - - /** - * The default charset of the protocol. RFC 2277, 2396 - */ - protected static String _protocolCharset = "UTF-8"; - - - /** - * The default charset of the document. RFC 2277, 2396 - * The platform's charset is used for the document by default. - */ - protected static String _documentCharset = null; - // Static initializer for _documentCharset - static { - Locale locale = Locale.getDefault(); - if (locale != null) { - // in order to support backward compatiblity - _documentCharset = LocaleToCharsetMap.getCharset(locale); - } else { - _documentCharset = (String)AccessController.doPrivileged( - new GetPropertyAction("file.encoding")); - } - } - - /** - * The scheme. - */ - protected char[] _scheme = null; - - - /** - * The opaque. - */ - protected char[] _opaque = null; - - - /** - * The authority. - */ - protected char[] _authority = null; - - - /** - * The userinfo. - */ - protected char[] _userinfo = null; - - - /** - * The host. - */ - protected char[] _host = null; - - - /** - * The port. - */ - protected int _port = -1; - - - /** - * The path. - */ - protected char[] _path = null; - - - /** - * The query. - */ - protected char[] _query = null; - - - /** - * The fragment. - */ - protected char[] _fragment = null; - - - /** - * The root path. - */ - protected static char[] rootPath = { '/' }; - - // ---------------------- Generous characters for each component validation - - /** - * The percent "%" character always has the reserved purpose of being the - * escape indicator, it must be escaped as "%25" in order to be used as - * data within a URI. - */ - protected static final BitSet percent = new BitSet(256); - // Static initializer for percent - static { - percent.set('%'); - } - - - /** - * BitSet for digit. - *

-     * digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
-     *            "8" | "9"
-     * 

- */ - protected static final BitSet digit = new BitSet(256); - // Static initializer for digit - static { - for(int i = '0'; i <= '9'; i++) { - digit.set(i); - } - } - - - /** - * BitSet for alpha. - *

-     * alpha         = lowalpha | upalpha
-     * 

- */ - protected static final BitSet alpha = new BitSet(256); - // Static initializer for alpha - static { - for (int i = 'a'; i <= 'z'; i++) { - alpha.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - alpha.set(i); - } - } - - - /** - * BitSet for alphanum (join of alpha & digit). - *

-     *  alphanum      = alpha | digit
-     * 

- */ - protected static final BitSet alphanum = new BitSet(256); - // Static initializer for alphanum - static { - alphanum.or(alpha); - alphanum.or(digit); - } - - - /** - * BitSet for hex. - *

-     * hex           = digit | "A" | "B" | "C" | "D" | "E" | "F" |
-     *                         "a" | "b" | "c" | "d" | "e" | "f"
-     * 

- */ - protected static final BitSet hex = new BitSet(256); - // Static initializer for hex - static { - hex.or(digit); - for(int i = 'a'; i <= 'f'; i++) { - hex.set(i); - } - for(int i = 'A'; i <= 'F'; i++) { - hex.set(i); - } - } - - - /** - * BitSet for escaped. - *

-     * escaped       = "%" hex hex
-     * 

- */ - protected static final BitSet escaped = new BitSet(256); - // Static initializer for escaped - static { - escaped.or(percent); - escaped.or(hex); - } - - - /** - * BitSet for mark. - *

-     * mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
-     *                 "(" | ")"
-     * 

- */ - protected static final BitSet mark = new BitSet(256); - // Static initializer for mark - static { - mark.set('-'); - mark.set('_'); - mark.set('.'); - mark.set('!'); - mark.set('~'); - mark.set('*'); - mark.set('\''); - mark.set('('); - mark.set(')'); - } - - - /** - * Data characters that are allowed in a URI but do not have a reserved - * purpose are called unreserved. - *

-     * unreserved    = alphanum | mark
-     * 

- */ - protected static final BitSet unreserved = new BitSet(256); - // Static initializer for unreserved - static { - unreserved.or(alphanum); - unreserved.or(mark); - } - - - /** - * BitSet for reserved. - *

-     * reserved      = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
-     *                 "$" | ","
-     * 

- */ - protected static final BitSet reserved = new BitSet(256); - // Static initializer for reserved - static { - reserved.set(';'); - reserved.set('/'); - reserved.set('?'); - reserved.set(':'); - reserved.set('@'); - reserved.set('&'); - reserved.set('='); - reserved.set('+'); - reserved.set('$'); - reserved.set(','); - } - - - /** - * BitSet for uric. - *

-     * uric          = reserved | unreserved | escaped
-     * 

- */ - protected static final BitSet uric = new BitSet(256); - // Static initializer for uric - static { - uric.or(reserved); - uric.or(unreserved); - uric.or(escaped); - } - - - /** - * BitSet for fragment (alias for uric). - *

-     * fragment      = *uric
-     * 

- */ - protected static final BitSet fragment = uric; - - - /** - * BitSet for query (alias for uric). - *

-     * query         = *uric
-     * 

- */ - protected static final BitSet query = uric; - - - /** - * BitSet for pchar. - *

-     * pchar         = unreserved | escaped |
-     *                 ":" | "@" | "&" | "=" | "+" | "$" | ","
-     * 

- */ - protected static final BitSet pchar = new BitSet(256); - // Static initializer for pchar - static { - pchar.or(unreserved); - pchar.or(escaped); - pchar.set(':'); - pchar.set('@'); - pchar.set('&'); - pchar.set('='); - pchar.set('+'); - pchar.set('$'); - pchar.set(','); - } - - - /** - * BitSet for param (alias for pchar). - *

-     * param         = *pchar
-     * 

- */ - protected static final BitSet param = pchar; - - - /** - * BitSet for segment. - *

-     * segment       = *pchar *( ";" param )
-     * 

- */ - protected static final BitSet segment = new BitSet(256); - // Static initializer for segment - static { - segment.or(pchar); - segment.set(';'); - segment.or(param); - } - - - /** - * BitSet for path segments. - *

-     * path_segments = segment *( "/" segment )
-     * 

- */ - protected static final BitSet path_segments = new BitSet(256); - // Static initializer for path_segments - static { - path_segments.set('/'); - path_segments.or(segment); - } - - - /** - * URI absolute path. - *

-     * abs_path      = "/"  path_segments
-     * 

- */ - protected static final BitSet abs_path = new BitSet(256); - // Static initializer for abs_path - static { - abs_path.set('/'); - abs_path.or(path_segments); - } - - - /** - * URI bitset for encoding typical non-slash characters. - *

-     * uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
-     *                 "&" | "=" | "+" | "$" | ","
-     * 

- */ - protected static final BitSet uric_no_slash = new BitSet(256); - // Static initializer for uric_no_slash - static { - uric_no_slash.or(unreserved); - uric_no_slash.or(escaped); - uric_no_slash.set(';'); - uric_no_slash.set('?'); - uric_no_slash.set(';'); - uric_no_slash.set('@'); - uric_no_slash.set('&'); - uric_no_slash.set('='); - uric_no_slash.set('+'); - uric_no_slash.set('$'); - uric_no_slash.set(','); - } - - - /** - * URI bitset that combines uric_no_slash and uric. - *

-     * opaque_part   = uric_no_slash *uric
-     * 

- */ - protected static final BitSet opaque_part = new BitSet(256); - // Static initializer for opaque_part - static { - opaque_part.or(uric_no_slash); - opaque_part.or(uric); - } - - - /** - * URI bitset that combines absolute path and opaque part. - *

-     * path          = [ abs_path | opaque_part ]
-     * 

- */ - protected static final BitSet path = new BitSet(256); - // Static initializer for path - static { - path.or(abs_path); - path.or(opaque_part); - } - - - /** - * Port, a logical alias for digit. - */ - protected static final BitSet port = digit; - - - /** - * Bitset that combines digit and dot fo IPv$address. - *

-     * IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
-     * 

- */ - protected static final BitSet IPv4address = new BitSet(256); - // Static initializer for IPv4address - static { - IPv4address.or(digit); - IPv4address.set('.'); - } - - - /** - * RFC 2373. - *

-     * IPv6address = hexpart [ ":" IPv4address ]
-     * 

- */ - protected static final BitSet IPv6address = new BitSet(256); - // Static initializer for IPv6address reference - static { - IPv6address.or(hex); // hexpart - IPv6address.set(':'); - IPv6address.or(IPv4address); - } - - - /** - * RFC 2732, 2373. - *

-     * IPv6reference   = "[" IPv6address "]"
-     * 

- */ - protected static final BitSet IPv6reference = new BitSet(256); - // Static initializer for IPv6reference - static { - IPv6reference.set('['); - IPv6reference.or(IPv6address); - IPv6reference.set(']'); - } - - - /** - * BitSet for toplabel. - *

-     * toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
-     * 

- */ - protected static final BitSet toplabel = new BitSet(256); - // Static initializer for toplabel - static { - toplabel.or(alphanum); - toplabel.set('-'); - } - - - /** - * BitSet for domainlabel. - *

-     * domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
-     * 

- */ - protected static final BitSet domainlabel = toplabel; - - - /** - * BitSet for hostname. - *

-     * hostname      = *( domainlabel "." ) toplabel [ "." ]
-     * 

- */ - protected static final BitSet hostname = new BitSet(256); - // Static initializer for hostname - static { - hostname.or(toplabel); - // hostname.or(domainlabel); - hostname.set('.'); - } - - - /** - * BitSet for host. - *

-     * host          = hostname | IPv4address | IPv6reference
-     * 

- */ - protected static final BitSet host = new BitSet(256); - // Static initializer for host - static { - host.or(hostname); - // host.or(IPv4address); - host.or(IPv6reference); // IPv4address - } - - - /** - * BitSet for hostport. - *

-     * hostport      = host [ ":" port ]
-     * 

- */ - protected static final BitSet hostport = new BitSet(256); - // Static initializer for hostport - static { - hostport.or(host); - hostport.set(':'); - hostport.or(port); - } - - - /** - * Bitset for userinfo. - *

-     * userinfo      = *( unreserved | escaped |
-     *                    ";" | ":" | "&" | "=" | "+" | "$" | "," )
-     * 

- */ - protected static final BitSet userinfo = new BitSet(256); - // Static initializer for userinfo - static { - userinfo.or(unreserved); - userinfo.or(escaped); - userinfo.set(';'); - userinfo.set(':'); - userinfo.set('&'); - userinfo.set('='); - userinfo.set('+'); - userinfo.set('$'); - userinfo.set(','); - } - - - /** - * BitSet for within the userinfo component like user and password. - */ - public static final BitSet within_userinfo = new BitSet(256); - // Static initializer for within_userinfo - static { - within_userinfo.or(userinfo); - within_userinfo.clear(';'); // reserved within authority - within_userinfo.clear(':'); - within_userinfo.clear('@'); - within_userinfo.clear('?'); - within_userinfo.clear('/'); - } - - - /** - * Bitset for server. - *

-     * server        = [ [ userinfo "@" ] hostport ]
-     * 

- */ - protected static final BitSet server = new BitSet(256); - // Static initializer for server - static { - server.or(userinfo); - server.set('@'); - server.or(hostport); - } - - - /** - * BitSet for reg_name. - *

-     * reg_name      = 1*( unreserved | escaped | "$" | "," |
-     *                     ";" | ":" | "@" | "&" | "=" | "+" )
-     * 

- */ - protected static final BitSet reg_name = new BitSet(256); - // Static initializer for reg_name - static { - reg_name.or(unreserved); - reg_name.or(escaped); - reg_name.set('$'); - reg_name.set(','); - reg_name.set(';'); - reg_name.set(':'); - reg_name.set('@'); - reg_name.set('&'); - reg_name.set('='); - reg_name.set('+'); - } - - - /** - * BitSet for authority. - *

-     * authority     = server | reg_name
-     * 

- */ - protected static final BitSet authority = new BitSet(256); - // Static initializer for authority - static { - authority.or(server); - authority.or(reg_name); - } - - - /** - * BitSet for scheme. - *

-     * scheme        = alpha *( alpha | digit | "+" | "-" | "." )
-     * 

- */ - protected static final BitSet scheme = new BitSet(256); - // Static initializer for scheme - static { - scheme.or(alpha); - scheme.or(digit); - scheme.set('+'); - scheme.set('-'); - scheme.set('.'); - } - - - /** - * BitSet for rel_segment. - *

-     * rel_segment   = 1*( unreserved | escaped |
-     *                     ";" | "@" | "&" | "=" | "+" | "$" | "," )
-     * 

- */ - protected static final BitSet rel_segment = new BitSet(256); - // Static initializer for rel_segment - static { - rel_segment.or(unreserved); - rel_segment.or(escaped); - rel_segment.set(';'); - rel_segment.set('@'); - rel_segment.set('&'); - rel_segment.set('='); - rel_segment.set('+'); - rel_segment.set('$'); - rel_segment.set(','); - } - - - /** - * BitSet for rel_path. - *

-     * rel_path      = rel_segment [ abs_path ]
-     * 

- */ - protected static final BitSet rel_path = new BitSet(256); - // Static initializer for rel_path - static { - rel_path.or(rel_segment); - rel_path.or(abs_path); - } - - - /** - * BitSet for net_path. - *

-     * net_path      = "//" authority [ abs_path ]
-     * 

- */ - protected static final BitSet net_path = new BitSet(256); - // Static initializer for net_path - static { - net_path.set('/'); - net_path.or(authority); - net_path.or(abs_path); - } - - - /** - * BitSet for hier_part. - *

-     * hier_part     = ( net_path | abs_path ) [ "?" query ]
-     * 

- */ - protected static final BitSet hier_part = new BitSet(256); - // Static initializer for hier_part - static { - hier_part.or(net_path); - hier_part.or(abs_path); - // hier_part.set('?'); aleady included - hier_part.or(query); - } - - - /** - * BitSet for relativeURI. - *

-     * relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
-     * 

- */ - protected static final BitSet relativeURI = new BitSet(256); - // Static initializer for relativeURI - static { - relativeURI.or(net_path); - relativeURI.or(abs_path); - relativeURI.or(rel_path); - // relativeURI.set('?'); aleady included - relativeURI.or(query); - } - - - /** - * BitSet for absoluteURI. - *

-     * absoluteURI   = scheme ":" ( hier_part | opaque_part )
-     * 

- */ - protected static final BitSet absoluteURI = new BitSet(256); - // Static initializer for absoluteURI - static { - absoluteURI.or(scheme); - absoluteURI.set(':'); - absoluteURI.or(hier_part); - absoluteURI.or(opaque_part); - } - - - /** - * BitSet for URI-reference. - *

-     * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-     * 

- */ - protected static final BitSet URI_reference = new BitSet(256); - // Static initializer for URI_reference - static { - URI_reference.or(absoluteURI); - URI_reference.or(relativeURI); - URI_reference.set('#'); - URI_reference.or(fragment); - } - - // ---------------------------- Characters disallowed within the URI syntax - // Excluded US-ASCII Characters are like control, space, delims and unwise - - /** - * BitSet for control. - */ - public static final BitSet control = new BitSet(256); - // Static initializer for control - static { - for (int i = 0; i <= 0x1F; i++) { - control.set(i); - } - control.set(0x7F); - } - - /** - * BitSet for space. - */ - public static final BitSet space = new BitSet(256); - // Static initializer for space - static { - space.set(0x20); - } - - - /** - * BitSet for delims. - */ - public static final BitSet delims = new BitSet(256); - // Static initializer for delims - static { - delims.set('<'); - delims.set('>'); - delims.set('#'); - delims.set('%'); - delims.set('"'); - } - - - /** - * BitSet for unwise. - */ - public static final BitSet unwise = new BitSet(256); - // Static initializer for unwise - static { - unwise.set('{'); - unwise.set('}'); - unwise.set('|'); - unwise.set('\\'); - unwise.set('^'); - unwise.set('['); - unwise.set(']'); - unwise.set('`'); - } - - - /** - * Disallowed rel_path before escaping. - */ - public static final BitSet disallowed_rel_path = new BitSet(256); - // Static initializer for disallowed_rel_path - static { - disallowed_rel_path.or(uric); - disallowed_rel_path.andNot(rel_path); - } - - - /** - * Disallowed opaque_part before escaping. - */ - public static final BitSet disallowed_opaque_part = new BitSet(256); - // Static initializer for disallowed_opaque_part - static { - disallowed_opaque_part.or(uric); - disallowed_opaque_part.andNot(opaque_part); - } - - // ----------------------- Characters allowed within and for each component - - /** - * Those characters that are allowed for the authority component. - */ - public static final BitSet allowed_authority = new BitSet(256); - // Static initializer for allowed_authority - static { - allowed_authority.or(authority); - allowed_authority.clear('%'); - } - - - /** - * Those characters that are allowed for the opaque_part. - */ - public static final BitSet allowed_opaque_part = new BitSet(256); - // Static initializer for allowed_opaque_part - static { - allowed_opaque_part.or(opaque_part); - allowed_opaque_part.clear('%'); - } - - - /** - * Those characters that are allowed for the reg_name. - */ - public static final BitSet allowed_reg_name = new BitSet(256); - // Static initializer for allowed_reg_name - static { - allowed_reg_name.or(reg_name); - // allowed_reg_name.andNot(percent); - allowed_reg_name.clear('%'); - } - - - /** - * Those characters that are allowed for the userinfo component. - */ - public static final BitSet allowed_userinfo = new BitSet(256); - // Static initializer for allowed_userinfo - static { - allowed_userinfo.or(userinfo); - // allowed_userinfo.andNot(percent); - allowed_userinfo.clear('%'); - } - - - /** - * Those characters that are allowed for within the userinfo component. - */ - public static final BitSet allowed_within_userinfo = new BitSet(256); - // Static initializer for allowed_within_userinfo - static { - allowed_within_userinfo.or(within_userinfo); - allowed_within_userinfo.clear('%'); - } - - - /** - * Those characters that are allowed for the IPv6reference component. - * The characters '[', ']' in IPv6reference should be excluded. - */ - public static final BitSet allowed_IPv6reference = new BitSet(256); - // Static initializer for allowed_IPv6reference - static { - allowed_IPv6reference.or(IPv6reference); - // allowed_IPv6reference.andNot(unwise); - allowed_IPv6reference.clear('['); - allowed_IPv6reference.clear(']'); - } - - - /** - * Those characters that are allowed for the host component. - * The characters '[', ']' in IPv6reference should be excluded. - */ - public static final BitSet allowed_host = new BitSet(256); - // Static initializer for allowed_host - static { - allowed_host.or(hostname); - allowed_host.or(allowed_IPv6reference); - } - - - /** - * Those characters that are allowed for the authority component. - */ - public static final BitSet allowed_within_authority = new BitSet(256); - // Static initializer for allowed_within_authority - static { - allowed_within_authority.or(server); - allowed_within_authority.or(reg_name); - allowed_within_authority.clear(';'); - allowed_within_authority.clear(':'); - allowed_within_authority.clear('@'); - allowed_within_authority.clear('?'); - allowed_within_authority.clear('/'); - } - - - /** - * Those characters that are allowed for the abs_path. - */ - public static final BitSet allowed_abs_path = new BitSet(256); - // Static initializer for allowed_abs_path - static { - allowed_abs_path.or(abs_path); - // allowed_abs_path.set('/'); // aleady included - allowed_abs_path.andNot(percent); - } - - - /** - * Those characters that are allowed for the rel_path. - */ - public static final BitSet allowed_rel_path = new BitSet(256); - // Static initializer for allowed_rel_path - static { - allowed_rel_path.or(rel_path); - allowed_rel_path.clear('%'); - } - - - /** - * Those characters that are allowed within the path. - */ - public static final BitSet allowed_within_path = new BitSet(256); - // Static initializer for allowed_within_path - static { - allowed_within_path.or(abs_path); - allowed_within_path.clear('/'); - allowed_within_path.clear(';'); - allowed_within_path.clear('='); - allowed_within_path.clear('?'); - } - - - /** - * Those characters that are allowed for the query component. - */ - public static final BitSet allowed_query = new BitSet(256); - // Static initializer for allowed_query - static { - allowed_query.or(uric); - allowed_query.clear('%'); - } - - - /** - * Those characters that are allowed within the query component. - */ - public static final BitSet allowed_within_query = new BitSet(256); - // Static initializer for allowed_within_query - static { - allowed_within_query.or(allowed_query); - allowed_within_query.andNot(reserved); // excluded 'reserved' - allowed_within_query.clear('#'); // avoid confict with the fragment - } - - - /** - * Those characters that are allowed for the fragment component. - */ - public static final BitSet allowed_fragment = new BitSet(256); - // Static initializer for allowed_fragment - static { - allowed_fragment.or(uric); - allowed_fragment.clear('%'); - } - - // ------------------------------------------- Flags for this URI-reference - - // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] - // absoluteURI = scheme ":" ( hier_part | opaque_part ) - protected boolean _is_hier_part; - protected boolean _is_opaque_part; - // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] - // hier_part = ( net_path | abs_path ) [ "?" query ] - protected boolean _is_net_path; - protected boolean _is_abs_path; - protected boolean _is_rel_path; - // net_path = "//" authority [ abs_path ] - // authority = server | reg_name - protected boolean _is_reg_name; - protected boolean _is_server; // = _has_server - // server = [ [ userinfo "@" ] hostport ] - // host = hostname | IPv4address | IPv6reference - protected boolean _is_hostname; - protected boolean _is_IPv4address; - protected boolean _is_IPv6reference; - - // ------------------------------------------ Character and escape encoding - - /** - * Encode with the default protocol charset. - * - * @param original the original character sequence - * @param allowed those characters that are allowed within a component - * @return URI character sequence - * @exception IOException null component or unsupported character encoding - */ - protected static char[] encode(String original, BitSet allowed) - throws IOException { - - return encode(original, allowed, _protocolCharset); - } - - - /** - * Encodes URI string. - * - * This is a two mapping, one from original characters to octets, and - * subsequently a second from octets to URI characters: - *

-     *   original character sequence->octet sequence->URI character sequence
-     * 

- * - * An escaped octet is encoded as a character triplet, consisting of the - * percent character "%" followed by the two hexadecimal digits - * representing the octet code. For example, "%20" is the escaped - * encoding for the US-ASCII space character. - *

- * Conversion from the local filesystem character set to UTF-8 will - * normally involve a two step process. First convert the local character - * set to the UCS; then convert the UCS to UTF-8. - * The first step in the process can be performed by maintaining a mapping - * table that includes the local character set code and the corresponding - * UCS code. - * The next step is to convert the UCS character code to the UTF-8 encoding. - *

- * Mapping between vendor codepages can be done in a very similar manner - * as described above. - *

- * The only time escape encodings can allowedly be made is when a URI is - * being created from its component parts. The escape and validate methods - * are internally performed within this method. - * - * @param original the original character sequence - * @param allowed those characters that are allowed within a component - * @param charset the protocol charset - * @return URI character sequence - * @exception IOException null component or unsupported character encoding - */ - protected static char[] encode(String original, BitSet allowed, - String charset) throws IOException { - - // encode original to uri characters. - if (original == null) { - throw new IOException(/*IOException.PARSING,*/ "URI: null"); - } - // escape octet to uri characters. - if (allowed == null) { - throw new IOException(/*IOException.PARSING,*/ - "URI: null allowed characters"); - } - byte[] octets; - try { - octets = original.getBytes(charset); - } catch (UnsupportedEncodingException error) { - throw new IOException(/*IOException.UNSUPPORTED_ENCODING,*/ "Unsupported Encoding: " + charset); - } - StringBuffer buf = new StringBuffer(octets.length); - for (int i = 0; i < octets.length; i++) { - char c = (char) octets[i]; - if (allowed.get(c)) { - buf.append(c); - } else { - buf.append('%'); - byte b = octets[i]; // use the original byte value - char hexadecimal = Character.forDigit((b >> 4) & 0xF, 16); - buf.append(Character.toUpperCase(hexadecimal)); // high - hexadecimal = Character.forDigit(b & 0xF, 16); - buf.append(Character.toUpperCase(hexadecimal)); // low - } - } - - return buf.toString().toCharArray(); - } - - - /** - * Decode with the default protocol charset. - * - * @param component the URI character sequence - * @return original character sequence - * @exception IOException incomplete trailing escape pattern - * or unsupported character encoding - */ - protected static String decode(char[] component) throws IOException { - return decode(component, _protocolCharset); - } - - - /** - * Decodes URI encoded string. - * - * This is a two mapping, one from URI characters to octets, and - * subsequently a second from octets to original characters: - *

-     *   URI character sequence->octet sequence->original character sequence
-     * 

- * - * A URI must be separated into its components before the escaped - * characters within those components can be allowedly decoded. - *

- * Notice that there is a chance that URI characters that are non UTF-8 - * may be parsed as valid UTF-8. A recent non-scientific analysis found - * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a - * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0% - * false reading. - *

- * The percent "%" character always has the reserved purpose of being - * the escape indicator, it must be escaped as "%25" in order to be used - * as data within a URI. - *

- * The unescape method is internally performed within this method. - * - * @param component the URI character sequence - * @param charset the protocol charset - * @return original character sequence - * @exception IOException incomplete trailing escape pattern - * or unsupported character encoding - */ - protected static String decode(char[] component, String charset) - throws IOException { - - // unescape uri characters to octets - if (component == null) return null; - - byte[] octets; - try { - octets = new String(component).getBytes(charset); - } catch (UnsupportedEncodingException error) { - throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */ - "URI: not supported " + charset + " encoding"); - } - int length = octets.length; - int oi = 0; // output index - for (int ii = 0; ii < length; oi++) { - byte aByte = (byte) octets[ii++]; - if (aByte == '%' && ii+2 <= length) { - byte high = (byte) Character.digit((char) octets[ii++], 16); - byte low = (byte) Character.digit((char) octets[ii++], 16); - if (high == -1 || low == -1) { - throw new IOException(/* IOException.ESCAPING, */ - "URI: incomplete trailing escape pattern"); - - } - aByte = (byte) ((high << 4) + low); - } - octets[oi] = (byte) aByte; - } - - String result; - try { - result = new String(octets, 0, oi, charset); - } catch (UnsupportedEncodingException error) { - throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */ - "URI: not supported " + charset + " encoding"); - } - - return result; - } - - - /** - * Pre-validate the unescaped URI string within a specific component. - * - * @param component the component string within the component - * @param disallowed those characters disallowed within the component - * @return if true, it doesn't have the disallowed characters - * if false, the component is undefined or an incorrect one - */ - protected boolean prevalidate(String component, BitSet disallowed) { - // prevalidate the given component by disallowed characters - if (component == null) { - return false; // undefined - } - char[] target = component.toCharArray(); - for (int i = 0; i < target.length; i++) { - if (disallowed.get(target[i])) { - return false; - } - } - return true; - } - - - /** - * Validate the URI characters within a specific component. - * The component must be performed after escape encoding. Or it doesn't - * include escaped characters. - * - * @param component the characters sequence within the component - * @param generous those characters that are allowed within a component - * @return if true, it's the correct URI character sequence - */ - protected boolean validate(char[] component, BitSet generous) { - // validate each component by generous characters - return validate(component, 0, -1, generous); - } - - - /** - * Validate the URI characters within a specific component. - * The component must be performed after escape encoding. Or it doesn't - * include escaped characters. - *

- * It's not that much strict, generous. The strict validation might be - * performed before being called this method. - * - * @param component the characters sequence within the component - * @param soffset the starting offset of the given component - * @param eoffset the ending offset of the given component - * if -1, it means the length of the component - * @param generous those characters that are allowed within a component - * @return if true, it's the correct URI character sequence - * @throws NullPointerException null component - */ - protected boolean validate(char[] component, int soffset, int eoffset, - BitSet generous) { - // validate each component by generous characters - if (eoffset == -1) { - eoffset = component.length -1; - } - for (int i = soffset; i <= eoffset; i++) { - if (!generous.get(component[i])) return false; - } - return true; - } - - - /** - * In order to avoid any possilbity of conflict with non-ASCII characters, - * Parse a URI reference as a String with the character - * encoding of the local system or the document. - *

- * The following line is the regular expression for breaking-down a URI - * reference into its components. - *

-     *   ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-     *    12            3  4          5       6  7        8 9
-     * 

- * For example, matching the above expression to - * http://jakarta.apache.org/ietf/uri/#Related - * results in the following subexpression matches: - *

-     *               $1 = http:
-     *  scheme    =  $2 = http
-     *               $3 = //jakarta.apache.org
-     *  authority =  $4 = jakarta.apache.org
-     *  path      =  $5 = /ietf/uri/
-     *               $6 = 
-     *  query     =  $7 = 
-     *               $8 = #Related
-     *  fragment  =  $9 = Related
-     * 

- * - * @param original the original character sequence - * @param escaped true if original is escaped - * @return the original character sequence - * @exception IOException - */ - protected void parseUriReference(String original, boolean escaped) - throws IOException { - - // validate and contruct the URI character sequence - if (original == null || original.length() == 0) { - throw new IOException("URI-Reference required"); - } - - /** @ - * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? - */ - String tmp = original.trim(); - - /** - * The length of the string sequence of characters. - * It may not be equal to the length of the byte array. - */ - int length = tmp.length(); - - /** - * Remove the delimiters like angle brackets around an URI. - */ - char[] firstDelimiter = { tmp.charAt(0) }; - if (validate(firstDelimiter, delims)) { - if (length >= 2) { - char[] lastDelimiter = { tmp.charAt(length - 1) }; - if (validate(lastDelimiter, delims)) { - tmp = tmp.substring(1, length - 1); - length = length - 2; - } - } - } - - /** - * The starting index - */ - int from = 0; - - /** - * The test flag whether the URI is started from the path component. - */ - boolean isStartedFromPath = false; - int atColon = tmp.indexOf(':'); - int atSlash = tmp.indexOf('/'); - if (atColon < 0 || (atSlash >= 0 && atSlash < atColon)) { - isStartedFromPath = true; - } - - /** - *

-         *     @@@@@@@@
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - int at = indexFirstOf(tmp, isStartedFromPath ? "/?#" : ":/?#", from); - if (at == -1) at = 0; - - /** - * Parse the scheme. - *

-         *  scheme    =  $2 = http
-         *              @
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - if (at < length && tmp.charAt(at) == ':') { - char[] target = tmp.substring(0, at).toLowerCase().toCharArray(); - if (validate(target, scheme)) { - _scheme = target; - } else { - throw new IOException("incorrect scheme"); - } - from = ++at; - } - - /** - * Parse the authority component. - *

-         *  authority =  $4 = jakarta.apache.org
-         *                  @@
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - // Reset flags - _is_net_path = _is_abs_path = _is_rel_path = _is_hier_part = false; - if (0 <= at && at < length && tmp.charAt(at) == '/') { - // Set flag - _is_hier_part = true; - if (at + 2 < length && tmp.charAt(at + 1) == '/') { - // the temporary index to start the search from - int next = indexFirstOf(tmp, "/?#", at + 2); - if (next == -1) { - next = (tmp.substring(at + 2).length() == 0) ? at + 2 : - tmp.length(); - } - parseAuthority(tmp.substring(at + 2, next), escaped); - from = at = next; - // Set flag - _is_net_path = true; - } - if (from == at) { - // Set flag - _is_abs_path = true; - } - } - - /** - * Parse the path component. - *

-         *  path      =  $5 = /ietf/uri/
-         *                                @@@@@@
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - if (from < length) { - // rel_path = rel_segment [ abs_path ] - int next = indexFirstOf(tmp, "?#", from); - if (next == -1) { - next = tmp.length(); - } - if (!_is_abs_path) { - if (!escaped && prevalidate(tmp.substring(from, next), - disallowed_rel_path) || escaped && - validate(tmp.substring(from, next).toCharArray(), - rel_path)) { - // Set flag - _is_rel_path = true; - } else if (!escaped && prevalidate(tmp.substring(from, next), - disallowed_opaque_part) || escaped && - validate(tmp.substring(from, next).toCharArray(), - opaque_part)) { - // Set flag - _is_opaque_part = true; - } else { - // the path component may be empty - _path = null; - } - } - setPath(tmp.substring(from, next)); - at = next; - } - - /** - * Parse the query component. - *

-         *  query     =  $7 = 
-         *                                        @@@@@@@@@
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - if (0 <= at && at+1 < length && tmp.charAt(at) == '?') { - int next = tmp.indexOf('#', at + 1); - if (next == -1) { - next = tmp.length(); - } - _query = (escaped) ? tmp.substring(at + 1, next).toCharArray() : - encode(tmp.substring(at + 1, next), allowed_query); - at = next; - } - - /** - * Parse the fragment component. - *

-         *  fragment  =  $9 = Related
-         *                                                   @@@@@@@@
-         *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
-         * 

- */ - if (0 <= at && at+1 < length && tmp.charAt(at) == '#') { - _fragment = (escaped) ? tmp.substring(at + 1).toCharArray() : - encode(tmp.substring(at + 1), allowed_fragment); - } - - // set this URI. - setUriReference(); - } - - - /** - * Get the earlier index that to be searched for the first occurrance in - * one of any of the given string. - * - * @param s the string to be indexed - * @param delims the delimiters used to index - * @return the earlier index if there are delimiters - */ - protected int indexFirstOf(String s, String delims) { - return indexFirstOf(s, delims, -1); - } - - - /** - * Get the earlier index that to be searched for the first occurrance in - * one of any of the given string. - * - * @param s the string to be indexed - * @param delims the delimiters used to index - * @param offset the from index - * @return the earlier index if there are delimiters - */ - protected int indexFirstOf(String s, String delims, int offset) { - if (s == null || s.length() == 0) { - return -1; - } - if (delims == null || delims.length() == 0) { - return -1; - } - // check boundaries - if (offset < 0) { - offset = 0; - } else if (offset > s.length()) { - return -1; - } - // s is never null - int min = s.length(); - char[] delim = delims.toCharArray(); - for (int i = 0; i < delim.length; i++) { - int at = s.indexOf(delim[i], offset); - if (at >= 0 && at < min) { - min = at; - } - } - return (min == s.length()) ? -1 : min; - } - - - /** - * Get the earlier index that to be searched for the first occurrance in - * one of any of the given array. - * - * @param s the character array to be indexed - * @param delim the delimiter used to index - * @return the ealier index if there are a delimiter - */ - protected int indexFirstOf(char[] s, char delim) { - return indexFirstOf(s, delim, 0); - } - - - /** - * Get the earlier index that to be searched for the first occurrance in - * one of any of the given array. - * - * @param s the character array to be indexed - * @param delim the delimiter used to index - * @return the ealier index if there is a delimiter - */ - protected int indexFirstOf(char[] s, char delim, int offset) { - if (s == null || s.length == 0) { - return -1; - } - // check boundaries - if (offset < 0) { - offset = 0; - } else if (offset > s.length) { - return -1; - } - for (int i = offset; i < s.length; i++) { - if (s[i] == delim) { - return i; - } - } - return -1; - } - - - /** - * Parse the authority component. - * - * @param original the original character sequence of authority component - * @param escaped true if original is escaped - * @exception IOException - */ - protected void parseAuthority(String original, boolean escaped) - throws IOException { - - // Reset flags - _is_reg_name = _is_server = - _is_hostname = _is_IPv4address = _is_IPv6reference = false; - - boolean has_port = true; - int from = 0; - int next = original.indexOf('@'); - if (next != -1) { // neither -1 and 0 - // each protocol extented from URI supports the specific userinfo - _userinfo = (escaped) ? original.substring(0, next).toCharArray() : - encode(original.substring(0, next), allowed_userinfo); - from = next + 1; - } - next = original.indexOf('[', from); - if (next >= from) { - next = original.indexOf(']', from); - if (next == -1) { - throw new IOException(/* IOException.PARSING,*/ "URI: IPv6reference"); - } else { - next++; - } - // In IPv6reference, '[', ']' should be excluded - _host = (escaped) ? original.substring(from, next).toCharArray() : - encode(original.substring(from, next), allowed_IPv6reference); - // Set flag - _is_IPv6reference = true; - } else { // only for !_is_IPv6reference - next = original.indexOf(':', from); - if (next == -1) { - next = original.length(); - has_port = false; - } - // REMINDME: it doesn't need the pre-validation - _host = original.substring(from, next).toCharArray(); - if (validate(_host, IPv4address)) { - // Set flag - _is_IPv4address = true; - } else if (validate(_host, hostname)) { - // Set flag - _is_hostname = true; - } else { - // Set flag - _is_reg_name = true; - } - } - if (_is_reg_name) { - // Reset flags for a server-based naming authority - _is_server = _is_hostname = _is_IPv4address = - _is_IPv6reference = false; - // set a registry-based naming authority - _authority = (escaped) ? original.toString().toCharArray() : - encode(original.toString(), allowed_reg_name); - } else { - if (original.length()-1 > next && has_port && - original.charAt(next) == ':') { // not empty - from = next + 1; - try { - _port = Integer.parseInt(original.substring(from)); - } catch (NumberFormatException error) { - throw new IOException(/*IOException.PARSING, */ - "URI: invalid port number"); - } - } - // set a server-based naming authority - StringBuffer buf = new StringBuffer(); - if (_userinfo != null) { // has_userinfo - buf.append(_userinfo); - buf.append('@'); - } - if (_host != null) { - buf.append(_host); - if (_port != -1) { - buf.append(':'); - buf.append(_port); - } - } - _authority = buf.toString().toCharArray(); - // Set flag - _is_server = true; - } - } - - - /** - * Once it's parsed successfully, set this URI. - * - * @see #getRawURI - */ - protected void setUriReference() { - // set _uri - StringBuffer buf = new StringBuffer(); - // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? - if (_scheme != null) { - buf.append(_scheme); - buf.append(':'); - } - if (_is_net_path) { - buf.append("//"); - if (_authority != null) { // has_authority - if (_userinfo != null) { // by default, remove userinfo part - if (_host != null) { - buf.append(_host); - if (_port != -1) { - buf.append(':'); - buf.append(_port); - } - } - } else { - buf.append(_authority); - } - } - } - if (_opaque != null && _is_opaque_part) { - buf.append(_opaque); - } else if (_path != null) { - // _is_hier_part or _is_relativeURI - if (_path.length != 0) { - buf.append(_path); - } - } - if (_query != null) { // has_query - buf.append('?'); - buf.append(_query); - } - if (_fragment != null) { // has_fragment - buf.append('#'); - buf.append(_fragment); - } - - _uri = buf.toString().toCharArray(); - } - - // ----------------------------------------------------------- Test methods - - - /** - * Tell whether or not this URI is absolute. - * - * @return true iif this URI is absoluteURI - */ - public boolean isAbsoluteURI() { - return (_scheme != null); - } - - - /** - * Tell whether or not this URI is relative. - * - * @return true iif this URI is relativeURI - */ - public boolean isRelativeURI() { - return (_scheme == null); - } - - - /** - * Tell whether or not the absoluteURI of this URI is hier_part. - * - * @return true iif the absoluteURI is hier_part - */ - public boolean isHierPart() { - return _is_hier_part; - } - - - /** - * Tell whether or not the absoluteURI of this URI is opaque_part. - * - * @return true iif the absoluteURI is opaque_part - */ - public boolean isOpaquePart() { - return _is_opaque_part; - } - - - /** - * Tell whether or not the relativeURI or heir_part of this URI is net_path. - * It's the same function as the has_authority() method. - * - * @return true iif the relativeURI or heir_part is net_path - * @see #hasAuthority - */ - public boolean isNetPath() { - return _is_net_path || (_authority != null); - } - - - /** - * Tell whether or not the relativeURI or hier_part of this URI is abs_path. - * - * @return true iif the relativeURI or hier_part is abs_path - */ - public boolean isAbsPath() { - return _is_abs_path; - } - - - /** - * Tell whether or not the relativeURI of this URI is rel_path. - * - * @return true iif the relativeURI is rel_path - */ - public boolean isRelPath() { - return _is_rel_path; - } - - - /** - * Tell whether or not this URI has authority. - * It's the same function as the is_net_path() method. - * - * @return true iif this URI has authority - * @see #isNetPath - */ - public boolean hasAuthority() { - return (_authority != null) || _is_net_path; - } - - /** - * Tell whether or not the authority component of this URI is reg_name. - * - * @return true iif the authority component is reg_name - */ - public boolean isRegName() { - return _is_reg_name; - } - - - /** - * Tell whether or not the authority component of this URI is server. - * - * @return true iif the authority component is server - */ - public boolean isServer() { - return _is_server; - } - - - /** - * Tell whether or not this URI has userinfo. - * - * @return true iif this URI has userinfo - */ - public boolean hasUserinfo() { - return (_userinfo != null); - } - - - /** - * Tell whether or not the host part of this URI is hostname. - * - * @return true iif the host part is hostname - */ - public boolean isHostname() { - return _is_hostname; - } - - - /** - * Tell whether or not the host part of this URI is IPv4address. - * - * @return true iif the host part is IPv4address - */ - public boolean isIPv4address() { - return _is_IPv4address; - } - - - /** - * Tell whether or not the host part of this URI is IPv6reference. - * - * @return true iif the host part is IPv6reference - */ - public boolean isIPv6reference() { - return _is_IPv6reference; - } - - - /** - * Tell whether or not this URI has query. - * - * @return true iif this URI has query - */ - public boolean hasQuery() { - return (_query != null); - } - - - /** - * Tell whether or not this URI has fragment. - * - * @return true iif this URI has fragment - */ - public boolean hasFragment() { - return (_fragment != null); - } - - - // ---------------------------------------------------------------- Charset - - - /** - * Set the default charset of the protocol. - *

- * The character set used to store files SHALL remain a local decision and - * MAY depend on the capability of local operating systems. Prior to the - * exchange of URIs they SHOULD be converted into a ISO/IEC 10646 format - * and UTF-8 encoded. This approach, while allowing international exchange - * of URIs, will still allow backward compatibility with older systems - * because the code set positions for ASCII characters are identical to the - * one byte sequence in UTF-8. - *

- * An individual URI scheme may require a single charset, define a default - * charset, or provide a way to indicate the charset used. - * - * @param charset the default charset for each protocol - */ - public static void setProtocolCharset(String charset) { - _protocolCharset = charset; - } - - - /** - * Get the default charset of the protocol. - *

- * An individual URI scheme may require a single charset, define a default - * charset, or provide a way to indicate the charset used. - *

- * To work globally either requires support of a number of character sets - * and to be able to convert between them, or the use of a single preferred - * character set. - * For support of global compatibility it is STRONGLY RECOMMENDED that - * clients and servers use UTF-8 encoding when exchanging URIs. - * - * @return the charset string - */ - public static String getProtocolCharset() { - return _protocolCharset; - } - - - /** - * Set the default charset of the document. - *

- * Notice that it will be possible to contain mixed characters (e.g. - * ftp://host/KoreanNamespace/ChineseResource). To handle the Bi-directional - * display of these character sets, the protocol charset could be simply - * used again. Because it's not yet implemented that the insertion of BIDI - * control characters at different points during composition is extracted. - * - * @param charset the default charset for the document - */ - public static void setDocumentCharset(String charset) { - _documentCharset = charset; - } - - - /** - * Get the default charset of the document. - * - * @return the charset string - */ - public static String getDocumentCharset() { - return _documentCharset; - } - - // ------------------------------------------------------------- The scheme - - /** - * Get the scheme. - * - * @return the scheme - */ - public char[] getRawScheme() { - return _scheme; - } - - - /** - * Get the scheme. - * - * @return the scheme - * null if undefined scheme - */ - public String getScheme() { - return (_scheme == null) ? null : new String(_scheme); - } - - // ---------------------------------------------------------- The authority - - /** - * Set the authority. It can be one type of server, hostport, hostname, - * IPv4address, IPv6reference and reg_name. - *

-     *   authority     = server | reg_name
-     * 

- * - * @param escapedAuthority the raw escaped authority - * @exception IOException - * @throws NullPointerException null authority - */ - public void setRawAuthority(char[] escapedAuthority) throws IOException { - parseAuthority(new String(escapedAuthority), true); - setUriReference(); - } - - - /** - * Set the authority. It can be one type of server, hostport, hostname, - * IPv4address, IPv6reference and reg_name. - * Note that there is no setAuthority method by the escape encoding reason. - * - * @param escapedAuthority the escaped authority string - * @exception IOException - */ - public void setEscapedAuthority(String escapedAuthority) - throws IOException { - - parseAuthority(escapedAuthority, true); - setUriReference(); - } - - - /** - * Get the raw-escaped authority. - * - * @return the raw-escaped authority - */ - public char[] getRawAuthority() { - return _authority; - } - - - /** - * Get the escaped authority. - * - * @return the escaped authority - */ - public String getEscapedAuthority() { - return (_authority == null) ? null : new String(_authority); - } - - - /** - * Get the authority. - * - * @return the authority - * @exception IOException - * @see #decode - */ - public String getAuthority() throws IOException { - return (_authority == null) ? null : decode(_authority); - } - - // ----------------------------------------------------------- The userinfo - - /** - * Get the raw-escaped userinfo. - * - * @return the raw-escaped userinfo - * @see #getAuthority - */ - public char[] getRawUserinfo() { - return _userinfo; - } - - - /** - * Get the escaped userinfo. - * - * @return the escaped userinfo - * @see #getAuthority - */ - public String getEscapedUserinfo() { - return (_userinfo == null) ? null : new String(_userinfo); - } - - - /** - * Get the userinfo. - * - * @return the userinfo - * @exception IOException - * @see #decode - * @see #getAuthority - */ - public String getUserinfo() throws IOException { - return (_userinfo == null) ? null : decode(_userinfo); - } - - // --------------------------------------------------------------- The host - - /** - * Get the host. - *

-     *   host          = hostname | IPv4address | IPv6reference
-     * 

- * - * @return the host - * @see #getAuthority - */ - public char[] getRawHost() { - return _host; - } - - - /** - * Get the host. - *

-     *   host          = hostname | IPv4address | IPv6reference
-     * 

- * - * @return the host - * @exception IOException - * @see #decode - * @see #getAuthority - */ - public String getHost() throws IOException { - return decode(_host); - } - - // --------------------------------------------------------------- The port - - /** - * Get the port. In order to get the specfic default port, the specific - * protocol-supported class extended from the URI class should be used. - * It has the server-based naming authority. - * - * @return the port - * if -1, it has the default port for the scheme or the server-based - * naming authority is not supported in the specific URI. - */ - public int getPort() { - return _port; - } - - // --------------------------------------------------------------- The path - - /** - * Set the path. The method couldn't be used by API programmers. - * - * @param path the path string - * @exception IOException set incorrectly or fragment only - * @see #encode - */ - protected void setPath(String path) throws IOException { - - // set path - if (_is_net_path || _is_abs_path) { - _path = encode(path, allowed_abs_path); - } else if (_is_rel_path) { - StringBuffer buff = new StringBuffer(path.length()); - int at = path.indexOf('/'); - if (at > 0) { // never 0 - buff.append(encode(path.substring(0, at), allowed_rel_path)); - buff.append(encode(path.substring(at), allowed_abs_path)); - } else { - buff.append(encode(path, allowed_rel_path)); - } - _path = buff.toString().toCharArray(); - } else if (_is_opaque_part) { - _opaque = encode(path, allowed_opaque_part); - } else { - throw new IOException(/*IOException.PARSING, */"URI: incorrect path"); - } - } - - - /** - * Resolve the base and relative path. - * - * @param base_path a character array of the base_path - * @param rel_path a character array of the rel_path - * @return the resolved path - */ - protected char[] resolvePath(char[] base_path, char[] rel_path) { - - // REMINDME: paths are never null - String base = (base_path == null) ? "" : new String(base_path); - int at = base.lastIndexOf('/'); - if (at != -1) { - base_path = base.substring(0, at + 1).toCharArray(); - } - // _path could be empty - if (rel_path == null || rel_path.length == 0) { - return normalize(base_path); - } else if (rel_path[0] == '/') { - return rel_path; - } else { - StringBuffer buff = new StringBuffer(base.length() + - rel_path.length); - if (at != -1) { - buff.append(base.substring(0, at + 1)); - buff.append(rel_path); - } - return normalize(buff.toString().toCharArray()); - } - } - - - /** - * Get the raw-escaped current hierarchy level in the given path. - * If the last namespace is a collection, the slash mark ('/') should be - * ended with at the last character of the path string. - * - * @param path the path - * @return the current hierarchy level - * @exception IOException no hierarchy level - */ - protected char[] getRawCurrentHierPath(char[] path) throws IOException { - - if (_is_opaque_part) { - throw new IOException(/*IOException.PARSING,*/ "URI: no hierarchy level"); - } - if (path == null) { - throw new IOException(/*IOException.PARSING,*/ "URI: emtpy path"); - } - String buff = new String(path); - int first = buff.indexOf('/'); - int last = buff.lastIndexOf('/'); - if (last == 0) { - return rootPath; - } else if (first != last && last != -1) { - return buff.substring(0, last).toCharArray(); - } - // FIXME: it could be a document on the server side - return path; - } - - - /** - * Get the raw-escaped current hierarchy level. - * - * @return the raw-escaped current hierarchy level - * @exception IOException no hierarchy level - */ - public char[] getRawCurrentHierPath() throws IOException { - return (_path == null) ? null : getRawCurrentHierPath(_path); - } - - - /** - * Get the escaped current hierarchy level. - * - * @return the escaped current hierarchy level - * @exception IOException no hierarchy level - */ - public String getEscapedCurrentHierPath() throws IOException { - char[] path = getRawCurrentHierPath(); - return (path == null) ? null : new String(path); - } - - - /** - * Get the current hierarchy level. - * - * @return the current hierarchy level - * @exception IOException - * @see #decode - */ - public String getCurrentHierPath() throws IOException { - char[] path = getRawCurrentHierPath(); - return (path == null) ? null : decode(path); - } - - - /** - * Get the level above the this hierarchy level. - * - * @return the raw above hierarchy level - * @exception IOException - */ - public char[] getRawAboveHierPath() throws IOException { - char[] path = getRawCurrentHierPath(); - return (path == null) ? null : getRawCurrentHierPath(path); - } - - - /** - * Get the level above the this hierarchy level. - * - * @return the raw above hierarchy level - * @exception IOException - */ - public String getEscapedAboveHierPath() throws IOException { - char[] path = getRawAboveHierPath(); - return (path == null) ? null : new String(path); - } - - - /** - * Get the level above the this hierarchy level. - * - * @return the above hierarchy level - * @exception IOException - * @see #decode - */ - public String getAboveHierPath() throws IOException { - char[] path = getRawAboveHierPath(); - return (path == null) ? null : decode(path); - } - - - /** - * Get the raw-escaped path. - *

-     *   path          = [ abs_path | opaque_part ]
-     * 

- * - * @return the raw-escaped path - */ - public char[] getRawPath() { - return _is_opaque_part ? _opaque : _path; - } - - - /** - * Get the escaped path. - *

-     *   path          = [ abs_path | opaque_part ]
-     *   abs_path      = "/"  path_segments 
-     *   opaque_part   = uric_no_slash *uric
-     * 

- * - * @return the escaped path string - */ - public String getEscapedPath() { - char[] path = getRawPath(); - return (path == null) ? null : new String(path); - } - - - /** - * Get the path. - *

-     *   path          = [ abs_path | opaque_part ]
-     * 

- * @return the path string - * @exception IOException - * @see #decode - */ - public String getPath() throws IOException { - char[] path = getRawPath(); - return (path == null) ? null : decode(path); - } - - - /** - * Get the raw-escaped basename of the path. - * - * @return the raw-escaped basename - */ - public char[] getRawName() { - if (_path == null) return null; - - int at = 0; - for (int i = _path.length - 1; i >= 0; i--) { - if (_path[i] == '/') { - at = i + 1; - break; - } - } - int len = _path.length - at; - char[] basename = new char[len]; - System.arraycopy(_path, at, basename, 0, len); - return basename; - } - - - /** - * Get the escaped basename of the path. - * - * @return the escaped basename string - */ - public String getEscapedName() { - char[] basename = getRawName(); - return (basename == null) ? null : new String(basename); - } - - - /** - * Get the basename of the path. - * - * @return the basename string - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @see #decode - */ - public String getName() throws IOException { - char[] basename = getRawName(); - return (basename == null) ? null : decode(getRawName()); - } - - // ----------------------------------------------------- The path and query - - /** - * Get the raw-escaped path and query. - * - * @return the raw-escaped path and query - */ - public char[] getRawPathQuery() { - - if (_path == null && _query == null) { - return null; - } - StringBuffer buff = new StringBuffer(); - if (_path != null) { - buff.append(_path); - } - if (_query != null) { - buff.append('?'); - buff.append(_query); - } - return buff.toString().toCharArray(); - } - - - /** - * Get the escaped query. - * - * @return the escaped path and query string - */ - public String getEscapedPathQuery() { - char[] rawPathQuery = getRawPathQuery(); - return (rawPathQuery == null) ? null : new String(rawPathQuery); - } - - - /** - * Get the path and query. - * - * @return the path and query string. - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @see #decode - */ - public String getPathQuery() throws IOException { - char[] rawPathQuery = getRawPathQuery(); - return (rawPathQuery == null) ? null : decode(rawPathQuery); - } - - // -------------------------------------------------------------- The query - - /** - * Set the raw-escaped query. - * - * @param escapedQuery the raw-escaped query - * @exception IOException escaped query not valid - * @throws NullPointerException null query - */ - public void setRawQuery(char[] escapedQuery) throws IOException { - if (!validate(escapedQuery, query)) - throw new IOException(/*IOException.ESCAPING,*/ - "URI: escaped query not valid"); - _query = escapedQuery; - setUriReference(); - } - - - /** - * Set the escaped query string. - * - * @param escapedQuery the escaped query string - * @exception IOException escaped query not valid - * @throws NullPointerException null query - */ - public void setEscapedQuery(String escapedQuery) throws IOException { - setRawQuery(escapedQuery.toCharArray()); - } - - - /** - * Set the query. - * When a query string is not misunderstood the reserved special characters - * ("&", "=", "+", ",", and "$") within a query component, it is - * recommended to use in encoding the whole query with this method. - * - * @param query the query string. - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @throws NullPointerException null query - * @see #encode - */ - public void setQuery(String query) throws IOException { - setRawQuery(encode(query, allowed_query)); - } - - - /** - * Get the raw-escaped query. - * - * @return the raw-escaped query - */ - public char[] getRawQuery() { - return _query; - } - - - /** - * Get the escaped query. - * - * @return the escaped query string - */ - public String getEscapedQuery() { - return (_query == null) ? null : new String(_query); - } - - - /** - * Get the query. - * - * @return the query string. - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @see #decode - */ - public String getQuery() throws IOException { - return (_query == null) ? null : decode(_query); - } - - // ----------------------------------------------------------- The fragment - - /** - * Set the raw-escaped fragment. - * - * @param escapedFragment the raw-escaped fragment - * @exception IOException escaped fragment not valid - * @throws NullPointerException null fragment - */ - public void setRawFragment(char[] escapedFragment) throws IOException { - if (!validate(escapedFragment, fragment)) - throw new IOException(/*IOException.ESCAPING,*/ - "URI: escaped fragment not valid"); - _fragment = escapedFragment; - setUriReference(); - } - - - /** - * Set the escaped fragment string. - * - * @param escapedFragment the escaped fragment string - * @exception IOException escaped fragment not valid - * @throws NullPointerException null fragment - */ - public void setEscapedFragment(String escapedFragment) throws IOException { - char[] fragmentSequence = escapedFragment.toCharArray(); - if (!validate(fragmentSequence, fragment)) - throw new IOException(/*IOException.ESCAPING,*/ - "URI: escaped fragment not valid"); - _fragment = fragmentSequence; - setUriReference(); - } - - - /** - * Set the fragment. - * - * @param the fragment string. - * @exception IOException - * Or unsupported character encoding - * @throws NullPointerException null fragment - */ - public void setFragment(String fragment) throws IOException { - _fragment = encode(fragment, allowed_fragment); - setUriReference(); - } - - - /** - * Get the raw-escaped fragment. - *

- * The optional fragment identifier is not part of a URI, but is often used - * in conjunction with a URI. - *

- * The format and interpretation of fragment identifiers is dependent on - * the media type [RFC2046] of the retrieval result. - *

- * A fragment identifier is only meaningful when a URI reference is - * intended for retrieval and the result of that retrieval is a document - * for which the identified fragment is consistently defined. - * - * @return the raw-escaped fragment - */ - public char[] getRawFragment() { - return _fragment; - } - - - /** - * Get the escaped fragment. - * - * @return the escaped fragment string - */ - public String getEscapedFragment() { - return (_fragment == null) ? null : new String(_fragment); - } - - - /** - * Get the fragment. - * - * @return the fragment string - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @see #decode - */ - public String getFragment() throws IOException { - return (_fragment == null) ? null : decode(_fragment); - } - - // ------------------------------------------------------------- Utilities - - /** - * Normalize the given hier path part. - * - * @param path the path to normalize - * @return the normalized path - */ - protected char[] normalize(char[] path) { - - if (path == null) return null; - - String normalized = new String(path); - boolean endsWithSlash = true; - // precondition - if (!normalized.endsWith("/")) { - normalized += '/'; - endsWithSlash = false; - } - if (normalized.endsWith("/./") || normalized.endsWith("/../")) { - endsWithSlash = true; - } - // Resolve occurrences of "/./" in the normalized path - while (true) { - int at = normalized.indexOf("/./"); - if (at == -1) { - break; - } - normalized = normalized.substring(0, at) + - normalized.substring(at + 2); - } - // Resolve occurrences of "/../" in the normalized path - while (true) { - int at = normalized.indexOf("/../"); - if (at == -1) { - break; - } - if (at == 0) { - normalized = "/"; - break; - } - int backward = normalized.lastIndexOf('/', at - 1); - if (backward == -1) { - // consider the rel_path - normalized = normalized.substring(at + 4); - } else { - normalized = normalized.substring(0, backward) + - normalized.substring(at + 3); - } - } - // Resolve occurrences of "//" in the normalized path - while (true) { - int at = normalized.indexOf("//"); - if (at == -1) { - break; - } - normalized = normalized.substring(0, at) + - normalized.substring(at + 1); - } - if (!endsWithSlash && normalized.endsWith("/")) { - normalized = normalized.substring(0, normalized.length()-1); - } else if (endsWithSlash && !normalized.endsWith("/")) { - normalized = normalized + "/"; - } - // Set the normalized path that we have completed - return normalized.toCharArray(); - } - - - /** - * Normalize the path part of this URI. - */ - public void normalize() { - _path = normalize(_path); - } - - - /** - * Test if the first array is equal to the second array. - * - * @param first the first character array - * @param second the second character array - * @return true if they're equal - */ - protected boolean equals(char[] first, char[] second) { - - if (first == null && second == null) { - return true; - } - if (first == null || second == null) { - return false; - } - if (first.length != second.length) { - return false; - } - for (int i = 0; i < first.length; i++) { - if (first[i] != second[i]) { - return false; - } - } - return true; - } - - - /** - * Test an object if this URI is equal to another. - * - * @param obj an object to compare - * @return true if two URI objects are equal - */ - public boolean equals(Object obj) { - - // normalize and test each components - if (obj == this) { - return true; - } - if (!(obj instanceof URI)) { - return false; - } - URI another = (URI) obj; - // scheme - if (!equals(_scheme, another._scheme)) { - return false; - } - // is_opaque_part or is_hier_part? and opaque - if (!equals(_opaque, another._opaque)) { - return false; - } - // is_hier_part - // has_authority - if (!equals(_authority, another._authority)) { - return false; - } - // path - if (!equals(_path, another._path)) { - return false; - } - // has_query - if (!equals(_query, another._query)) { - return false; - } - // has_fragment? should be careful of the only fragment case. - if (!equals(_fragment, another._fragment)) { - return false; - } - return true; - } - - // ---------------------------------------------------------- Serialization - - /** - * Write the content of this URI. - * - * @param oos the object-output stream - */ - protected void writeObject(java.io.ObjectOutputStream oos) - throws IOException { - - oos.defaultWriteObject(); - } - - - /** - * Read a URI. - * - * @param ois the object-input stream - */ - protected void readObject(java.io.ObjectInputStream ois) - throws ClassNotFoundException, IOException { - - ois.defaultReadObject(); - } - - // ------------------------------------------------------------- Comparison - - /** - * Compare this URI to another object. - * - * @param obj the object to be compared. - * @return 0, if it's same, - * -1, if failed, first being compared with in the authority component - * @exception ClassCastException not URI argument - * @throws NullPointerException null object - */ - public int compareTo(Object obj) { - - URI another = (URI) obj; - if (!equals(_authority, another.getRawAuthority())) return -1; - return toString().compareTo(another.toString()); - } - - // ------------------------------------------------------------------ Clone - - /** - * Create and return a copy of this object, the URI-reference containing - * the userinfo component. Notice that the whole URI-reference including - * the userinfo component counld not be gotten as a String. - *

- * To copy the identical URI object including the userinfo - * component, it should be used. - * - * @return a clone of this instance - */ - public synchronized Object clone() { - - URI instance = new URI(); - - instance._uri = _uri; - instance._scheme = _scheme; - instance._opaque = _opaque; - instance._authority = _authority; - instance._userinfo = _userinfo; - instance._host = _host; - instance._port = _port; - instance._path = _path; - instance._query = _query; - instance._fragment = _fragment; - // flags - instance._is_hier_part = _is_hier_part; - instance._is_opaque_part = _is_opaque_part; - instance._is_net_path = _is_net_path; - instance._is_abs_path = _is_abs_path; - instance._is_rel_path = _is_rel_path; - instance._is_reg_name = _is_reg_name; - instance._is_server = _is_server; - instance._is_hostname = _is_hostname; - instance._is_IPv4address = _is_IPv4address; - instance._is_IPv6reference = _is_IPv6reference; - - return instance; - } - - // ------------------------------------------------------------ Get the URI - - /** - * It can be gotten the URI character sequence. It's raw-escaped. - * For the purpose of the protocol to be transported, it will be useful. - *

- * It is clearly unwise to use a URL that contains a password which is - * intended to be secret. In particular, the use of a password within - * the 'userinfo' component of a URL is strongly disrecommended except - * in those rare cases where the 'password' parameter is intended to be - * public. - *

- * When you want to get each part of the userinfo, you need to use the - * specific methods in the specific URL. It depends on the specific URL. - * - * @return URI character sequence - */ - public char[] getRawURI() { - return _uri; - } - - - /** - * It can be gotten the URI character sequence. It's escaped. - * For the purpose of the protocol to be transported, it will be useful. - * - * @return the URI string - */ - public String getEscapedURI() { - return (_uri == null) ? null : new String(_uri); - } - - - /** - * It can be gotten the URI character sequence. - * - * @return the URI string - * @exception IOException incomplete trailing escape pattern - * Or unsupported character encoding - * @see #decode - */ - public String getURI() throws IOException { - return (_uri == null) ? null : decode(_uri); - } - - - /** - * Get the escaped URI string. - *

- * On the document, the URI-reference form is only used without the userinfo - * component like http://jakarta.apache.org/ by the security reason. - * But the URI-reference form with the userinfo component could be parsed. - *

- * In other words, this URI and any its subclasses must not expose the - * URI-reference expression with the userinfo component like - * http://user:password@hostport/restricted_zone.
- * It means that the API client programmer should extract each user and - * password to access manually. Probably it will be supported in the each - * subclass, however, not a whole URI-reference expression. - * - * @return the URI string - * @see #clone() - */ - public String toString() { - return getEscapedURI(); - } - - - // ------------------------------------------------------------ Inner class - - /** - * A mapping to determine the (somewhat arbitrarily) preferred charset for - * a given locale. Supports all locales recognized in JDK 1.1. - *

- * The distribution of this class is Servlets.com. It was originally - * written by Jason Hunter [jhunter at acm.org] and used by with permission. - */ - public static class LocaleToCharsetMap { - - private static Hashtable map; - static { - map = new Hashtable(); - map.put("ar", "ISO-8859-6"); - map.put("be", "ISO-8859-5"); - map.put("bg", "ISO-8859-5"); - map.put("ca", "ISO-8859-1"); - map.put("cs", "ISO-8859-2"); - map.put("da", "ISO-8859-1"); - map.put("de", "ISO-8859-1"); - map.put("el", "ISO-8859-7"); - map.put("en", "ISO-8859-1"); - map.put("es", "ISO-8859-1"); - map.put("et", "ISO-8859-1"); - map.put("fi", "ISO-8859-1"); - map.put("fr", "ISO-8859-1"); - map.put("hr", "ISO-8859-2"); - map.put("hu", "ISO-8859-2"); - map.put("is", "ISO-8859-1"); - map.put("it", "ISO-8859-1"); - map.put("iw", "ISO-8859-8"); - map.put("ja", "Shift_JIS"); - map.put("ko", "EUC-KR"); - map.put("lt", "ISO-8859-2"); - map.put("lv", "ISO-8859-2"); - map.put("mk", "ISO-8859-5"); - map.put("nl", "ISO-8859-1"); - map.put("no", "ISO-8859-1"); - map.put("pl", "ISO-8859-2"); - map.put("pt", "ISO-8859-1"); - map.put("ro", "ISO-8859-2"); - map.put("ru", "ISO-8859-5"); - map.put("sh", "ISO-8859-5"); - map.put("sk", "ISO-8859-2"); - map.put("sl", "ISO-8859-2"); - map.put("sq", "ISO-8859-2"); - map.put("sr", "ISO-8859-5"); - map.put("sv", "ISO-8859-1"); - map.put("tr", "ISO-8859-9"); - map.put("uk", "ISO-8859-5"); - map.put("zh", "GB2312"); - map.put("zh_TW", "Big5"); - } - - /** - * Get the preferred charset for the given locale. - * - * @param locale the locale - * @return the preferred charset - * or null if the locale is not recognized - */ - public static String getCharset(Locale locale) { - // try for an full name match (may include country) - String charset = (String) map.get(locale.toString()); - if (charset != null) return charset; - - // if a full name didn't match, try just the language - charset = (String) map.get(locale.getLanguage()); - return charset; // may be null - } - - } + // ----------------------------------------------------------- Constructors + + protected URI() { + } + + /** + * Construct a URI as an escaped form of a character array. An URI can be placed + * within double-quotes or angle brackets like "http://test.com/" and + * <http://test.com/> + * + * @param escaped the URI character sequence + * @exception IOException + * @throws NullPointerException if escaped is null + */ + public URI(char[] escaped) throws IOException { + parseUriReference(new String(escaped), true); + } + + /** + * Construct a URI from the given string. + *

+ *

+ * + *
+	 *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+	 * 
+ * + *
+ *

+ * An URI can be placed within double-quotes or angle brackets like + * "http://test.com/" and <http://test.com/> + * + * @param original the string to be represented to URI character sequence It is + * one of absoluteURI and relativeURI. + * @exception IOException + */ + public URI(String original) throws IOException { + parseUriReference(original, false); + } + + /** + * Construct a URI from a URL. + * + * @param url a valid URL. + * @throws IOException + * @since 2.0 + */ + public URI(URL url) throws IOException { + this(url.toString()); + } + + /** + * Construct a general URI from the given components. + *

+ *

+ * + *
+	 *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+	 *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
+	 *   opaque_part   = uric_no_slash *uric
+	 * 
+ * + *
+ *

+ * It's for absolute URI = <scheme>:<scheme-specific-part># + * <fragment>. + * + * @param scheme the scheme string + * @param scheme_specific_part scheme_specific_part + * @param fragment the fragment string + * @exception IOException + */ + public URI(String scheme, String scheme_specific_part, String fragment) throws IOException { + + // validate and contruct the URI character sequence + if (scheme == null) { + throw new IOException(/* IOException.PARSING, */ "URI: scheme required"); + } + char[] s = scheme.toLowerCase().toCharArray(); + if (validate(s, URI.scheme)) { + _scheme = s; // is_absoluteURI + } else { + throw new IOException(/* IOException.PARSING, */ "URI: incorrect scheme"); + } + _opaque = encode(scheme_specific_part, allowed_opaque_part); + // Set flag + _is_opaque_part = true; + setUriReference(); + } + + /** + * Construct a general URI from the given components. + *

+ *

+ * + *
+	 *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+	 *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
+	 *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
+	 *   hier_part     = ( net_path | abs_path ) [ "?" query ]
+	 * 
+ * + *
+ *

+ * It's for absolute URI = <scheme>:<path>?<query>#< + * fragment> and relative URI = <path>?<query>#<fragment >. + * + * @param scheme the scheme string + * @param authority the authority string + * @param path the path string + * @param query the query string + * @param fragment the fragment string + * @exception IOException + */ + public URI(String scheme, String authority, String path, String query, String fragment) throws IOException { + + // validate and contruct the URI character sequence + StringBuffer buff = new StringBuffer(); + if (scheme != null) { + buff.append(scheme); + buff.append(':'); + } + if (authority != null) { + buff.append("//"); + buff.append(authority); + } + if (path != null) { // accept empty path + if ((scheme != null || authority != null) && !path.startsWith("/")) { + throw new IOException(/* IOException.PARSING*, */ + "URI: abs_path requested"); + } + buff.append(path); + } + if (query != null) { + buff.append('?'); + buff.append(query); + } + if (fragment != null) { + buff.append('#'); + buff.append(fragment); + } + parseUriReference(buff.toString(), false); + } + + /** + * Construct a general URI from the given components. + * + * @param scheme the scheme string + * @param userinfo the userinfo string + * @param host the host string + * @param port the port number + * @exception IOException + */ + public URI(String scheme, String userinfo, String host, int port) throws IOException { + + this(scheme, userinfo, host, port, null, null, null); + } + + /** + * Construct a general URI from the given components. + * + * @param scheme the scheme string + * @param userinfo the userinfo string + * @param host the host string + * @param port the port number + * @param path the path string + * @exception IOException + */ + public URI(String scheme, String userinfo, String host, int port, String path) throws IOException { + + this(scheme, userinfo, host, port, path, null, null); + } + + /** + * Construct a general URI from the given components. + * + * @param scheme the scheme string + * @param userinfo the userinfo string + * @param host the host string + * @param port the port number + * @param path the path string + * @param query the query string + * @exception IOException + */ + public URI(String scheme, String userinfo, String host, int port, String path, String query) throws IOException { + + this(scheme, userinfo, host, port, path, query, null); + } + + /** + * Construct a general URI from the given components. + * + * @param scheme the scheme string + * @param userinfo the userinfo string + * @param host the host string + * @param port the port number + * @param path the path string + * @param query the query string + * @param fragment the fragment string + * @exception IOException + */ + public URI(String scheme, String userinfo, String host, int port, String path, String query, String fragment) + throws IOException { + + this(scheme, + (host == null) ? null + : ((userinfo != null) ? userinfo + '@' : "") + host + ((port != -1) ? ":" + port : ""), + path, query, fragment); + } + + /** + * Construct a general URI from the given components. + * + * @param scheme the scheme string + * @param host the host string + * @param path the path string + * @param fragment the fragment string + * @exception IOException + */ + public URI(String scheme, String host, String path, String fragment) throws IOException { + + this(scheme, host, path, null, fragment); + } + + /** + * Construct a general URI with the given relative URI string. + * + * @param base the base URI + * @param relative the relative URI string + * @exception IOException + */ + public URI(URI base, String relative) throws IOException { + this(base, new URI(relative)); + } + + /** + * Construct a general URI with the given relative URI. + *

+ *

+ * + *
+	 *   URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+	 *   relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
+	 * 
+ * + *
+ *

+ * Resolving Relative References to Absolute Form. + * + * Examples of Resolving Relative URI References + * + * Within an object with a well-defined base URI of + *

+ *

+ * + *
+	 *   http://a/b/c/d;p?q
+	 * 
+ * + *
+ *

+ * the relative URI would be resolved as follows: + * + * Normal Examples + * + *

+ *

+ * + *
+	 *   g:h           =  g:h
+	 *   g             =  http://a/b/c/g
+	 *   ./g           =  http://a/b/c/g
+	 *   g/            =  http://a/b/c/g/
+	 *   /g            =  http://a/g
+	 *   //g           =  http://g
+	 *   ?y            =  http://a/b/c/?y
+	 *   g?y           =  http://a/b/c/g?y
+	 *   #s            =  (current document)#s
+	 *   g#s           =  http://a/b/c/g#s
+	 *   g?y#s         =  http://a/b/c/g?y#s
+	 *   ;x            =  http://a/b/c/;x
+	 *   g;x           =  http://a/b/c/g;x
+	 *   g;x?y#s       =  http://a/b/c/g;x?y#s
+	 *   .             =  http://a/b/c/
+	 *   ./            =  http://a/b/c/
+	 *   ..            =  http://a/b/
+	 *   ../           =  http://a/b/
+	 *   ../g          =  http://a/b/g
+	 *   ../..         =  http://a/
+	 *   ../../        =  http://a/ 
+	 *   ../../g       =  http://a/g
+	 * 
+ * + *
+ *

+ * + * Some URI schemes do not allow a hierarchical syntax matching the + * syntax, and thus cannot use relative references. + * + * @param base the base URI + * @param relative the relative URI + * @exception IOException + */ + public URI(URI base, URI relative) throws IOException { + + if (base._scheme == null) { + throw new IOException(/* IOException.PARSING, */ "URI: base URI required"); + } + if (base._scheme != null) { + this._scheme = base._scheme; + this._authority = base._authority; + } + if (base._is_opaque_part || relative._is_opaque_part) { + this._scheme = base._scheme; + this._is_opaque_part = relative._is_opaque_part; + this._opaque = relative._opaque; + this._fragment = relative._fragment; + this.setUriReference(); + return; + } + if (relative._scheme != null) { + this._scheme = relative._scheme; + this._is_net_path = relative._is_net_path; + this._authority = relative._authority; + if (relative._is_server) { + this._userinfo = relative._userinfo; + this._host = relative._host; + this._port = relative._port; + } else if (relative._is_reg_name) { + this._is_reg_name = relative._is_reg_name; + } + this._is_abs_path = relative._is_abs_path; + this._is_rel_path = relative._is_rel_path; + this._path = relative._path; + } else if (base._authority != null && relative._scheme == null) { + this._is_net_path = base._is_net_path; + this._authority = base._authority; + if (base._is_server) { + this._userinfo = base._userinfo; + this._host = base._host; + this._port = base._port; + } else if (base._is_reg_name) { + this._is_reg_name = base._is_reg_name; + } + } + if (relative._authority != null) { + this._is_net_path = relative._is_net_path; + this._authority = relative._authority; + if (relative._is_server) { + this._is_server = relative._is_server; + this._userinfo = relative._userinfo; + this._host = relative._host; + this._port = relative._port; + } else if (relative._is_reg_name) { + this._is_reg_name = relative._is_reg_name; + } + this._is_abs_path = relative._is_abs_path; + this._is_rel_path = relative._is_rel_path; + this._path = relative._path; + } + // resolve the path + if (relative._scheme == null && relative._authority == null || equals(base._scheme, relative._scheme)) { + this._path = resolvePath(base._path, relative._path); + } + // base._query removed + if (relative._query != null) { + this._query = relative._query; + } + // base._fragment removed + if (relative._fragment != null) { + this._fragment = relative._fragment; + } + this.setUriReference(); + } + + // --------------------------------------------------- Instance Variables + + static final long serialVersionUID = 604752400577948726L; + + /** + * This Uniform Resource Identifier (URI). The URI is always in an "escaped" + * form, since escaping or unescaping a completed URI might change its + * semantics. + */ + protected char[] _uri = null; + + /** + * The default charset of the protocol. RFC 2277, 2396 + */ + protected static String _protocolCharset = "UTF-8"; + + /** + * The default charset of the document. RFC 2277, 2396 The platform's charset is + * used for the document by default. + */ + protected static String _documentCharset = null; + // Static initializer for _documentCharset + static { + Locale locale = Locale.getDefault(); + if (locale != null) { + // in order to support backward compatiblity + _documentCharset = LocaleToCharsetMap.getCharset(locale); + } else { + _documentCharset = (String) AccessController.doPrivileged(new GetPropertyAction("file.encoding")); + } + } + + /** + * The scheme. + */ + protected char[] _scheme = null; + + /** + * The opaque. + */ + protected char[] _opaque = null; + + /** + * The authority. + */ + protected char[] _authority = null; + + /** + * The userinfo. + */ + protected char[] _userinfo = null; + + /** + * The host. + */ + protected char[] _host = null; + + /** + * The port. + */ + protected int _port = -1; + + /** + * The path. + */ + protected char[] _path = null; + + /** + * The query. + */ + protected char[] _query = null; + + /** + * The fragment. + */ + protected char[] _fragment = null; + + /** + * The root path. + */ + protected static char[] rootPath = { '/' }; + + // ---------------------- Generous characters for each component validation + + /** + * The percent "%" character always has the reserved purpose of being the escape + * indicator, it must be escaped as "%25" in order to be used as data within a + * URI. + */ + protected static final BitSet percent = new BitSet(256); + // Static initializer for percent + static { + percent.set('%'); + } + + /** + * BitSet for digit. + *

+ *

+ * + *
+	 * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet digit = new BitSet(256); + // Static initializer for digit + static { + for (int i = '0'; i <= '9'; i++) { + digit.set(i); + } + } + + /** + * BitSet for alpha. + *

+ *

+ * + *
+	 * alpha = lowalpha | upalpha
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet alpha = new BitSet(256); + // Static initializer for alpha + static { + for (int i = 'a'; i <= 'z'; i++) { + alpha.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + alpha.set(i); + } + } + + /** + * BitSet for alphanum (join of alpha & digit). + *

+ *

+ * + *
+	 * alphanum = alpha | digit
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet alphanum = new BitSet(256); + // Static initializer for alphanum + static { + alphanum.or(alpha); + alphanum.or(digit); + } + + /** + * BitSet for hex. + *

+ *

+ * + *
+	 * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f"
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet hex = new BitSet(256); + // Static initializer for hex + static { + hex.or(digit); + for (int i = 'a'; i <= 'f'; i++) { + hex.set(i); + } + for (int i = 'A'; i <= 'F'; i++) { + hex.set(i); + } + } + + /** + * BitSet for escaped. + *

+ *

+ * + *
+	 * escaped       = "%" hex hex
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet escaped = new BitSet(256); + // Static initializer for escaped + static { + escaped.or(percent); + escaped.or(hex); + } + + /** + * BitSet for mark. + *

+ *

+ * + *
+	 * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet mark = new BitSet(256); + // Static initializer for mark + static { + mark.set('-'); + mark.set('_'); + mark.set('.'); + mark.set('!'); + mark.set('~'); + mark.set('*'); + mark.set('\''); + mark.set('('); + mark.set(')'); + } + + /** + * Data characters that are allowed in a URI but do not have a reserved purpose + * are called unreserved. + *

+ *

+ * + *
+	 * unreserved = alphanum | mark
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet unreserved = new BitSet(256); + // Static initializer for unreserved + static { + unreserved.or(alphanum); + unreserved.or(mark); + } + + /** + * BitSet for reserved. + *

+ *

+ * + *
+	 * reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet reserved = new BitSet(256); + // Static initializer for reserved + static { + reserved.set(';'); + reserved.set('/'); + reserved.set('?'); + reserved.set(':'); + reserved.set('@'); + reserved.set('&'); + reserved.set('='); + reserved.set('+'); + reserved.set('$'); + reserved.set(','); + } + + /** + * BitSet for uric. + *

+ *

+ * + *
+	 * uric = reserved | unreserved | escaped
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet uric = new BitSet(256); + // Static initializer for uric + static { + uric.or(reserved); + uric.or(unreserved); + uric.or(escaped); + } + + /** + * BitSet for fragment (alias for uric). + *

+ *

+ * + *
+	 * fragment      = *uric
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet fragment = uric; + + /** + * BitSet for query (alias for uric). + *

+ *

+ * + *
+	 * query         = *uric
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet query = uric; + + /** + * BitSet for pchar. + *

+ *

+ * + *
+	 * pchar = unreserved | escaped | ":" | "@" | "&" | "=" | "+" | "$" | ","
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet pchar = new BitSet(256); + // Static initializer for pchar + static { + pchar.or(unreserved); + pchar.or(escaped); + pchar.set(':'); + pchar.set('@'); + pchar.set('&'); + pchar.set('='); + pchar.set('+'); + pchar.set('$'); + pchar.set(','); + } + + /** + * BitSet for param (alias for pchar). + *

+ *

+ * + *
+	 * param         = *pchar
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet param = pchar; + + /** + * BitSet for segment. + *

+ *

+ * + *
+	 * segment       = *pchar *( ";" param )
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet segment = new BitSet(256); + // Static initializer for segment + static { + segment.or(pchar); + segment.set(';'); + segment.or(param); + } + + /** + * BitSet for path segments. + *

+ *

+ * + *
+	 * path_segments = segment *( "/" segment )
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet path_segments = new BitSet(256); + // Static initializer for path_segments + static { + path_segments.set('/'); + path_segments.or(segment); + } + + /** + * URI absolute path. + *

+ *

+ * + *
+	 * abs_path      = "/"  path_segments
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet abs_path = new BitSet(256); + // Static initializer for abs_path + static { + abs_path.set('/'); + abs_path.or(path_segments); + } + + /** + * URI bitset for encoding typical non-slash characters. + *

+ *

+ * + *
+	 * uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet uric_no_slash = new BitSet(256); + // Static initializer for uric_no_slash + static { + uric_no_slash.or(unreserved); + uric_no_slash.or(escaped); + uric_no_slash.set(';'); + uric_no_slash.set('?'); + uric_no_slash.set(';'); + uric_no_slash.set('@'); + uric_no_slash.set('&'); + uric_no_slash.set('='); + uric_no_slash.set('+'); + uric_no_slash.set('$'); + uric_no_slash.set(','); + } + + /** + * URI bitset that combines uric_no_slash and uric. + *

+ *

+ * + *
+	 * opaque_part = uric_no_slash * uric
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet opaque_part = new BitSet(256); + // Static initializer for opaque_part + static { + opaque_part.or(uric_no_slash); + opaque_part.or(uric); + } + + /** + * URI bitset that combines absolute path and opaque part. + *

+ *

+ * + *
+	 * path          = [ abs_path | opaque_part ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet path = new BitSet(256); + // Static initializer for path + static { + path.or(abs_path); + path.or(opaque_part); + } + + /** + * Port, a logical alias for digit. + */ + protected static final BitSet port = digit; + + /** + * Bitset that combines digit and dot fo IPv$address. + *

+ *

+ * + *
+	 * IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet IPv4address = new BitSet(256); + // Static initializer for IPv4address + static { + IPv4address.or(digit); + IPv4address.set('.'); + } + + /** + * RFC 2373. + *

+ *

+ * + *
+	 * IPv6address = hexpart [ ":" IPv4address ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet IPv6address = new BitSet(256); + // Static initializer for IPv6address reference + static { + IPv6address.or(hex); // hexpart + IPv6address.set(':'); + IPv6address.or(IPv4address); + } + + /** + * RFC 2732, 2373. + *

+ *

+ * + *
+	 * IPv6reference   = "[" IPv6address "]"
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet IPv6reference = new BitSet(256); + // Static initializer for IPv6reference + static { + IPv6reference.set('['); + IPv6reference.or(IPv6address); + IPv6reference.set(']'); + } + + /** + * BitSet for toplabel. + *

+ *

+ * + *
+	 * toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet toplabel = new BitSet(256); + // Static initializer for toplabel + static { + toplabel.or(alphanum); + toplabel.set('-'); + } + + /** + * BitSet for domainlabel. + *

+ *

+ * + *
+	 * domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet domainlabel = toplabel; + + /** + * BitSet for hostname. + *

+ *

+ * + *
+	 * hostname      = *( domainlabel "." ) toplabel [ "." ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet hostname = new BitSet(256); + // Static initializer for hostname + static { + hostname.or(toplabel); + // hostname.or(domainlabel); + hostname.set('.'); + } + + /** + * BitSet for host. + *

+ *

+ * + *
+	 * host = hostname | IPv4address | IPv6reference
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet host = new BitSet(256); + // Static initializer for host + static { + host.or(hostname); + // host.or(IPv4address); + host.or(IPv6reference); // IPv4address + } + + /** + * BitSet for hostport. + *

+ *

+ * + *
+	 * hostport      = host [ ":" port ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet hostport = new BitSet(256); + // Static initializer for hostport + static { + hostport.or(host); + hostport.set(':'); + hostport.or(port); + } + + /** + * Bitset for userinfo. + *

+ *

+ * + *
+	 * userinfo      = *( unreserved | escaped |
+	 *                    ";" | ":" | "&" | "=" | "+" | "$" | "," )
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet userinfo = new BitSet(256); + // Static initializer for userinfo + static { + userinfo.or(unreserved); + userinfo.or(escaped); + userinfo.set(';'); + userinfo.set(':'); + userinfo.set('&'); + userinfo.set('='); + userinfo.set('+'); + userinfo.set('$'); + userinfo.set(','); + } + + /** + * BitSet for within the userinfo component like user and password. + */ + public static final BitSet within_userinfo = new BitSet(256); + // Static initializer for within_userinfo + static { + within_userinfo.or(userinfo); + within_userinfo.clear(';'); // reserved within authority + within_userinfo.clear(':'); + within_userinfo.clear('@'); + within_userinfo.clear('?'); + within_userinfo.clear('/'); + } + + /** + * Bitset for server. + *

+ *

+ * + *
+	 * server        = [ [ userinfo "@" ] hostport ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet server = new BitSet(256); + // Static initializer for server + static { + server.or(userinfo); + server.set('@'); + server.or(hostport); + } + + /** + * BitSet for reg_name. + *

+ *

+ * + *
+	 * reg_name = 1 * (unreserved | escaped | "$" | "," | ";" | ":" | "@" | "&" | "=" | "+")
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet reg_name = new BitSet(256); + // Static initializer for reg_name + static { + reg_name.or(unreserved); + reg_name.or(escaped); + reg_name.set('$'); + reg_name.set(','); + reg_name.set(';'); + reg_name.set(':'); + reg_name.set('@'); + reg_name.set('&'); + reg_name.set('='); + reg_name.set('+'); + } + + /** + * BitSet for authority. + *

+ *

+ * + *
+	 * authority = server | reg_name
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet authority = new BitSet(256); + // Static initializer for authority + static { + authority.or(server); + authority.or(reg_name); + } + + /** + * BitSet for scheme. + *

+ *

+ * + *
+	 * scheme = alpha * (alpha | digit | "+" | "-" | ".")
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet scheme = new BitSet(256); + // Static initializer for scheme + static { + scheme.or(alpha); + scheme.or(digit); + scheme.set('+'); + scheme.set('-'); + scheme.set('.'); + } + + /** + * BitSet for rel_segment. + *

+ *

+ * + *
+	 * rel_segment = 1 * (unreserved | escaped | ";" | "@" | "&" | "=" | "+" | "$" | ",")
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet rel_segment = new BitSet(256); + // Static initializer for rel_segment + static { + rel_segment.or(unreserved); + rel_segment.or(escaped); + rel_segment.set(';'); + rel_segment.set('@'); + rel_segment.set('&'); + rel_segment.set('='); + rel_segment.set('+'); + rel_segment.set('$'); + rel_segment.set(','); + } + + /** + * BitSet for rel_path. + *

+ *

+ * + *
+	 * rel_path = rel_segment[abs_path]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet rel_path = new BitSet(256); + // Static initializer for rel_path + static { + rel_path.or(rel_segment); + rel_path.or(abs_path); + } + + /** + * BitSet for net_path. + *

+ *

+ * + *
+	 * net_path      = "//" authority [ abs_path ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet net_path = new BitSet(256); + // Static initializer for net_path + static { + net_path.set('/'); + net_path.or(authority); + net_path.or(abs_path); + } + + /** + * BitSet for hier_part. + *

+ *

+ * + *
+	 * hier_part     = ( net_path | abs_path ) [ "?" query ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet hier_part = new BitSet(256); + // Static initializer for hier_part + static { + hier_part.or(net_path); + hier_part.or(abs_path); + // hier_part.set('?'); aleady included + hier_part.or(query); + } + + /** + * BitSet for relativeURI. + *

+ *

+ * + *
+	 * relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet relativeURI = new BitSet(256); + // Static initializer for relativeURI + static { + relativeURI.or(net_path); + relativeURI.or(abs_path); + relativeURI.or(rel_path); + // relativeURI.set('?'); aleady included + relativeURI.or(query); + } + + /** + * BitSet for absoluteURI. + *

+ *

+ * + *
+	 * absoluteURI   = scheme ":" ( hier_part | opaque_part )
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet absoluteURI = new BitSet(256); + // Static initializer for absoluteURI + static { + absoluteURI.or(scheme); + absoluteURI.set(':'); + absoluteURI.or(hier_part); + absoluteURI.or(opaque_part); + } + + /** + * BitSet for URI-reference. + *

+ *

+ * + *
+	 * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+	 * 
+ * + *
+ *

+ */ + protected static final BitSet URI_reference = new BitSet(256); + // Static initializer for URI_reference + static { + URI_reference.or(absoluteURI); + URI_reference.or(relativeURI); + URI_reference.set('#'); + URI_reference.or(fragment); + } + + // ---------------------------- Characters disallowed within the URI syntax + // Excluded US-ASCII Characters are like control, space, delims and unwise + + /** + * BitSet for control. + */ + public static final BitSet control = new BitSet(256); + // Static initializer for control + static { + for (int i = 0; i <= 0x1F; i++) { + control.set(i); + } + control.set(0x7F); + } + + /** + * BitSet for space. + */ + public static final BitSet space = new BitSet(256); + // Static initializer for space + static { + space.set(0x20); + } + + /** + * BitSet for delims. + */ + public static final BitSet delims = new BitSet(256); + // Static initializer for delims + static { + delims.set('<'); + delims.set('>'); + delims.set('#'); + delims.set('%'); + delims.set('"'); + } + + /** + * BitSet for unwise. + */ + public static final BitSet unwise = new BitSet(256); + // Static initializer for unwise + static { + unwise.set('{'); + unwise.set('}'); + unwise.set('|'); + unwise.set('\\'); + unwise.set('^'); + unwise.set('['); + unwise.set(']'); + unwise.set('`'); + } + + /** + * Disallowed rel_path before escaping. + */ + public static final BitSet disallowed_rel_path = new BitSet(256); + // Static initializer for disallowed_rel_path + static { + disallowed_rel_path.or(uric); + disallowed_rel_path.andNot(rel_path); + } + + /** + * Disallowed opaque_part before escaping. + */ + public static final BitSet disallowed_opaque_part = new BitSet(256); + // Static initializer for disallowed_opaque_part + static { + disallowed_opaque_part.or(uric); + disallowed_opaque_part.andNot(opaque_part); + } + + // ----------------------- Characters allowed within and for each component + + /** + * Those characters that are allowed for the authority component. + */ + public static final BitSet allowed_authority = new BitSet(256); + // Static initializer for allowed_authority + static { + allowed_authority.or(authority); + allowed_authority.clear('%'); + } + + /** + * Those characters that are allowed for the opaque_part. + */ + public static final BitSet allowed_opaque_part = new BitSet(256); + // Static initializer for allowed_opaque_part + static { + allowed_opaque_part.or(opaque_part); + allowed_opaque_part.clear('%'); + } + + /** + * Those characters that are allowed for the reg_name. + */ + public static final BitSet allowed_reg_name = new BitSet(256); + // Static initializer for allowed_reg_name + static { + allowed_reg_name.or(reg_name); + // allowed_reg_name.andNot(percent); + allowed_reg_name.clear('%'); + } + + /** + * Those characters that are allowed for the userinfo component. + */ + public static final BitSet allowed_userinfo = new BitSet(256); + // Static initializer for allowed_userinfo + static { + allowed_userinfo.or(userinfo); + // allowed_userinfo.andNot(percent); + allowed_userinfo.clear('%'); + } + + /** + * Those characters that are allowed for within the userinfo component. + */ + public static final BitSet allowed_within_userinfo = new BitSet(256); + // Static initializer for allowed_within_userinfo + static { + allowed_within_userinfo.or(within_userinfo); + allowed_within_userinfo.clear('%'); + } + + /** + * Those characters that are allowed for the IPv6reference component. The + * characters '[', ']' in IPv6reference should be excluded. + */ + public static final BitSet allowed_IPv6reference = new BitSet(256); + // Static initializer for allowed_IPv6reference + static { + allowed_IPv6reference.or(IPv6reference); + // allowed_IPv6reference.andNot(unwise); + allowed_IPv6reference.clear('['); + allowed_IPv6reference.clear(']'); + } + + /** + * Those characters that are allowed for the host component. The characters '[', + * ']' in IPv6reference should be excluded. + */ + public static final BitSet allowed_host = new BitSet(256); + // Static initializer for allowed_host + static { + allowed_host.or(hostname); + allowed_host.or(allowed_IPv6reference); + } + + /** + * Those characters that are allowed for the authority component. + */ + public static final BitSet allowed_within_authority = new BitSet(256); + // Static initializer for allowed_within_authority + static { + allowed_within_authority.or(server); + allowed_within_authority.or(reg_name); + allowed_within_authority.clear(';'); + allowed_within_authority.clear(':'); + allowed_within_authority.clear('@'); + allowed_within_authority.clear('?'); + allowed_within_authority.clear('/'); + } + + /** + * Those characters that are allowed for the abs_path. + */ + public static final BitSet allowed_abs_path = new BitSet(256); + // Static initializer for allowed_abs_path + static { + allowed_abs_path.or(abs_path); + // allowed_abs_path.set('/'); // aleady included + allowed_abs_path.andNot(percent); + } + + /** + * Those characters that are allowed for the rel_path. + */ + public static final BitSet allowed_rel_path = new BitSet(256); + // Static initializer for allowed_rel_path + static { + allowed_rel_path.or(rel_path); + allowed_rel_path.clear('%'); + } + + /** + * Those characters that are allowed within the path. + */ + public static final BitSet allowed_within_path = new BitSet(256); + // Static initializer for allowed_within_path + static { + allowed_within_path.or(abs_path); + allowed_within_path.clear('/'); + allowed_within_path.clear(';'); + allowed_within_path.clear('='); + allowed_within_path.clear('?'); + } + + /** + * Those characters that are allowed for the query component. + */ + public static final BitSet allowed_query = new BitSet(256); + // Static initializer for allowed_query + static { + allowed_query.or(uric); + allowed_query.clear('%'); + } + + /** + * Those characters that are allowed within the query component. + */ + public static final BitSet allowed_within_query = new BitSet(256); + // Static initializer for allowed_within_query + static { + allowed_within_query.or(allowed_query); + allowed_within_query.andNot(reserved); // excluded 'reserved' + allowed_within_query.clear('#'); // avoid confict with the fragment + } + + /** + * Those characters that are allowed for the fragment component. + */ + public static final BitSet allowed_fragment = new BitSet(256); + // Static initializer for allowed_fragment + static { + allowed_fragment.or(uric); + allowed_fragment.clear('%'); + } + + // ------------------------------------------- Flags for this URI-reference + + // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + // absoluteURI = scheme ":" ( hier_part | opaque_part ) + protected boolean _is_hier_part; + protected boolean _is_opaque_part; + // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] + // hier_part = ( net_path | abs_path ) [ "?" query ] + protected boolean _is_net_path; + protected boolean _is_abs_path; + protected boolean _is_rel_path; + // net_path = "//" authority [ abs_path ] + // authority = server | reg_name + protected boolean _is_reg_name; + protected boolean _is_server; // = _has_server + // server = [ [ userinfo "@" ] hostport ] + // host = hostname | IPv4address | IPv6reference + protected boolean _is_hostname; + protected boolean _is_IPv4address; + protected boolean _is_IPv6reference; + + // ------------------------------------------ Character and escape encoding + + /** + * Encode with the default protocol charset. + * + * @param original the original character sequence + * @param allowed those characters that are allowed within a component + * @return URI character sequence + * @exception IOException null component or unsupported character encoding + */ + protected static char[] encode(String original, BitSet allowed) throws IOException { + + return encode(original, allowed, _protocolCharset); + } + + /** + * Encodes URI string. + * + * This is a two mapping, one from original characters to octets, and + * subsequently a second from octets to URI characters: + *

+ *

+ * + *
+	 *   original character sequence->octet sequence->URI character sequence
+	 * 
+ * + *
+ *

+ * + * An escaped octet is encoded as a character triplet, consisting of the percent + * character "%" followed by the two hexadecimal digits representing the octet + * code. For example, "%20" is the escaped encoding for the US-ASCII space + * character. + *

+ * Conversion from the local filesystem character set to UTF-8 will normally + * involve a two step process. First convert the local character set to the UCS; + * then convert the UCS to UTF-8. The first step in the process can be performed + * by maintaining a mapping table that includes the local character set code and + * the corresponding UCS code. The next step is to convert the UCS character + * code to the UTF-8 encoding. + *

+ * Mapping between vendor codepages can be done in a very similar manner as + * described above. + *

+ * The only time escape encodings can allowedly be made is when a URI is being + * created from its component parts. The escape and validate methods are + * internally performed within this method. + * + * @param original the original character sequence + * @param allowed those characters that are allowed within a component + * @param charset the protocol charset + * @return URI character sequence + * @exception IOException null component or unsupported character encoding + */ + protected static char[] encode(String original, BitSet allowed, String charset) throws IOException { + + // encode original to uri characters. + if (original == null) { + throw new IOException(/* IOException.PARSING, */ "URI: null"); + } + // escape octet to uri characters. + if (allowed == null) { + throw new IOException(/* IOException.PARSING, */ + "URI: null allowed characters"); + } + byte[] octets; + try { + octets = original.getBytes(charset); + } catch (UnsupportedEncodingException error) { + throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */ "Unsupported Encoding: " + charset); + } + StringBuffer buf = new StringBuffer(octets.length); + for (int i = 0; i < octets.length; i++) { + char c = (char) octets[i]; + if (allowed.get(c)) { + buf.append(c); + } else { + buf.append('%'); + byte b = octets[i]; // use the original byte value + char hexadecimal = Character.forDigit((b >> 4) & 0xF, 16); + buf.append(Character.toUpperCase(hexadecimal)); // high + hexadecimal = Character.forDigit(b & 0xF, 16); + buf.append(Character.toUpperCase(hexadecimal)); // low + } + } + + return buf.toString().toCharArray(); + } + + /** + * Decode with the default protocol charset. + * + * @param component the URI character sequence + * @return original character sequence + * @exception IOException incomplete trailing escape pattern or unsupported + * character encoding + */ + protected static String decode(char[] component) throws IOException { + return decode(component, _protocolCharset); + } + + /** + * Decodes URI encoded string. + * + * This is a two mapping, one from URI characters to octets, and subsequently a + * second from octets to original characters: + *

+ *

+ * + *
+	 *   URI character sequence->octet sequence->original character sequence
+	 * 
+ * + *
+ *

+ * + * A URI must be separated into its components before the escaped characters + * within those components can be allowedly decoded. + *

+ * Notice that there is a chance that URI characters that are non UTF-8 may be + * parsed as valid UTF-8. A recent non-scientific analysis found that EUC + * encoded Japanese words had a 2.7% false reading; SJIS had a 0.0005% false + * reading; other encoding such as ASCII or KOI-8 have a 0% false reading. + *

+ * The percent "%" character always has the reserved purpose of being the escape + * indicator, it must be escaped as "%25" in order to be used as data within a + * URI. + *

+ * The unescape method is internally performed within this method. + * + * @param component the URI character sequence + * @param charset the protocol charset + * @return original character sequence + * @exception IOException incomplete trailing escape pattern or unsupported + * character encoding + */ + protected static String decode(char[] component, String charset) throws IOException { + + // unescape uri characters to octets + if (component == null) + return null; + + byte[] octets; + try { + octets = new String(component).getBytes(charset); + } catch (UnsupportedEncodingException error) { + throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */ + "URI: not supported " + charset + " encoding"); + } + int length = octets.length; + int oi = 0; // output index + for (int ii = 0; ii < length; oi++) { + byte aByte = (byte) octets[ii++]; + if (aByte == '%' && ii + 2 <= length) { + byte high = (byte) Character.digit((char) octets[ii++], 16); + byte low = (byte) Character.digit((char) octets[ii++], 16); + if (high == -1 || low == -1) { + throw new IOException(/* IOException.ESCAPING, */ + "URI: incomplete trailing escape pattern"); + + } + aByte = (byte) ((high << 4) + low); + } + octets[oi] = (byte) aByte; + } + + String result; + try { + result = new String(octets, 0, oi, charset); + } catch (UnsupportedEncodingException error) { + throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */ + "URI: not supported " + charset + " encoding"); + } + + return result; + } + + /** + * Pre-validate the unescaped URI string within a specific component. + * + * @param component the component string within the component + * @param disallowed those characters disallowed within the component + * @return if true, it doesn't have the disallowed characters if false, the + * component is undefined or an incorrect one + */ + protected boolean prevalidate(String component, BitSet disallowed) { + // prevalidate the given component by disallowed characters + if (component == null) { + return false; // undefined + } + char[] target = component.toCharArray(); + for (int i = 0; i < target.length; i++) { + if (disallowed.get(target[i])) { + return false; + } + } + return true; + } + + /** + * Validate the URI characters within a specific component. The component must + * be performed after escape encoding. Or it doesn't include escaped characters. + * + * @param component the characters sequence within the component + * @param generous those characters that are allowed within a component + * @return if true, it's the correct URI character sequence + */ + protected boolean validate(char[] component, BitSet generous) { + // validate each component by generous characters + return validate(component, 0, -1, generous); + } + + /** + * Validate the URI characters within a specific component. The component must + * be performed after escape encoding. Or it doesn't include escaped characters. + *

+ * It's not that much strict, generous. The strict validation might be performed + * before being called this method. + * + * @param component the characters sequence within the component + * @param soffset the starting offset of the given component + * @param eoffset the ending offset of the given component if -1, it means the + * length of the component + * @param generous those characters that are allowed within a component + * @return if true, it's the correct URI character sequence + * @throws NullPointerException null component + */ + protected boolean validate(char[] component, int soffset, int eoffset, BitSet generous) { + // validate each component by generous characters + if (eoffset == -1) { + eoffset = component.length - 1; + } + for (int i = soffset; i <= eoffset; i++) { + if (!generous.get(component[i])) + return false; + } + return true; + } + + /** + * In order to avoid any possilbity of conflict with non-ASCII characters, Parse + * a URI reference as a String with the character encoding of the + * local system or the document. + *

+ * The following line is the regular expression for breaking-down a URI + * reference into its components. + *

+ *

+ * + *
+	 *   ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+	 *    12            3  4          5       6  7        8 9
+	 * 
+ * + *
+ *

+ * For example, matching the above expression to + * http://jakarta.apache.org/ietf/uri/#Related results in the following + * subexpression matches: + *

+ *

+ * + *
+	 *               $1 = http:
+	 *  scheme    =  $2 = http
+	 *               $3 = //jakarta.apache.org
+	 *  authority =  $4 = jakarta.apache.org
+	 *  path      =  $5 = /ietf/uri/
+	 *               $6 = 
+	 *  query     =  $7 = 
+	 *               $8 = #Related
+	 *  fragment  =  $9 = Related
+	 * 
+ * + *
+ *

+ * + * @param original the original character sequence + * @param escaped true if original is escaped + * @return the original character sequence + * @exception IOException + */ + protected void parseUriReference(String original, boolean escaped) throws IOException { + + // validate and contruct the URI character sequence + if (original == null || original.length() == 0) { + throw new IOException("URI-Reference required"); + } + + /** + * @ ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + */ + String tmp = original.trim(); + + /** + * The length of the string sequence of characters. It may not be equal to the + * length of the byte array. + */ + int length = tmp.length(); + + /** + * Remove the delimiters like angle brackets around an URI. + */ + char[] firstDelimiter = { tmp.charAt(0) }; + if (validate(firstDelimiter, delims)) { + if (length >= 2) { + char[] lastDelimiter = { tmp.charAt(length - 1) }; + if (validate(lastDelimiter, delims)) { + tmp = tmp.substring(1, length - 1); + length = length - 2; + } + } + } + + /** + * The starting index + */ + int from = 0; + + /** + * The test flag whether the URI is started from the path component. + */ + boolean isStartedFromPath = false; + int atColon = tmp.indexOf(':'); + int atSlash = tmp.indexOf('/'); + if (atColon < 0 || (atSlash >= 0 && atSlash < atColon)) { + isStartedFromPath = true; + } + + /** + *

+ *

+ * + *
+		 *     @@@@@@@@
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + int at = indexFirstOf(tmp, isStartedFromPath ? "/?#" : ":/?#", from); + if (at == -1) + at = 0; + + /** + * Parse the scheme. + *

+ *

+ * + *
+		 *  scheme    =  $2 = http
+		 *              @
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + if (at < length && tmp.charAt(at) == ':') { + char[] target = tmp.substring(0, at).toLowerCase().toCharArray(); + if (validate(target, scheme)) { + _scheme = target; + } else { + throw new IOException("incorrect scheme"); + } + from = ++at; + } + + /** + * Parse the authority component. + *

+ *

+ * + *
+		 *  authority =  $4 = jakarta.apache.org
+		 *                  @@
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + // Reset flags + _is_net_path = _is_abs_path = _is_rel_path = _is_hier_part = false; + if (0 <= at && at < length && tmp.charAt(at) == '/') { + // Set flag + _is_hier_part = true; + if (at + 2 < length && tmp.charAt(at + 1) == '/') { + // the temporary index to start the search from + int next = indexFirstOf(tmp, "/?#", at + 2); + if (next == -1) { + next = (tmp.substring(at + 2).length() == 0) ? at + 2 : tmp.length(); + } + parseAuthority(tmp.substring(at + 2, next), escaped); + from = at = next; + // Set flag + _is_net_path = true; + } + if (from == at) { + // Set flag + _is_abs_path = true; + } + } + + /** + * Parse the path component. + *

+ *

+ * + *
+		 *  path      =  $5 = /ietf/uri/
+		 *                                @@@@@@
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + if (from < length) { + // rel_path = rel_segment [ abs_path ] + int next = indexFirstOf(tmp, "?#", from); + if (next == -1) { + next = tmp.length(); + } + if (!_is_abs_path) { + if (!escaped && prevalidate(tmp.substring(from, next), disallowed_rel_path) + || escaped && validate(tmp.substring(from, next).toCharArray(), rel_path)) { + // Set flag + _is_rel_path = true; + } else if (!escaped && prevalidate(tmp.substring(from, next), disallowed_opaque_part) + || escaped && validate(tmp.substring(from, next).toCharArray(), opaque_part)) { + // Set flag + _is_opaque_part = true; + } else { + // the path component may be empty + _path = null; + } + } + setPath(tmp.substring(from, next)); + at = next; + } + + /** + * Parse the query component. + *

+ *

+ * + *
+		 *  query     =  $7 = 
+		 *                                        @@@@@@@@@
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + if (0 <= at && at + 1 < length && tmp.charAt(at) == '?') { + int next = tmp.indexOf('#', at + 1); + if (next == -1) { + next = tmp.length(); + } + _query = (escaped) ? tmp.substring(at + 1, next).toCharArray() + : encode(tmp.substring(at + 1, next), allowed_query); + at = next; + } + + /** + * Parse the fragment component. + *

+ *

+ * + *
+		 *  fragment  =  $9 = Related
+		 *                                                   @@@@@@@@
+		 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+		 * 
+ * + *
+ *

+ */ + if (0 <= at && at + 1 < length && tmp.charAt(at) == '#') { + _fragment = (escaped) ? tmp.substring(at + 1).toCharArray() + : encode(tmp.substring(at + 1), allowed_fragment); + } + + // set this URI. + setUriReference(); + } + + /** + * Get the earlier index that to be searched for the first occurrance in one of + * any of the given string. + * + * @param s the string to be indexed + * @param delims the delimiters used to index + * @return the earlier index if there are delimiters + */ + protected int indexFirstOf(String s, String delims) { + return indexFirstOf(s, delims, -1); + } + + /** + * Get the earlier index that to be searched for the first occurrance in one of + * any of the given string. + * + * @param s the string to be indexed + * @param delims the delimiters used to index + * @param offset the from index + * @return the earlier index if there are delimiters + */ + protected int indexFirstOf(String s, String delims, int offset) { + if (s == null || s.length() == 0) { + return -1; + } + if (delims == null || delims.length() == 0) { + return -1; + } + // check boundaries + if (offset < 0) { + offset = 0; + } else if (offset > s.length()) { + return -1; + } + // s is never null + int min = s.length(); + char[] delim = delims.toCharArray(); + for (int i = 0; i < delim.length; i++) { + int at = s.indexOf(delim[i], offset); + if (at >= 0 && at < min) { + min = at; + } + } + return (min == s.length()) ? -1 : min; + } + + /** + * Get the earlier index that to be searched for the first occurrance in one of + * any of the given array. + * + * @param s the character array to be indexed + * @param delim the delimiter used to index + * @return the ealier index if there are a delimiter + */ + protected int indexFirstOf(char[] s, char delim) { + return indexFirstOf(s, delim, 0); + } + + /** + * Get the earlier index that to be searched for the first occurrance in one of + * any of the given array. + * + * @param s the character array to be indexed + * @param delim the delimiter used to index + * @return the ealier index if there is a delimiter + */ + protected int indexFirstOf(char[] s, char delim, int offset) { + if (s == null || s.length == 0) { + return -1; + } + // check boundaries + if (offset < 0) { + offset = 0; + } else if (offset > s.length) { + return -1; + } + for (int i = offset; i < s.length; i++) { + if (s[i] == delim) { + return i; + } + } + return -1; + } + + /** + * Parse the authority component. + * + * @param original the original character sequence of authority component + * @param escaped true if original is escaped + * @exception IOException + */ + protected void parseAuthority(String original, boolean escaped) throws IOException { + + // Reset flags + _is_reg_name = _is_server = _is_hostname = _is_IPv4address = _is_IPv6reference = false; + + boolean has_port = true; + int from = 0; + int next = original.indexOf('@'); + if (next != -1) { // neither -1 and 0 + // each protocol extented from URI supports the specific userinfo + _userinfo = (escaped) ? original.substring(0, next).toCharArray() + : encode(original.substring(0, next), allowed_userinfo); + from = next + 1; + } + next = original.indexOf('[', from); + if (next >= from) { + next = original.indexOf(']', from); + if (next == -1) { + throw new IOException(/* IOException.PARSING, */ "URI: IPv6reference"); + } else { + next++; + } + // In IPv6reference, '[', ']' should be excluded + _host = (escaped) ? original.substring(from, next).toCharArray() + : encode(original.substring(from, next), allowed_IPv6reference); + // Set flag + _is_IPv6reference = true; + } else { // only for !_is_IPv6reference + next = original.indexOf(':', from); + if (next == -1) { + next = original.length(); + has_port = false; + } + // REMINDME: it doesn't need the pre-validation + _host = original.substring(from, next).toCharArray(); + if (validate(_host, IPv4address)) { + // Set flag + _is_IPv4address = true; + } else if (validate(_host, hostname)) { + // Set flag + _is_hostname = true; + } else { + // Set flag + _is_reg_name = true; + } + } + if (_is_reg_name) { + // Reset flags for a server-based naming authority + _is_server = _is_hostname = _is_IPv4address = _is_IPv6reference = false; + // set a registry-based naming authority + _authority = (escaped) ? original.toString().toCharArray() : encode(original.toString(), allowed_reg_name); + } else { + if (original.length() - 1 > next && has_port && original.charAt(next) == ':') { // not empty + from = next + 1; + try { + _port = Integer.parseInt(original.substring(from)); + } catch (NumberFormatException error) { + throw new IOException(/* IOException.PARSING, */ + "URI: invalid port number"); + } + } + // set a server-based naming authority + StringBuffer buf = new StringBuffer(); + if (_userinfo != null) { // has_userinfo + buf.append(_userinfo); + buf.append('@'); + } + if (_host != null) { + buf.append(_host); + if (_port != -1) { + buf.append(':'); + buf.append(_port); + } + } + _authority = buf.toString().toCharArray(); + // Set flag + _is_server = true; + } + } + + /** + * Once it's parsed successfully, set this URI. + * + * @see #getRawURI + */ + protected void setUriReference() { + // set _uri + StringBuffer buf = new StringBuffer(); + // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + if (_scheme != null) { + buf.append(_scheme); + buf.append(':'); + } + if (_is_net_path) { + buf.append("//"); + if (_authority != null) { // has_authority + if (_userinfo != null) { // by default, remove userinfo part + if (_host != null) { + buf.append(_host); + if (_port != -1) { + buf.append(':'); + buf.append(_port); + } + } + } else { + buf.append(_authority); + } + } + } + if (_opaque != null && _is_opaque_part) { + buf.append(_opaque); + } else if (_path != null) { + // _is_hier_part or _is_relativeURI + if (_path.length != 0) { + buf.append(_path); + } + } + if (_query != null) { // has_query + buf.append('?'); + buf.append(_query); + } + if (_fragment != null) { // has_fragment + buf.append('#'); + buf.append(_fragment); + } + + _uri = buf.toString().toCharArray(); + } + + // ----------------------------------------------------------- Test methods + + /** + * Tell whether or not this URI is absolute. + * + * @return true iif this URI is absoluteURI + */ + public boolean isAbsoluteURI() { + return (_scheme != null); + } + + /** + * Tell whether or not this URI is relative. + * + * @return true iif this URI is relativeURI + */ + public boolean isRelativeURI() { + return (_scheme == null); + } + + /** + * Tell whether or not the absoluteURI of this URI is hier_part. + * + * @return true iif the absoluteURI is hier_part + */ + public boolean isHierPart() { + return _is_hier_part; + } + + /** + * Tell whether or not the absoluteURI of this URI is opaque_part. + * + * @return true iif the absoluteURI is opaque_part + */ + public boolean isOpaquePart() { + return _is_opaque_part; + } + + /** + * Tell whether or not the relativeURI or heir_part of this URI is net_path. + * It's the same function as the has_authority() method. + * + * @return true iif the relativeURI or heir_part is net_path + * @see #hasAuthority + */ + public boolean isNetPath() { + return _is_net_path || (_authority != null); + } + + /** + * Tell whether or not the relativeURI or hier_part of this URI is abs_path. + * + * @return true iif the relativeURI or hier_part is abs_path + */ + public boolean isAbsPath() { + return _is_abs_path; + } + + /** + * Tell whether or not the relativeURI of this URI is rel_path. + * + * @return true iif the relativeURI is rel_path + */ + public boolean isRelPath() { + return _is_rel_path; + } + + /** + * Tell whether or not this URI has authority. It's the same function as the + * is_net_path() method. + * + * @return true iif this URI has authority + * @see #isNetPath + */ + public boolean hasAuthority() { + return (_authority != null) || _is_net_path; + } + + /** + * Tell whether or not the authority component of this URI is reg_name. + * + * @return true iif the authority component is reg_name + */ + public boolean isRegName() { + return _is_reg_name; + } + + /** + * Tell whether or not the authority component of this URI is server. + * + * @return true iif the authority component is server + */ + public boolean isServer() { + return _is_server; + } + + /** + * Tell whether or not this URI has userinfo. + * + * @return true iif this URI has userinfo + */ + public boolean hasUserinfo() { + return (_userinfo != null); + } + + /** + * Tell whether or not the host part of this URI is hostname. + * + * @return true iif the host part is hostname + */ + public boolean isHostname() { + return _is_hostname; + } + + /** + * Tell whether or not the host part of this URI is IPv4address. + * + * @return true iif the host part is IPv4address + */ + public boolean isIPv4address() { + return _is_IPv4address; + } + + /** + * Tell whether or not the host part of this URI is IPv6reference. + * + * @return true iif the host part is IPv6reference + */ + public boolean isIPv6reference() { + return _is_IPv6reference; + } + + /** + * Tell whether or not this URI has query. + * + * @return true iif this URI has query + */ + public boolean hasQuery() { + return (_query != null); + } + + /** + * Tell whether or not this URI has fragment. + * + * @return true iif this URI has fragment + */ + public boolean hasFragment() { + return (_fragment != null); + } + + // ---------------------------------------------------------------- Charset + + /** + * Set the default charset of the protocol. + *

+ * The character set used to store files SHALL remain a local decision and MAY + * depend on the capability of local operating systems. Prior to the exchange of + * URIs they SHOULD be converted into a ISO/IEC 10646 format and UTF-8 encoded. + * This approach, while allowing international exchange of URIs, will still + * allow backward compatibility with older systems because the code set + * positions for ASCII characters are identical to the one byte sequence in + * UTF-8. + *

+ * An individual URI scheme may require a single charset, define a default + * charset, or provide a way to indicate the charset used. + * + * @param charset the default charset for each protocol + */ + public static void setProtocolCharset(String charset) { + _protocolCharset = charset; + } + + /** + * Get the default charset of the protocol. + *

+ * An individual URI scheme may require a single charset, define a default + * charset, or provide a way to indicate the charset used. + *

+ * To work globally either requires support of a number of character sets and to + * be able to convert between them, or the use of a single preferred character + * set. For support of global compatibility it is STRONGLY RECOMMENDED that + * clients and servers use UTF-8 encoding when exchanging URIs. + * + * @return the charset string + */ + public static String getProtocolCharset() { + return _protocolCharset; + } + + /** + * Set the default charset of the document. + *

+ * Notice that it will be possible to contain mixed characters (e.g. + * ftp://host/KoreanNamespace/ChineseResource). To handle the Bi-directional + * display of these character sets, the protocol charset could be simply used + * again. Because it's not yet implemented that the insertion of BIDI control + * characters at different points during composition is extracted. + * + * @param charset the default charset for the document + */ + public static void setDocumentCharset(String charset) { + _documentCharset = charset; + } + + /** + * Get the default charset of the document. + * + * @return the charset string + */ + public static String getDocumentCharset() { + return _documentCharset; + } + + // ------------------------------------------------------------- The scheme + + /** + * Get the scheme. + * + * @return the scheme + */ + public char[] getRawScheme() { + return _scheme; + } + + /** + * Get the scheme. + * + * @return the scheme null if undefined scheme + */ + public String getScheme() { + return (_scheme == null) ? null : new String(_scheme); + } + + // ---------------------------------------------------------- The authority + + /** + * Set the authority. It can be one type of server, hostport, hostname, + * IPv4address, IPv6reference and reg_name. + *

+ *

+ * + *
+	 * authority = server | reg_name
+	 * 
+ * + *
+ *

+ * + * @param escapedAuthority the raw escaped authority + * @exception IOException + * @throws NullPointerException null authority + */ + public void setRawAuthority(char[] escapedAuthority) throws IOException { + parseAuthority(new String(escapedAuthority), true); + setUriReference(); + } + + /** + * Set the authority. It can be one type of server, hostport, hostname, + * IPv4address, IPv6reference and reg_name. Note that there is no setAuthority + * method by the escape encoding reason. + * + * @param escapedAuthority the escaped authority string + * @exception IOException + */ + public void setEscapedAuthority(String escapedAuthority) throws IOException { + + parseAuthority(escapedAuthority, true); + setUriReference(); + } + + /** + * Get the raw-escaped authority. + * + * @return the raw-escaped authority + */ + public char[] getRawAuthority() { + return _authority; + } + + /** + * Get the escaped authority. + * + * @return the escaped authority + */ + public String getEscapedAuthority() { + return (_authority == null) ? null : new String(_authority); + } + + /** + * Get the authority. + * + * @return the authority + * @exception IOException + * @see #decode + */ + public String getAuthority() throws IOException { + return (_authority == null) ? null : decode(_authority); + } + + // ----------------------------------------------------------- The userinfo + + /** + * Get the raw-escaped userinfo. + * + * @return the raw-escaped userinfo + * @see #getAuthority + */ + public char[] getRawUserinfo() { + return _userinfo; + } + + /** + * Get the escaped userinfo. + * + * @return the escaped userinfo + * @see #getAuthority + */ + public String getEscapedUserinfo() { + return (_userinfo == null) ? null : new String(_userinfo); + } + + /** + * Get the userinfo. + * + * @return the userinfo + * @exception IOException + * @see #decode + * @see #getAuthority + */ + public String getUserinfo() throws IOException { + return (_userinfo == null) ? null : decode(_userinfo); + } + + // --------------------------------------------------------------- The host + + /** + * Get the host. + *

+ *

+ * + *
+	 * host = hostname | IPv4address | IPv6reference
+	 * 
+ * + *
+ *

+ * + * @return the host + * @see #getAuthority + */ + public char[] getRawHost() { + return _host; + } + + /** + * Get the host. + *

+ *

+ * + *
+	 * host = hostname | IPv4address | IPv6reference
+	 * 
+ * + *
+ *

+ * + * @return the host + * @exception IOException + * @see #decode + * @see #getAuthority + */ + public String getHost() throws IOException { + return decode(_host); + } + + // --------------------------------------------------------------- The port + + /** + * Get the port. In order to get the specfic default port, the specific + * protocol-supported class extended from the URI class should be used. It has + * the server-based naming authority. + * + * @return the port if -1, it has the default port for the scheme or the + * server-based naming authority is not supported in the specific URI. + */ + public int getPort() { + return _port; + } + + // --------------------------------------------------------------- The path + + /** + * Set the path. The method couldn't be used by API programmers. + * + * @param path the path string + * @exception IOException set incorrectly or fragment only + * @see #encode + */ + protected void setPath(String path) throws IOException { + + // set path + if (_is_net_path || _is_abs_path) { + _path = encode(path, allowed_abs_path); + } else if (_is_rel_path) { + StringBuffer buff = new StringBuffer(path.length()); + int at = path.indexOf('/'); + if (at > 0) { // never 0 + buff.append(encode(path.substring(0, at), allowed_rel_path)); + buff.append(encode(path.substring(at), allowed_abs_path)); + } else { + buff.append(encode(path, allowed_rel_path)); + } + _path = buff.toString().toCharArray(); + } else if (_is_opaque_part) { + _opaque = encode(path, allowed_opaque_part); + } else { + throw new IOException(/* IOException.PARSING, */"URI: incorrect path"); + } + } + + /** + * Resolve the base and relative path. + * + * @param base_path a character array of the base_path + * @param rel_path a character array of the rel_path + * @return the resolved path + */ + protected char[] resolvePath(char[] base_path, char[] rel_path) { + + // REMINDME: paths are never null + String base = (base_path == null) ? "" : new String(base_path); + int at = base.lastIndexOf('/'); + if (at != -1) { + base_path = base.substring(0, at + 1).toCharArray(); + } + // _path could be empty + if (rel_path == null || rel_path.length == 0) { + return normalize(base_path); + } else if (rel_path[0] == '/') { + return rel_path; + } else { + StringBuffer buff = new StringBuffer(base.length() + rel_path.length); + if (at != -1) { + buff.append(base.substring(0, at + 1)); + buff.append(rel_path); + } + return normalize(buff.toString().toCharArray()); + } + } + + /** + * Get the raw-escaped current hierarchy level in the given path. If the last + * namespace is a collection, the slash mark ('/') should be ended with at the + * last character of the path string. + * + * @param path the path + * @return the current hierarchy level + * @exception IOException no hierarchy level + */ + protected char[] getRawCurrentHierPath(char[] path) throws IOException { + + if (_is_opaque_part) { + throw new IOException(/* IOException.PARSING, */ "URI: no hierarchy level"); + } + if (path == null) { + throw new IOException(/* IOException.PARSING, */ "URI: emtpy path"); + } + String buff = new String(path); + int first = buff.indexOf('/'); + int last = buff.lastIndexOf('/'); + if (last == 0) { + return rootPath; + } else if (first != last && last != -1) { + return buff.substring(0, last).toCharArray(); + } + // FIXME: it could be a document on the server side + return path; + } + + /** + * Get the raw-escaped current hierarchy level. + * + * @return the raw-escaped current hierarchy level + * @exception IOException no hierarchy level + */ + public char[] getRawCurrentHierPath() throws IOException { + return (_path == null) ? null : getRawCurrentHierPath(_path); + } + + /** + * Get the escaped current hierarchy level. + * + * @return the escaped current hierarchy level + * @exception IOException no hierarchy level + */ + public String getEscapedCurrentHierPath() throws IOException { + char[] path = getRawCurrentHierPath(); + return (path == null) ? null : new String(path); + } + + /** + * Get the current hierarchy level. + * + * @return the current hierarchy level + * @exception IOException + * @see #decode + */ + public String getCurrentHierPath() throws IOException { + char[] path = getRawCurrentHierPath(); + return (path == null) ? null : decode(path); + } + + /** + * Get the level above the this hierarchy level. + * + * @return the raw above hierarchy level + * @exception IOException + */ + public char[] getRawAboveHierPath() throws IOException { + char[] path = getRawCurrentHierPath(); + return (path == null) ? null : getRawCurrentHierPath(path); + } + + /** + * Get the level above the this hierarchy level. + * + * @return the raw above hierarchy level + * @exception IOException + */ + public String getEscapedAboveHierPath() throws IOException { + char[] path = getRawAboveHierPath(); + return (path == null) ? null : new String(path); + } + + /** + * Get the level above the this hierarchy level. + * + * @return the above hierarchy level + * @exception IOException + * @see #decode + */ + public String getAboveHierPath() throws IOException { + char[] path = getRawAboveHierPath(); + return (path == null) ? null : decode(path); + } + + /** + * Get the raw-escaped path. + *

+ *

+ * + *
+	 *   path          = [ abs_path | opaque_part ]
+	 * 
+ * + *
+ *

+ * + * @return the raw-escaped path + */ + public char[] getRawPath() { + return _is_opaque_part ? _opaque : _path; + } + + /** + * Get the escaped path. + *

+ *

+ * + *
+	 *   path          = [ abs_path | opaque_part ]
+	 *   abs_path      = "/"  path_segments 
+	 *   opaque_part   = uric_no_slash *uric
+	 * 
+ * + *
+ *

+ * + * @return the escaped path string + */ + public String getEscapedPath() { + char[] path = getRawPath(); + return (path == null) ? null : new String(path); + } + + /** + * Get the path. + *

+ *

+ * + *
+	 *   path          = [ abs_path | opaque_part ]
+	 * 
+ * + *
+ *

+ * + * @return the path string + * @exception IOException + * @see #decode + */ + public String getPath() throws IOException { + char[] path = getRawPath(); + return (path == null) ? null : decode(path); + } + + /** + * Get the raw-escaped basename of the path. + * + * @return the raw-escaped basename + */ + public char[] getRawName() { + if (_path == null) + return null; + + int at = 0; + for (int i = _path.length - 1; i >= 0; i--) { + if (_path[i] == '/') { + at = i + 1; + break; + } + } + int len = _path.length - at; + char[] basename = new char[len]; + System.arraycopy(_path, at, basename, 0, len); + return basename; + } + + /** + * Get the escaped basename of the path. + * + * @return the escaped basename string + */ + public String getEscapedName() { + char[] basename = getRawName(); + return (basename == null) ? null : new String(basename); + } + + /** + * Get the basename of the path. + * + * @return the basename string + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @see #decode + */ + public String getName() throws IOException { + char[] basename = getRawName(); + return (basename == null) ? null : decode(getRawName()); + } + + // ----------------------------------------------------- The path and query + + /** + * Get the raw-escaped path and query. + * + * @return the raw-escaped path and query + */ + public char[] getRawPathQuery() { + + if (_path == null && _query == null) { + return null; + } + StringBuffer buff = new StringBuffer(); + if (_path != null) { + buff.append(_path); + } + if (_query != null) { + buff.append('?'); + buff.append(_query); + } + return buff.toString().toCharArray(); + } + + /** + * Get the escaped query. + * + * @return the escaped path and query string + */ + public String getEscapedPathQuery() { + char[] rawPathQuery = getRawPathQuery(); + return (rawPathQuery == null) ? null : new String(rawPathQuery); + } + + /** + * Get the path and query. + * + * @return the path and query string. + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @see #decode + */ + public String getPathQuery() throws IOException { + char[] rawPathQuery = getRawPathQuery(); + return (rawPathQuery == null) ? null : decode(rawPathQuery); + } + + // -------------------------------------------------------------- The query + + /** + * Set the raw-escaped query. + * + * @param escapedQuery the raw-escaped query + * @exception IOException escaped query not valid + * @throws NullPointerException null query + */ + public void setRawQuery(char[] escapedQuery) throws IOException { + if (!validate(escapedQuery, query)) + throw new IOException(/* IOException.ESCAPING, */ + "URI: escaped query not valid"); + _query = escapedQuery; + setUriReference(); + } + + /** + * Set the escaped query string. + * + * @param escapedQuery the escaped query string + * @exception IOException escaped query not valid + * @throws NullPointerException null query + */ + public void setEscapedQuery(String escapedQuery) throws IOException { + setRawQuery(escapedQuery.toCharArray()); + } + + /** + * Set the query. When a query string is not misunderstood the reserved special + * characters ("&", "=", "+", ",", and "$") within a query component, it is + * recommended to use in encoding the whole query with this method. + * + * @param query the query string. + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @throws NullPointerException null query + * @see #encode + */ + public void setQuery(String query) throws IOException { + setRawQuery(encode(query, allowed_query)); + } + + /** + * Get the raw-escaped query. + * + * @return the raw-escaped query + */ + public char[] getRawQuery() { + return _query; + } + + /** + * Get the escaped query. + * + * @return the escaped query string + */ + public String getEscapedQuery() { + return (_query == null) ? null : new String(_query); + } + + /** + * Get the query. + * + * @return the query string. + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @see #decode + */ + public String getQuery() throws IOException { + return (_query == null) ? null : decode(_query); + } + + // ----------------------------------------------------------- The fragment + + /** + * Set the raw-escaped fragment. + * + * @param escapedFragment the raw-escaped fragment + * @exception IOException escaped fragment not valid + * @throws NullPointerException null fragment + */ + public void setRawFragment(char[] escapedFragment) throws IOException { + if (!validate(escapedFragment, fragment)) + throw new IOException(/* IOException.ESCAPING, */ + "URI: escaped fragment not valid"); + _fragment = escapedFragment; + setUriReference(); + } + + /** + * Set the escaped fragment string. + * + * @param escapedFragment the escaped fragment string + * @exception IOException escaped fragment not valid + * @throws NullPointerException null fragment + */ + public void setEscapedFragment(String escapedFragment) throws IOException { + char[] fragmentSequence = escapedFragment.toCharArray(); + if (!validate(fragmentSequence, fragment)) + throw new IOException(/* IOException.ESCAPING, */ + "URI: escaped fragment not valid"); + _fragment = fragmentSequence; + setUriReference(); + } + + /** + * Set the fragment. + * + * @param the fragment string. + * @exception IOException Or unsupported character encoding + * @throws NullPointerException null fragment + */ + public void setFragment(String fragment) throws IOException { + _fragment = encode(fragment, allowed_fragment); + setUriReference(); + } + + /** + * Get the raw-escaped fragment. + *

+ * The optional fragment identifier is not part of a URI, but is often used in + * conjunction with a URI. + *

+ * The format and interpretation of fragment identifiers is dependent on the + * media type [RFC2046] of the retrieval result. + *

+ * A fragment identifier is only meaningful when a URI reference is intended for + * retrieval and the result of that retrieval is a document for which the + * identified fragment is consistently defined. + * + * @return the raw-escaped fragment + */ + public char[] getRawFragment() { + return _fragment; + } + + /** + * Get the escaped fragment. + * + * @return the escaped fragment string + */ + public String getEscapedFragment() { + return (_fragment == null) ? null : new String(_fragment); + } + + /** + * Get the fragment. + * + * @return the fragment string + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @see #decode + */ + public String getFragment() throws IOException { + return (_fragment == null) ? null : decode(_fragment); + } + + // ------------------------------------------------------------- Utilities + + /** + * Normalize the given hier path part. + * + * @param path the path to normalize + * @return the normalized path + */ + protected char[] normalize(char[] path) { + + if (path == null) + return null; + + String normalized = new String(path); + boolean endsWithSlash = true; + // precondition + if (!normalized.endsWith("/")) { + normalized += '/'; + endsWithSlash = false; + } + if (normalized.endsWith("/./") || normalized.endsWith("/../")) { + endsWithSlash = true; + } + // Resolve occurrences of "/./" in the normalized path + while (true) { + int at = normalized.indexOf("/./"); + if (at == -1) { + break; + } + normalized = normalized.substring(0, at) + normalized.substring(at + 2); + } + // Resolve occurrences of "/../" in the normalized path + while (true) { + int at = normalized.indexOf("/../"); + if (at == -1) { + break; + } + if (at == 0) { + normalized = "/"; + break; + } + int backward = normalized.lastIndexOf('/', at - 1); + if (backward == -1) { + // consider the rel_path + normalized = normalized.substring(at + 4); + } else { + normalized = normalized.substring(0, backward) + normalized.substring(at + 3); + } + } + // Resolve occurrences of "//" in the normalized path + while (true) { + int at = normalized.indexOf("//"); + if (at == -1) { + break; + } + normalized = normalized.substring(0, at) + normalized.substring(at + 1); + } + if (!endsWithSlash && normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } else if (endsWithSlash && !normalized.endsWith("/")) { + normalized = normalized + "/"; + } + // Set the normalized path that we have completed + return normalized.toCharArray(); + } + + /** + * Normalize the path part of this URI. + */ + public void normalize() { + _path = normalize(_path); + } + + /** + * Test if the first array is equal to the second array. + * + * @param first the first character array + * @param second the second character array + * @return true if they're equal + */ + protected boolean equals(char[] first, char[] second) { + + if (first == null && second == null) { + return true; + } + if (first == null || second == null) { + return false; + } + if (first.length != second.length) { + return false; + } + for (int i = 0; i < first.length; i++) { + if (first[i] != second[i]) { + return false; + } + } + return true; + } + + /** + * Test an object if this URI is equal to another. + * + * @param obj an object to compare + * @return true if two URI objects are equal + */ + public boolean equals(Object obj) { + + // normalize and test each components + if (obj == this) { + return true; + } + if (!(obj instanceof URI)) { + return false; + } + URI another = (URI) obj; + // scheme + if (!equals(_scheme, another._scheme)) { + return false; + } + // is_opaque_part or is_hier_part? and opaque + if (!equals(_opaque, another._opaque)) { + return false; + } + // is_hier_part + // has_authority + if (!equals(_authority, another._authority)) { + return false; + } + // path + if (!equals(_path, another._path)) { + return false; + } + // has_query + if (!equals(_query, another._query)) { + return false; + } + // has_fragment? should be careful of the only fragment case. + if (!equals(_fragment, another._fragment)) { + return false; + } + return true; + } + + // ---------------------------------------------------------- Serialization + + /** + * Write the content of this URI. + * + * @param oos the object-output stream + */ + protected void writeObject(java.io.ObjectOutputStream oos) throws IOException { + + oos.defaultWriteObject(); + } + + /** + * Read a URI. + * + * @param ois the object-input stream + */ + protected void readObject(java.io.ObjectInputStream ois) throws ClassNotFoundException, IOException { + + ois.defaultReadObject(); + } + + // ------------------------------------------------------------- Comparison + + /** + * Compare this URI to another object. + * + * @param obj the object to be compared. + * @return 0, if it's same, -1, if failed, first being compared with in the + * authority component + * @exception ClassCastException not URI argument + * @throws NullPointerException null object + */ + public int compareTo(Object obj) { + + URI another = (URI) obj; + if (!equals(_authority, another.getRawAuthority())) + return -1; + return toString().compareTo(another.toString()); + } + + // ------------------------------------------------------------------ Clone + + /** + * Create and return a copy of this object, the URI-reference containing the + * userinfo component. Notice that the whole URI-reference including the + * userinfo component counld not be gotten as a String. + *

+ * To copy the identical URI object including the userinfo + * component, it should be used. + * + * @return a clone of this instance + */ + public synchronized Object clone() { + + URI instance = new URI(); + + instance._uri = _uri; + instance._scheme = _scheme; + instance._opaque = _opaque; + instance._authority = _authority; + instance._userinfo = _userinfo; + instance._host = _host; + instance._port = _port; + instance._path = _path; + instance._query = _query; + instance._fragment = _fragment; + // flags + instance._is_hier_part = _is_hier_part; + instance._is_opaque_part = _is_opaque_part; + instance._is_net_path = _is_net_path; + instance._is_abs_path = _is_abs_path; + instance._is_rel_path = _is_rel_path; + instance._is_reg_name = _is_reg_name; + instance._is_server = _is_server; + instance._is_hostname = _is_hostname; + instance._is_IPv4address = _is_IPv4address; + instance._is_IPv6reference = _is_IPv6reference; + + return instance; + } + + // ------------------------------------------------------------ Get the URI + + /** + * It can be gotten the URI character sequence. It's raw-escaped. For the + * purpose of the protocol to be transported, it will be useful. + *

+ * It is clearly unwise to use a URL that contains a password which is intended + * to be secret. In particular, the use of a password within the 'userinfo' + * component of a URL is strongly disrecommended except in those rare cases + * where the 'password' parameter is intended to be public. + *

+ * When you want to get each part of the userinfo, you need to use the specific + * methods in the specific URL. It depends on the specific URL. + * + * @return URI character sequence + */ + public char[] getRawURI() { + return _uri; + } + + /** + * It can be gotten the URI character sequence. It's escaped. For the purpose of + * the protocol to be transported, it will be useful. + * + * @return the URI string + */ + public String getEscapedURI() { + return (_uri == null) ? null : new String(_uri); + } + + /** + * It can be gotten the URI character sequence. + * + * @return the URI string + * @exception IOException incomplete trailing escape pattern Or unsupported + * character encoding + * @see #decode + */ + public String getURI() throws IOException { + return (_uri == null) ? null : decode(_uri); + } + + /** + * Get the escaped URI string. + *

+ * On the document, the URI-reference form is only used without the userinfo + * component like http://jakarta.apache.org/ by the security reason. But the + * URI-reference form with the userinfo component could be parsed. + *

+ * In other words, this URI and any its subclasses must not expose the + * URI-reference expression with the userinfo component like + * http://user:password@hostport/restricted_zone.
+ * It means that the API client programmer should extract each user and password + * to access manually. Probably it will be supported in the each subclass, + * however, not a whole URI-reference expression. + * + * @return the URI string + * @see #clone() + */ + public String toString() { + return getEscapedURI(); + } + + // ------------------------------------------------------------ Inner class + + /** + * A mapping to determine the (somewhat arbitrarily) preferred charset for a + * given locale. Supports all locales recognized in JDK 1.1. + *

+ * The distribution of this class is Servlets.com. It was originally written by + * Jason Hunter [jhunter at acm.org] and used by with permission. + */ + public static class LocaleToCharsetMap { + + private static Hashtable map; + static { + map = new Hashtable(); + map.put("ar", "ISO-8859-6"); + map.put("be", "ISO-8859-5"); + map.put("bg", "ISO-8859-5"); + map.put("ca", "ISO-8859-1"); + map.put("cs", "ISO-8859-2"); + map.put("da", "ISO-8859-1"); + map.put("de", "ISO-8859-1"); + map.put("el", "ISO-8859-7"); + map.put("en", "ISO-8859-1"); + map.put("es", "ISO-8859-1"); + map.put("et", "ISO-8859-1"); + map.put("fi", "ISO-8859-1"); + map.put("fr", "ISO-8859-1"); + map.put("hr", "ISO-8859-2"); + map.put("hu", "ISO-8859-2"); + map.put("is", "ISO-8859-1"); + map.put("it", "ISO-8859-1"); + map.put("iw", "ISO-8859-8"); + map.put("ja", "Shift_JIS"); + map.put("ko", "EUC-KR"); + map.put("lt", "ISO-8859-2"); + map.put("lv", "ISO-8859-2"); + map.put("mk", "ISO-8859-5"); + map.put("nl", "ISO-8859-1"); + map.put("no", "ISO-8859-1"); + map.put("pl", "ISO-8859-2"); + map.put("pt", "ISO-8859-1"); + map.put("ro", "ISO-8859-2"); + map.put("ru", "ISO-8859-5"); + map.put("sh", "ISO-8859-5"); + map.put("sk", "ISO-8859-2"); + map.put("sl", "ISO-8859-2"); + map.put("sq", "ISO-8859-2"); + map.put("sr", "ISO-8859-5"); + map.put("sv", "ISO-8859-1"); + map.put("tr", "ISO-8859-9"); + map.put("uk", "ISO-8859-5"); + map.put("zh", "GB2312"); + map.put("zh_TW", "Big5"); + } + + /** + * Get the preferred charset for the given locale. + * + * @param locale the locale + * @return the preferred charset or null if the locale is not recognized + */ + public static String getCharset(Locale locale) { + // try for an full name match (may include country) + String charset = (String) map.get(locale.toString()); + if (charset != null) + return charset; + + // if a full name didn't match, try just the language + charset = (String) map.get(locale.getLanguage()); + return charset; // may be null + } + + } } - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java index 377345c..cc95697 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java @@ -19,33 +19,27 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web; /** -* An interface defining the results of a DirectAction. -* Implemented by both WOResponse and WOComponent so both -* can be returned from a DirectAction. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public interface WOActionResults -{ -/** -* Returns a response object as appropriate for the target. -*/ - WOResponse generateResponse (); + * An interface defining the results of a DirectAction. Implemented by both + * WOResponse and WOComponent so both can be returned from a DirectAction. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public interface WOActionResults { + /** + * Returns a response object as appropriate for the target. + */ + WOResponse generateResponse(); } /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ 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:44 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:44 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java index 78191b6..86e7807 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java @@ -21,20 +21,23 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** - * This dynamic element renders only the URL of a hyperlink. - * Bindings are: + * This dynamic element renders only the URL of a hyperlink. Bindings are: *

    *
  • href: The URL that the hyperlink should point to.
  • - *
  • pageName: The name of the WOComponent that the hyperlink should point to.
  • - *
  • directActionName: The name of the direct action to call when the link is activated.
  • - *
  • actionClass: The name of the WODirectAction subclass where the direct action resides.
  • - *
  • action: A pointer to a method on the component that contains this element. If the link is activated, - * the method will be called. + *
  • pageName: The name of the WOComponent that the hyperlink should point + * to.
  • + *
  • directActionName: The name of the direct action to call when the link is + * activated.
  • + *
  • actionClass: The name of the WODirectAction subclass where the direct + * action resides.
  • + *
  • action: A pointer to a method on the component that contains this + * element. If the link is activated, the method will be called. *
  • ref: The name of the anchor to go to inside the resulting page.
  • *
* - * The href, pageName and directActionName/actionClass and name properties are mutually exclusive and you should - * only use at most one of them simultaneously. + * The href, pageName and directActionName/actionClass and name properties are + * mutually exclusive and you should only use at most one of them + * simultaneously. * * @author ezamudio@nasoft.com * @author $Author: cgruber $ @@ -42,16 +45,16 @@ import net.wotonomy.foundation.NSDictionary; */ public class WOActionURL extends WOHyperlink { - public WOActionURL() { - super(); - } + public WOActionURL() { + super(); + } - public WOActionURL(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOActionURL(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(actionURL(c)); - } + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(actionURL(c)); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java index cad6f64..bf80b3e 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java @@ -7,8 +7,9 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; /** - * WOActiveImage renders a dynamically generated IMG tag, enclosed in a hyperlink. - * Internally, it uses a WOImage and a WOHyperlink to do the actual work. + * WOActiveImage renders a dynamically generated IMG tag, enclosed in a + * hyperlink. Internally, it uses a WOImage and a WOHyperlink to do the actual + * work. * * The bindings are those of WOImage and WOHyperlink combined. * @@ -18,64 +19,64 @@ import net.wotonomy.foundation.NSMutableDictionary; */ public class WOActiveImage extends WODynamicElement { - protected WOActiveImage() { - super(); - } + protected WOActiveImage() { + super(); + } - public WOActiveImage(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } + public WOActiveImage(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } - public void appendToResponse(WOResponse r, WOContext c) { - NSMutableDictionary atribs = new NSMutableDictionary(5); - if (associations.objectForKey("mimeType") != null) - atribs.setObjectForKey(associations.objectForKey("mimeType"), "mimeType"); - if (associations.objectForKey("data") != null) - atribs.setObjectForKey(associations.objectForKey("data"), "data"); - if (associations.objectForKey("src") != null) - atribs.setObjectForKey(associations.objectForKey("src"), "src"); - if (associations.objectForKey("framework") != null) - atribs.setObjectForKey(associations.objectForKey("framework"), "framework"); - if (associations.objectForKey("filename") != null) - atribs.setObjectForKey(associations.objectForKey("filename"), "filename"); - if (associations.objectForKey("alt") != null) - atribs.setObjectForKey(associations.objectForKey("alt"), "alt"); - if (associations.objectForKey("border") != null) - atribs.setObjectForKey(associations.objectForKey("border"), "border"); - if (associations.objectForKey("width") != null) - atribs.setObjectForKey(associations.objectForKey("width"), "width"); - if (associations.objectForKey("height") != null) - atribs.setObjectForKey(associations.objectForKey("height"), "height"); - WODynamicElement img = new WOImage("WOImage_" + name, atribs, null); - NSMutableDictionary uf = new NSMutableDictionary(); - Enumeration enumeration = associations.keyEnumerator(); - while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); - if (key.startsWith("?")) - uf.setObjectForKey(associations.objectForKey(key), key); - } - createLink(img).appendToResponse(r, c); - } + public void appendToResponse(WOResponse r, WOContext c) { + NSMutableDictionary atribs = new NSMutableDictionary(5); + if (associations.objectForKey("mimeType") != null) + atribs.setObjectForKey(associations.objectForKey("mimeType"), "mimeType"); + if (associations.objectForKey("data") != null) + atribs.setObjectForKey(associations.objectForKey("data"), "data"); + if (associations.objectForKey("src") != null) + atribs.setObjectForKey(associations.objectForKey("src"), "src"); + if (associations.objectForKey("framework") != null) + atribs.setObjectForKey(associations.objectForKey("framework"), "framework"); + if (associations.objectForKey("filename") != null) + atribs.setObjectForKey(associations.objectForKey("filename"), "filename"); + if (associations.objectForKey("alt") != null) + atribs.setObjectForKey(associations.objectForKey("alt"), "alt"); + if (associations.objectForKey("border") != null) + atribs.setObjectForKey(associations.objectForKey("border"), "border"); + if (associations.objectForKey("width") != null) + atribs.setObjectForKey(associations.objectForKey("width"), "width"); + if (associations.objectForKey("height") != null) + atribs.setObjectForKey(associations.objectForKey("height"), "height"); + WODynamicElement img = new WOImage("WOImage_" + name, atribs, null); + NSMutableDictionary uf = new NSMutableDictionary(); + Enumeration enumeration = associations.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String key = (String) enumeration.nextElement(); + if (key.startsWith("?")) + uf.setObjectForKey(associations.objectForKey(key), key); + } + createLink(img).appendToResponse(r, c); + } - public WOActionResults invokeAction(WORequest r, WOContext c) { - return createLink(null).invokeAction(r, c); - } + public WOActionResults invokeAction(WORequest r, WOContext c) { + return createLink(null).invokeAction(r, c); + } - protected WOHyperlink createLink(WOElement e) { - NSMutableDictionary atribs = new NSMutableDictionary(5); - if (associations.objectForKey("href") != null) - atribs.setObjectForKey(associations.objectForKey("href"), "href"); - if (associations.objectForKey("pageName") != null) - atribs.setObjectForKey(associations.objectForKey("pageName"), "pageName"); - if (associations.objectForKey("action") != null) - atribs.setObjectForKey(associations.objectForKey("action"), "action"); - if (associations.objectForKey("directActionName") != null) - atribs.setObjectForKey(associations.objectForKey("directActionName"), "directActionName"); - if (associations.objectForKey("actionClass") != null) - atribs.setObjectForKey(associations.objectForKey("actionClass"), "actionClass"); - if (associations.objectForKey("target") != null) - atribs.setObjectForKey(associations.objectForKey("target"), "target"); - return new WOHyperlink(name, atribs, e); - } + protected WOHyperlink createLink(WOElement e) { + NSMutableDictionary atribs = new NSMutableDictionary(5); + if (associations.objectForKey("href") != null) + atribs.setObjectForKey(associations.objectForKey("href"), "href"); + if (associations.objectForKey("pageName") != null) + atribs.setObjectForKey(associations.objectForKey("pageName"), "pageName"); + if (associations.objectForKey("action") != null) + atribs.setObjectForKey(associations.objectForKey("action"), "action"); + if (associations.objectForKey("directActionName") != null) + atribs.setObjectForKey(associations.objectForKey("directActionName"), "directActionName"); + if (associations.objectForKey("actionClass") != null) + atribs.setObjectForKey(associations.objectForKey("actionClass"), "actionClass"); + if (associations.objectForKey("target") != null) + atribs.setObjectForKey(associations.objectForKey("target"), "target"); + return new WOHyperlink(name, atribs, e); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java index 90e40c6..46b598c 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java @@ -37,1157 +37,982 @@ import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.util.InetAddrPort; /** -* A pure java implementation of WOApplication.

-* -* The application is responsible for creating and managing sessions -* and dispatching requests to the appropriate handlers.

-* -* This implementation extends HttpServlet, so the application itself -* is a servlet and can be configured and managed as such by the servlet -* container. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOApplication - extends HttpServlet -{ - /** - * A tricky way to allow multiple WOApplications - * in the same servlet container. - */ - static private ThreadLocal threadLocal; - //static private WOApplication application; - - /** - * Determines application-wide page caching. - * Pages may individually prevent caching. - */ - static private boolean cachingEnabled = false; - private static boolean autoOpenInBrowser = true; - - private String name; + * A pure java implementation of WOApplication.
+ *
+ * + * The application is responsible for creating and managing sessions and + * dispatching requests to the appropriate handlers.
+ *
+ * + * This implementation extends HttpServlet, so the application itself is a + * servlet and can be configured and managed as such by the servlet container. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOApplication extends HttpServlet { + /** + * A tricky way to allow multiple WOApplications in the same servlet container. + */ + static private ThreadLocal threadLocal; + // static private WOApplication application; + + /** + * Determines application-wide page caching. Pages may individually prevent + * caching. + */ + static private boolean cachingEnabled = false; + private static boolean autoOpenInBrowser = true; + + private String name; private WORequestHandler defaultRequestHandler; - private NSMutableDictionary requestHandlers; - private WOSessionStore sessionStore; - private WOResourceManager resourceManager; - private boolean pageRefreshOnBacktrack; - private int pageCacheSize; - private int permanentPageCacheSize; - - public static final String WOApplicationWillFinishLaunchingNotification - = "WOApplicationWillFinishLaunchingNotification"; - public static final String WOApplicationDidFinishLaunchingNotification - = "WOApplicationDidFinishLaunchingNotification"; - public static final String WOGarbageCollectionPeriodKey - = "WOGarbageCollectionPeriodKey"; - - static String _DirectActionRequestHandlerKey = "_DirectActionRequestHandlerKey"; - static String _ComponentRequestHandlerKey = "_ComponentRequestHandlerKey"; - static String _ResourceRequestHandlerKey = "_ResourceRequestHandlerKey"; - static String WOPort = "WOPort"; - static String WOSMTPHost = "WOSMTPHost"; - static final String ELEMENT_CLASS = "elementClass"; - - public WOApplication () - { - if ( threadLocal == null ) - { - threadLocal = new ThreadLocal(); - } - threadLocal.set( this ); - - //application = this; - resourceManager = createResourceManager(); - requestHandlers = new NSMutableDictionary(); - defaultRequestHandler = new WODirectActionRequestHandler(); - registerRequestHandler( defaultRequestHandler, directActionRequestHandlerKey() ); - registerRequestHandler( new WOComponentRequestHandler(), componentRequestHandlerKey() ); - registerRequestHandler( new WOResourceRequestHandler(), resourceRequestHandlerKey() ); - sessionStore = WOSessionStore.serverSessionStore(); - - pageRefreshOnBacktrack = true; - pageCacheSize = 30; - permanentPageCacheSize = 30; - - threadLocal.set( null ); - } - + private NSMutableDictionary requestHandlers; + private WOSessionStore sessionStore; + private WOResourceManager resourceManager; + private boolean pageRefreshOnBacktrack; + private int pageCacheSize; + private int permanentPageCacheSize; + + public static final String WOApplicationWillFinishLaunchingNotification = "WOApplicationWillFinishLaunchingNotification"; + public static final String WOApplicationDidFinishLaunchingNotification = "WOApplicationDidFinishLaunchingNotification"; + public static final String WOGarbageCollectionPeriodKey = "WOGarbageCollectionPeriodKey"; + + static String _DirectActionRequestHandlerKey = "_DirectActionRequestHandlerKey"; + static String _ComponentRequestHandlerKey = "_ComponentRequestHandlerKey"; + static String _ResourceRequestHandlerKey = "_ResourceRequestHandlerKey"; + static String WOPort = "WOPort"; + static String WOSMTPHost = "WOSMTPHost"; + static final String ELEMENT_CLASS = "elementClass"; + + public WOApplication() { + if (threadLocal == null) { + threadLocal = new ThreadLocal(); + } + threadLocal.set(this); + + // application = this; + resourceManager = createResourceManager(); + requestHandlers = new NSMutableDictionary(); + defaultRequestHandler = new WODirectActionRequestHandler(); + registerRequestHandler(defaultRequestHandler, directActionRequestHandlerKey()); + registerRequestHandler(new WOComponentRequestHandler(), componentRequestHandlerKey()); + registerRequestHandler(new WOResourceRequestHandler(), resourceRequestHandlerKey()); + sessionStore = WOSessionStore.serverSessionStore(); + + pageRefreshOnBacktrack = true; + pageCacheSize = 30; + permanentPageCacheSize = 30; + + threadLocal.set(null); + } + /** - * Dispatches the request and updates the specified response - * as appropriate. This implementation creates a new WORequest - * and WOContext from the request, sends the response to the - * appropriate WORequestHandler, and then updates the servlet - * response from the resulting WOResponse. - */ - protected void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, java.io.IOException - { - threadLocal.set( this ); - - WORequest request = new WORequest( req, this ); - WOResponse response = dispatchRequest( request ); - response.generateServletResponse( resp ); - } + * Dispatches the request and updates the specified response as appropriate. + * This implementation creates a new WORequest and WOContext from the request, + * sends the response to the appropriate WORequestHandler, and then updates the + * servlet response from the resulting WOResponse. + */ + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, java.io.IOException { + threadLocal.set(this); + + WORequest request = new WORequest(req, this); + WOResponse response = dispatchRequest(request); + response.generateServletResponse(resp); + } /** - * Handles post requests by calling doGet(), since the framework - * handles both gets and posts similarly. Override to handle - * post requests in a different manner. - */ - protected void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, java.io.IOException - { - doGet( req, resp ); + * Handles post requests by calling doGet(), since the framework handles both + * gets and posts similarly. Override to handle post requests in a different + * manner. + */ + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, java.io.IOException { + doGet(req, resp); } - + // obtaining attributes - + + /** + * Returns the singleton instance of this application. + */ + public static WOApplication application() { + return (WOApplication) threadLocal.get(); + // return application; + } + + /** + * Returns the name of the application. This implementation returns the name the + * jar file or directory from which the class was loaded, with no extensions. + */ + public String name() { + if (name == null) { + name = path(); + int i; + if (name.endsWith("/")) { // path + name = name.substring(0, name.length() - 1); + } else { // jar file + i = name.lastIndexOf('.'); + if (i != -1) + name = name.substring(0, i); + } + i = name.lastIndexOf('/'); + if (i != -1) + name = name.substring(i + 1); + } + return name; + } + /** - * Returns the singleton instance of this application. - */ - public static WOApplication application() - { - return (WOApplication) threadLocal.get(); - //return application; - } - + * Returns the absolute path to the application on the local file system. Note + * that the application might be embedded inside of a jar file. + */ + public String path() { + return getClass().getProtectionDomain().getCodeSource().getLocation().toString(); + } + /** - * Returns the name of the application. This implementation returns - * the name the jar file or directory from which the class was loaded, - * with no extensions. - */ - public String name() - { - if ( name == null ) - { - name = path(); - int i; - if ( name.endsWith( "/" ) ) - { // path - name = name.substring( 0, name.length() - 1 ); - } - else - { // jar file - i = name.lastIndexOf( '.' ); - if ( i != -1 ) name = name.substring( 0, i ); - } - i = name.lastIndexOf( '/' ); - if ( i != -1 ) name = name.substring( i+1 ); - } - return name; - } - - /** - * Returns the absolute path to the application on the local file system. - * Note that the application might be embedded inside of a jar file. - */ - public String path () - { - return getClass().getProtectionDomain().getCodeSource().getLocation().toString(); - } - - /** - * Returns the path to the application on the local file system - * relative to the server's document root. - */ - public String baseURL () - { - String root = getServletContext().getRealPath( "/" ); - String result = path(); - if ( result.endsWith("/") ) - { // path - if ( result.startsWith( root ) ) - { // relative to root - result = result.substring( root.length() ); - } - // else leave as absolute reference - } - // jar or war file: leave as absolute reference - return result; - } - - // concurrent request handling - - /** - * Returns whether this application allows request - * to be handled concurrently. - * This implementation returns true. - * Subclasses may override to return false to force - * single-threaded request handling, although this - * is not implemented. - */ - public boolean allowsConcurrentRequestHandling () - { - return true; - } - - /** - * Returns whether this application allows request - * to be handled concurrently. - * This implementation returns true. - */ - public boolean adaptorsDispatchRequestsConcurrently () - { - return true; - } - - /** - * Returns whether this application allows request - * to be handled concurrently. - * This implementation returns true. - */ - public boolean isConcurrentRequestHandlingEnabled () - { - return true; - } - - // handling requests + * Returns the path to the application on the local file system relative to the + * server's document root. + */ + public String baseURL() { + String root = getServletContext().getRealPath("/"); + String result = path(); + if (result.endsWith("/")) { // path + if (result.startsWith(root)) { // relative to root + result = result.substring(root.length()); + } + // else leave as absolute reference + } + // jar or war file: leave as absolute reference + return result; + } + + // concurrent request handling /** - * Invoked first in the request-response cycle. - * Override to perform any kind of initialization at the - * start of a request. This implementation does nothing. - */ - public void awake () - { - - } - - /** - * Invoked to start the first phase of the request-response cycle, - * after all calls to awake() have been completed. - */ - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - aContext.session().takeValuesFromRequest( aRequest, aContext ); + * Returns whether this application allows request to be handled concurrently. + * This implementation returns true. Subclasses may override to return false to + * force single-threaded request handling, although this is not implemented. + */ + public boolean allowsConcurrentRequestHandling() { + return true; } - /** - * Invoked to start the second phase of the request-response cycle, - * after all calls to takeValuesFromRequest have finished. - */ - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - return aContext.session().invokeAction( aRequest, aContext ); - } - - /** - * Invoked to start the third phase of the request-response cycle, - * after invokeAction() has completed and returned a WOResponse. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - aContext.session().appendToResponse( aResponse, aContext ); - } - - /** - * Invoked last in the request-response cycle. - * Override to perform any kind of clean-up at the - * end of a request. This implementation does nothing. - */ - public void sleep () - { - - } - - /** - * Dispatches the request to the appropriate handler. - */ - public WOResponse dispatchRequest (WORequest aRequest) - { - return handlerForRequest( aRequest ).handleRequest( aRequest ); - } - - // request handling - - /** - * Returns the default request handler used if the requested - * handler isn't specified or cannot be found. (This defaults - * to the WODirectActionRequestHandler.) - */ - public WORequestHandler defaultRequestHandler () - { - return defaultRequestHandler; - } - - /** - * Sets the default request handler used if the requested - * handler isn't specified or cannot be found. - */ - public void setDefaultRequestHandler (WORequestHandler aRequestHandler) - { - defaultRequestHandler = aRequestHandler; - } - - /** - * Registers the specified request handler for the specified key. - */ - public void registerRequestHandler (WORequestHandler aRequestHandler, String aKey) - { - requestHandlers.setObjectForKey( aRequestHandler, aKey ); - } - - /** - * Unregisters any existing request handler for the specified key - * returning the existing request handler, if any. - */ - public WORequestHandler removeRequestHandlerForKey (String aKey) - { - WORequestHandler result = requestHandlerForKey( aKey ); - requestHandlers.removeObjectForKey( aKey ); - return result; - } - - /** - * Returns the keys under which request handlers are registered. - */ - public NSArray registeredRequestHandlerKeys () - { - return requestHandlers.allKeys(); - } - - /** - * Returns the request handler registered for the specified key, - * or null if no request handler is registered for that key. - */ - public WORequestHandler requestHandlerForKey (String aKey) - { - return (WORequestHandler) requestHandlers.objectForKey( aKey ); - } - - /** - * Returns the request handler that would best service the specified request. - */ - public WORequestHandler handlerForRequest (WORequest aRequest) - { - WORequestHandler result = requestHandlerForKey( aRequest.requestHandlerKey() ); - if ( aRequest == null ) result = defaultRequestHandler(); - return result; - } - - // handling errors - - public WOResponse handleSessionCreationErrorInContext( WOContext aContext ) - { - WOResponse response = new WOResponse(); - response.setStatus( 500 ); // internal server error - //TODO: add more useful information to the response - System.err.println( "Failed to create session: " + aContext ); -new RuntimeException().printStackTrace(); // remove me - return response; - } - - public WOResponse handleSessionRestorationErrorInContext( WOContext aContext ) - { - WOResponse response = new WOResponse(); - response.setStatus( 500 ); // internal server error - //TODO: add more useful information to the response - System.err.println( "Failed to restore session: " + aContext ); -new RuntimeException().printStackTrace(); // remove me - return response; - } - - public WOResponse handlePageRestorationErrorInContext( WOContext aContext ) - { - WOResponse response = new WOResponse(); - response.setStatus( 500 ); // internal server error - //TODO: add more useful information to the response - System.err.println( "Failed to restore page: " + aContext ); -new RuntimeException().printStackTrace(); // remove me - return response; - } - - public WOResponse handleException( Throwable aThrowable, WOContext aContext ) - { - WOResponse response = new WOResponse(); - response.setStatus( 500 ); // internal server error - System.err.println( "Exception occurred: " + aContext ); - if ( aThrowable.getMessage() != null ) - { - response.appendContentString( aThrowable.getMessage() ); - aThrowable.printStackTrace(); - } - else - { - response.appendContentString( aThrowable.toString() ); - aThrowable.printStackTrace(); - } - aThrowable.printStackTrace(); - return response; - } - - // managing pages - - /** - * Sets the number of pages that will be retained - * in the user's session. Set to zero to disable page caching. - */ - public void setPageCacheSize (int aPositiveInt) - { - pageCacheSize = aPositiveInt; - } - - /** - * Returns the number of pages that will be retained - * in the user's session. The default page cache size is 30. - */ - public int pageCacheSize () - { - return pageCacheSize; - } - - /** - * Returns the number of pages that will be retained in the - * longer-term "permanent" page cache in the user's session, - * which is typically used for navigation bars in frames, etc. - * The default permanent page cache size is 30. - */ - public int permanentPageCacheSize () - { - return permanentPageCacheSize; - } - - /** - * Returns the number of pages that will be retained in the - * longer-term "permanent" page cache in the user's session, - * which is typically used for navigation bars in frames, etc. - * Set to zero to disable permanent page caching. - */ - public void setPermanentPageCacheSize (int aPositiveInt) - { - permanentPageCacheSize = aPositiveInt; - } - - /** - * Returns whether a "backtrack" for an existing page should - * simply call generateResponse() on the existing page instance. - * If false, a new page is created instead. The default is true. - */ - public void setPageRefreshOnBacktrackEnabled (boolean enabled) - { - pageRefreshOnBacktrack = enabled; - } - - /** - * Returns whether a "backtrack" for an existing page should - * simply call generateResponse() on the existing page instance. - * If false, a new page is created instead. The default is true. - */ - public boolean isPageRefreshOnBacktrackEnabled () - { - return pageRefreshOnBacktrack; - } - - // managing sessions - - /** - * Sets the session store used by this application to persist - * sessions between request-response transactions. - */ - public void setSessionStore(WOSessionStore aSessionStore) - { - sessionStore = aSessionStore; - } - - /** - * Returns the session store used by this application to persist - * sessions between request-response transactions. - */ - public WOSessionStore sessionStore() - { - return sessionStore; - } - - /** - * Called at the end of the request-response cycle - * to persist the current session until the user's next request. - */ - public void saveSessionForContext(WOContext aContext) - { - sessionStore.saveSessionForContext( aContext ); - } - - /** - * Called at the beginning of the request-response cycle - * to obtain the current session from the user's last request. - * Returns null if no such session has been created. - * This method sets the context of the session to the specified context. - */ - public WOSession restoreSessionWithID(String aSessionID, WOContext aContext) - { - WORequest request = aContext.request(); - WOSession session = sessionStore.restoreSessionWithID( aSessionID, request ); - if ( session != null ) - { - session.setContext( aContext ); - session.setServletSession( request.servletRequest().getSession() ); - } - return session; - } - - /** - * Called to create a session for a new request. This implementation - * looks for a class in the same package as the application class - * called "Session" and failing that returns a WOSession. - */ - public WOSession createSessionForRequest(WORequest aRequest) - { - WOSession result = null; - try - { + /** + * Returns whether this application allows request to be handled concurrently. + * This implementation returns true. + */ + public boolean adaptorsDispatchRequestsConcurrently() { + return true; + } + + /** + * Returns whether this application allows request to be handled concurrently. + * This implementation returns true. + */ + public boolean isConcurrentRequestHandlingEnabled() { + return true; + } + + // handling requests + + /** + * Invoked first in the request-response cycle. Override to perform any kind of + * initialization at the start of a request. This implementation does nothing. + */ + public void awake() { + + } + + /** + * Invoked to start the first phase of the request-response cycle, after all + * calls to awake() have been completed. + */ + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + aContext.session().takeValuesFromRequest(aRequest, aContext); + } + + /** + * Invoked to start the second phase of the request-response cycle, after all + * calls to takeValuesFromRequest have finished. + */ + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + return aContext.session().invokeAction(aRequest, aContext); + } + + /** + * Invoked to start the third phase of the request-response cycle, after + * invokeAction() has completed and returned a WOResponse. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + aContext.session().appendToResponse(aResponse, aContext); + } + + /** + * Invoked last in the request-response cycle. Override to perform any kind of + * clean-up at the end of a request. This implementation does nothing. + */ + public void sleep() { + + } + + /** + * Dispatches the request to the appropriate handler. + */ + public WOResponse dispatchRequest(WORequest aRequest) { + return handlerForRequest(aRequest).handleRequest(aRequest); + } + + // request handling + + /** + * Returns the default request handler used if the requested handler isn't + * specified or cannot be found. (This defaults to the + * WODirectActionRequestHandler.) + */ + public WORequestHandler defaultRequestHandler() { + return defaultRequestHandler; + } + + /** + * Sets the default request handler used if the requested handler isn't + * specified or cannot be found. + */ + public void setDefaultRequestHandler(WORequestHandler aRequestHandler) { + defaultRequestHandler = aRequestHandler; + } + + /** + * Registers the specified request handler for the specified key. + */ + public void registerRequestHandler(WORequestHandler aRequestHandler, String aKey) { + requestHandlers.setObjectForKey(aRequestHandler, aKey); + } + + /** + * Unregisters any existing request handler for the specified key returning the + * existing request handler, if any. + */ + public WORequestHandler removeRequestHandlerForKey(String aKey) { + WORequestHandler result = requestHandlerForKey(aKey); + requestHandlers.removeObjectForKey(aKey); + return result; + } + + /** + * Returns the keys under which request handlers are registered. + */ + public NSArray registeredRequestHandlerKeys() { + return requestHandlers.allKeys(); + } + + /** + * Returns the request handler registered for the specified key, or null if no + * request handler is registered for that key. + */ + public WORequestHandler requestHandlerForKey(String aKey) { + return (WORequestHandler) requestHandlers.objectForKey(aKey); + } + + /** + * Returns the request handler that would best service the specified request. + */ + public WORequestHandler handlerForRequest(WORequest aRequest) { + WORequestHandler result = requestHandlerForKey(aRequest.requestHandlerKey()); + if (aRequest == null) + result = defaultRequestHandler(); + return result; + } + + // handling errors + + public WOResponse handleSessionCreationErrorInContext(WOContext aContext) { + WOResponse response = new WOResponse(); + response.setStatus(500); // internal server error + // TODO: add more useful information to the response + System.err.println("Failed to create session: " + aContext); + new RuntimeException().printStackTrace(); // remove me + return response; + } + + public WOResponse handleSessionRestorationErrorInContext(WOContext aContext) { + WOResponse response = new WOResponse(); + response.setStatus(500); // internal server error + // TODO: add more useful information to the response + System.err.println("Failed to restore session: " + aContext); + new RuntimeException().printStackTrace(); // remove me + return response; + } + + public WOResponse handlePageRestorationErrorInContext(WOContext aContext) { + WOResponse response = new WOResponse(); + response.setStatus(500); // internal server error + // TODO: add more useful information to the response + System.err.println("Failed to restore page: " + aContext); + new RuntimeException().printStackTrace(); // remove me + return response; + } + + public WOResponse handleException(Throwable aThrowable, WOContext aContext) { + WOResponse response = new WOResponse(); + response.setStatus(500); // internal server error + System.err.println("Exception occurred: " + aContext); + if (aThrowable.getMessage() != null) { + response.appendContentString(aThrowable.getMessage()); + aThrowable.printStackTrace(); + } else { + response.appendContentString(aThrowable.toString()); + aThrowable.printStackTrace(); + } + aThrowable.printStackTrace(); + return response; + } + + // managing pages + + /** + * Sets the number of pages that will be retained in the user's session. Set to + * zero to disable page caching. + */ + public void setPageCacheSize(int aPositiveInt) { + pageCacheSize = aPositiveInt; + } + + /** + * Returns the number of pages that will be retained in the user's session. The + * default page cache size is 30. + */ + public int pageCacheSize() { + return pageCacheSize; + } + + /** + * Returns the number of pages that will be retained in the longer-term + * "permanent" page cache in the user's session, which is typically used for + * navigation bars in frames, etc. The default permanent page cache size is 30. + */ + public int permanentPageCacheSize() { + return permanentPageCacheSize; + } + + /** + * Returns the number of pages that will be retained in the longer-term + * "permanent" page cache in the user's session, which is typically used for + * navigation bars in frames, etc. Set to zero to disable permanent page + * caching. + */ + public void setPermanentPageCacheSize(int aPositiveInt) { + permanentPageCacheSize = aPositiveInt; + } + + /** + * Returns whether a "backtrack" for an existing page should simply call + * generateResponse() on the existing page instance. If false, a new page is + * created instead. The default is true. + */ + public void setPageRefreshOnBacktrackEnabled(boolean enabled) { + pageRefreshOnBacktrack = enabled; + } + + /** + * Returns whether a "backtrack" for an existing page should simply call + * generateResponse() on the existing page instance. If false, a new page is + * created instead. The default is true. + */ + public boolean isPageRefreshOnBacktrackEnabled() { + return pageRefreshOnBacktrack; + } + + // managing sessions + + /** + * Sets the session store used by this application to persist sessions between + * request-response transactions. + */ + public void setSessionStore(WOSessionStore aSessionStore) { + sessionStore = aSessionStore; + } + + /** + * Returns the session store used by this application to persist sessions + * between request-response transactions. + */ + public WOSessionStore sessionStore() { + return sessionStore; + } + + /** + * Called at the end of the request-response cycle to persist the current + * session until the user's next request. + */ + public void saveSessionForContext(WOContext aContext) { + sessionStore.saveSessionForContext(aContext); + } + + /** + * Called at the beginning of the request-response cycle to obtain the current + * session from the user's last request. Returns null if no such session has + * been created. This method sets the context of the session to the specified + * context. + */ + public WOSession restoreSessionWithID(String aSessionID, WOContext aContext) { + WORequest request = aContext.request(); + WOSession session = sessionStore.restoreSessionWithID(aSessionID, request); + if (session != null) { + session.setContext(aContext); + session.setServletSession(request.servletRequest().getSession()); + } + return session; + } + + /** + * Called to create a session for a new request. This implementation looks for a + * class in the same package as the application class called "Session" and + * failing that returns a WOSession. + */ + public WOSession createSessionForRequest(WORequest aRequest) { + WOSession result = null; + try { // using our class loader, which is hopefully dynamic. - result = (WOSession) getLocalClass( "Session" ).newInstance(); - } - catch ( Throwable t ) - { - // ignore: fall back to WOSession - //t.printStackTrace(); - } - - if ( result == null ) - { - result = new WOSession(); - } - - result.setServletSession( aRequest.servletRequest().getSession( true ) ); - return result; - } - + result = (WOSession) getLocalClass("Session").newInstance(); + } catch (Throwable t) { + // ignore: fall back to WOSession + // t.printStackTrace(); + } + + if (result == null) { + result = new WOSession(); + } + + result.setServletSession(aRequest.servletRequest().getSession(true)); + return result; + } + + /** + * Returns the page component with the specified name. A context is created with + * the specified request, along with a session if necessary. + */ + public WOComponent pageWithName(String aName, WORequest aRequest) { + return pageWithName(aName, WOContext.contextWithRequest(aRequest)); + } + /** - * Returns the page component with the specified name. - * A context is created with the specified request, - * along with a session if necessary. - */ - public WOComponent pageWithName (String aName, WORequest aRequest) - { - return pageWithName( aName, WOContext.contextWithRequest( aRequest ) ); - } - - /** - * Called to retrieve a component for the specified context. - */ - public WOComponent pageWithName (String aName, WOContext aContext) - { - if ( aName == null ) - { - throw new IllegalArgumentException( - "WOApplication.pageWithName: name is null" ); - } - - WOComponent result = null; - try - { + * Called to retrieve a component for the specified context. + */ + public WOComponent pageWithName(String aName, WOContext aContext) { + if (aName == null) { + throw new IllegalArgumentException("WOApplication.pageWithName: name is null"); + } + + WOComponent result = null; + try { // using our class loader, which is hopefully dynamic. - Class c = getLocalClass( aName ); - - if ( c != null ) - { - // get constructor - Constructor ctor; - try - { - ctor = c.getConstructor( new Class[] { WOContext.class } ); - } - catch ( NoSuchMethodException nsme ) - { - ctor = null; - } - - // create instance of class - if ( ctor != null ) - { - result = (WOComponent) ctor.newInstance( new Object[] { aContext } ); - } - else // call back on default constructor (deprecated) - { - result = (WOComponent) c.newInstance(); - } - } - } - catch ( Throwable t ) - { - // ignore for now - //TODO: Throw appropriate exception here - //System.err.println( "Not found: pageWithName: " + aName ); - t.printStackTrace(); - } - - if ( result != null && aContext != null ) - { - // this is where components get their context - result.ensureAwakeInContext( aContext ); - } - else - if ( result == null ) - { - System.err.println( "Not found: pageWithName: " + aName ); - } - - return result; - } - - /** - * Returns a class in the same package as the Application class, - * or, failing that, from the WOApplication package, or finally - * from the root of the class path. Returns null if not found. - */ - Class getLocalClass( String aName ) - { - Class result = null; - if ( getClass() != WOApplication.class ) - { - result = loadLocalClass( getClass(), aName ); - } - if ( result == null ) - { - result = loadLocalClass( WOApplication.class, aName ); - } - if ( result == null ) - { - result = loadLocalClass( null, aName ); - } - return result; - } - - private static final Class loadLocalClass( Class aClass, String aName ) - { - ClassLoader loader; - String packageName = ""; - if ( aClass != null ) - { - loader = aClass.getClassLoader(); - packageName = aClass.getName(); - int index = packageName.lastIndexOf( "." ); - if ( index > -1 ) - { - packageName = packageName.substring( 0, index+1 ); - } - else - { - packageName = ""; - } - } - else - { - loader = WOApplication.class.getClassLoader(); - } - - try - { - return loader.loadClass( packageName + aName ); - } - catch ( ClassNotFoundException e ) - { - return null; - } - } - - // creating elements - - /** - * Returns either a dynamic element or a component - * for the specified name. - */ - public WOElement dynamicElementWithName( - String anElementName, NSDictionary anAssociationMap, - WOElement aBodyElement, List aLanguageList) - { - WOElement element = null; - Class c = null; - try - { - c = getLocalClass( anElementName ); - if ( c == null ) - { - System.out.println( "Not found: dynamicElementWithName: " + - "could not find WODynamicElement subclass: " + anElementName ); - c = WODynamicElement.class; - } - - // get constructor - Class[] params = new Class[] - { String.class, NSDictionary.class, WOElement.class }; - Constructor ctor = c.getConstructor( params ); - - // create instance of class - if ( ctor != null ) - { - element = (WODynamicElement) ctor.newInstance( - new Object[] { anElementName, anAssociationMap, aBodyElement } ); - } - } - catch ( Throwable t ) - { - // ignore: not a dynamic element - //System.out.println( "Not a dynamic element: dynamicElementWithName: " + t ); - //exc.printStackTrace(); - } - - // no dynamic element found: look for a component - if ( element == null ) - { - WOComponent component = (WOComponent) pageWithName( anElementName, (WOContext) null ); - - // this seems hackish: - // I don't see another way of setting the bindings. - component.associations = anAssociationMap; - component.rootElement = aBodyElement; - - element = component; - } - - return element; - } - - // resource handling - - /** - * Called to create the application's resource manager. - * Override to create a custom resource manager. - */ - public WOResourceManager createResourceManager() - { - return new WOResourceManager(); - } - - /** - * Returns the application's current resource manager. - */ - public WOResourceManager resourceManager() - { - return resourceManager; - } - - /** - * Installs a custom resource manager into the current application. - * @deprecated Override createResourceManager() instead. - */ - public void setResourceManager(WOResourceManager aResourceManager) - { - resourceManager = aResourceManager; - } + Class c = getLocalClass(aName); + + if (c != null) { + // get constructor + Constructor ctor; + try { + ctor = c.getConstructor(new Class[] { WOContext.class }); + } catch (NoSuchMethodException nsme) { + ctor = null; + } + + // create instance of class + if (ctor != null) { + result = (WOComponent) ctor.newInstance(new Object[] { aContext }); + } else // call back on default constructor (deprecated) + { + result = (WOComponent) c.newInstance(); + } + } + } catch (Throwable t) { + // ignore for now + // TODO: Throw appropriate exception here + // System.err.println( "Not found: pageWithName: " + aName ); + t.printStackTrace(); + } + + if (result != null && aContext != null) { + // this is where components get their context + result.ensureAwakeInContext(aContext); + } else if (result == null) { + System.err.println("Not found: pageWithName: " + aName); + } + + return result; + } -/* - // request handling undocumented - - public WOComponent pageWithName (String); - public void savePage (WOComponent); - public WOComponent restorePageForContextID (String); - public WOContext context (); - public WOSession session (); - public WOSession createSession (); - public WOSession restoreSession (); - public void saveSession (WOSession); - - public WOResponse handleRequest (WORequest aRequest) - { - } - - // error handling undocumented - - WOResponse handleSessionCreationError (); - WOResponse handleSessionRestorationError (); - WOResponse handlePageRestorationError (); - WOResponse handleException (Throwable); - - // running - - public NSRunLoop runLoop (); - public void run (); - public void setTimeOut (double); - public double timeOut (); - public void terminate (); - public boolean isTerminating (); - - // script debugging - - public void traceScriptedMessages (boolean); - public void traceAssignments (boolean); - public void traceStatements (boolean); - public void traceObjectiveCMessages (boolean); - public void trace (boolean); - public void logTakeValueForDeclarationNamed (String, String, String, String, Object); - public void logSetValueForDeclarationNamed (String, String, String, String, Object); - - // script handling - - public String scriptedClassNameWithPath (String); - public String scriptedClassNameWithPathEncoding (String, int); - - // statistics report - - public void setStatisticsStore (WOStatisticsStore); - public WOStatisticsStore statisticsStore (); - public NSDictionary statistics (); - - // managing adaptors - - public WOAdaptor adaptorWithName (String, NSDictionary); - public NSArray adaptors (); - - // monitor support - - public boolean monitoringEnabled (); - public int activeSessionsCount (); - public void refuseNewSessions (boolean); - public boolean isRefusingNewSessions (); - public void setMinimumActiveSessionsCount (int); - public int minimumActiveSessionsCount (); - public void terminateAfterTimeInterval (double); - - // garbage collection undocumented - - int garbageCollectionPeriod (); - void setGarbageCollectionPeriod (int); - - // backwards compatibility - - public boolean requiresWOF35RequestHandling (); - public boolean requiresWOF35TemplateParser (); - - public void setPrintsHTMLParserDiagnostics (boolean); - public boolean printsHTMLParserDiagnostics (); - - // configuration and defaults - - public static NSArray loadFrameworks (); - public static void setLoadFrameworks (NSArray); -*/ - static boolean debuggingEnabled = false; - /** - * Returns whether the application is in "debug mode". - */ - public static boolean isDebuggingEnabled() - { - return debuggingEnabled; - } - - /** - * Sets whether the application is in "debug mode". - */ - public static void setDebuggingEnabled( boolean enabled ) - { - debuggingEnabled = enabled; - } - - /** - * Sets whether templates are cached. If true, templates will - * only be read once per application lifetime. Otherwise, templates - * will be read each time this class is instantiated. Defaults to false. - */ - public static void setCachingEnabled (boolean enabled) - { + /** + * Returns a class in the same package as the Application class, or, failing + * that, from the WOApplication package, or finally from the root of the class + * path. Returns null if not found. + */ + Class getLocalClass(String aName) { + Class result = null; + if (getClass() != WOApplication.class) { + result = loadLocalClass(getClass(), aName); + } + if (result == null) { + result = loadLocalClass(WOApplication.class, aName); + } + if (result == null) { + result = loadLocalClass(null, aName); + } + return result; + } + + private static final Class loadLocalClass(Class aClass, String aName) { + ClassLoader loader; + String packageName = ""; + if (aClass != null) { + loader = aClass.getClassLoader(); + packageName = aClass.getName(); + int index = packageName.lastIndexOf("."); + if (index > -1) { + packageName = packageName.substring(0, index + 1); + } else { + packageName = ""; + } + } else { + loader = WOApplication.class.getClassLoader(); + } + + try { + return loader.loadClass(packageName + aName); + } catch (ClassNotFoundException e) { + return null; + } + } + + // creating elements + + /** + * Returns either a dynamic element or a component for the specified name. + */ + public WOElement dynamicElementWithName(String anElementName, NSDictionary anAssociationMap, WOElement aBodyElement, + List aLanguageList) { + WOElement element = null; + Class c = null; + try { + c = getLocalClass(anElementName); + if (c == null) { + System.out.println("Not found: dynamicElementWithName: " + "could not find WODynamicElement subclass: " + + anElementName); + c = WODynamicElement.class; + } + + // get constructor + Class[] params = new Class[] { String.class, NSDictionary.class, WOElement.class }; + Constructor ctor = c.getConstructor(params); + + // create instance of class + if (ctor != null) { + element = (WODynamicElement) ctor + .newInstance(new Object[] { anElementName, anAssociationMap, aBodyElement }); + } + } catch (Throwable t) { + // ignore: not a dynamic element + // System.out.println( "Not a dynamic element: dynamicElementWithName: " + t ); + // exc.printStackTrace(); + } + + // no dynamic element found: look for a component + if (element == null) { + WOComponent component = (WOComponent) pageWithName(anElementName, (WOContext) null); + + // this seems hackish: + // I don't see another way of setting the bindings. + component.associations = anAssociationMap; + component.rootElement = aBodyElement; + + element = component; + } + + return element; + } + + // resource handling + + /** + * Called to create the application's resource manager. Override to create a + * custom resource manager. + */ + public WOResourceManager createResourceManager() { + return new WOResourceManager(); + } + + /** + * Returns the application's current resource manager. + */ + public WOResourceManager resourceManager() { + return resourceManager; + } + + /** + * Installs a custom resource manager into the current application. + * + * @deprecated Override createResourceManager() instead. + */ + public void setResourceManager(WOResourceManager aResourceManager) { + resourceManager = aResourceManager; + } + + /* + * // request handling undocumented + * + * public WOComponent pageWithName (String); public void savePage (WOComponent); + * public WOComponent restorePageForContextID (String); public WOContext context + * (); public WOSession session (); public WOSession createSession (); public + * WOSession restoreSession (); public void saveSession (WOSession); + * + * public WOResponse handleRequest (WORequest aRequest) { } + * + * // error handling undocumented + * + * WOResponse handleSessionCreationError (); WOResponse + * handleSessionRestorationError (); WOResponse handlePageRestorationError (); + * WOResponse handleException (Throwable); + * + * // running + * + * public NSRunLoop runLoop (); public void run (); public void setTimeOut + * (double); public double timeOut (); public void terminate (); public boolean + * isTerminating (); + * + * // script debugging + * + * public void traceScriptedMessages (boolean); public void traceAssignments + * (boolean); public void traceStatements (boolean); public void + * traceObjectiveCMessages (boolean); public void trace (boolean); public void + * logTakeValueForDeclarationNamed (String, String, String, String, Object); + * public void logSetValueForDeclarationNamed (String, String, String, String, + * Object); + * + * // script handling + * + * public String scriptedClassNameWithPath (String); public String + * scriptedClassNameWithPathEncoding (String, int); + * + * // statistics report + * + * public void setStatisticsStore (WOStatisticsStore); public WOStatisticsStore + * statisticsStore (); public NSDictionary statistics (); + * + * // managing adaptors + * + * public WOAdaptor adaptorWithName (String, NSDictionary); public NSArray + * adaptors (); + * + * // monitor support + * + * public boolean monitoringEnabled (); public int activeSessionsCount (); + * public void refuseNewSessions (boolean); public boolean isRefusingNewSessions + * (); public void setMinimumActiveSessionsCount (int); public int + * minimumActiveSessionsCount (); public void terminateAfterTimeInterval + * (double); + * + * // garbage collection undocumented + * + * int garbageCollectionPeriod (); void setGarbageCollectionPeriod (int); + * + * // backwards compatibility + * + * public boolean requiresWOF35RequestHandling (); public boolean + * requiresWOF35TemplateParser (); + * + * public void setPrintsHTMLParserDiagnostics (boolean); public boolean + * printsHTMLParserDiagnostics (); + * + * // configuration and defaults + * + * public static NSArray loadFrameworks (); public static void setLoadFrameworks + * (NSArray); + */ + static boolean debuggingEnabled = false; + + /** + * Returns whether the application is in "debug mode". + */ + public static boolean isDebuggingEnabled() { + return debuggingEnabled; + } + + /** + * Sets whether the application is in "debug mode". + */ + public static void setDebuggingEnabled(boolean enabled) { + debuggingEnabled = enabled; + } + + /** + * Sets whether templates are cached. If true, templates will only be read once + * per application lifetime. Otherwise, templates will be read each time this + * class is instantiated. Defaults to false. + */ + public static void setCachingEnabled(boolean enabled) { cachingEnabled = enabled; } - /** - * Returns whether templates are cached. If true, templates are - * read once per application lifetime. Otherwise, templates are - * read each time this class is instantiated. - */ - public static boolean isCachingEnabled () - { - return cachingEnabled; - } - - // configuration - - /** - * Returns the component request handler key, - * which is defined by the system property _ComponentRequestHandlerKey. - * The default is "wo". - */ - public static String componentRequestHandlerKey() - { - return System.getProperty( _ComponentRequestHandlerKey, "wo" ); - } - - /** - * Sets the component request handler key. - * @deprecated Set the system property _ComponentRequestHandlerKey. - */ - public static void setComponentRequestHandlerKey(String aKey) - { - System.setProperty( _ComponentRequestHandlerKey, aKey ); - } - - /** - * Returns the direct action request handler key, - * which is defined by the system property _DirectActionRequestHandlerKey. - * The default is "wa". - */ - public static String directActionRequestHandlerKey() - { - return System.getProperty( _DirectActionRequestHandlerKey, "wa" ); - } - - /** - * Sets the direct action request handler key. - * @deprecated Set the system property _DirectActionRequestHandlerKey. - */ - public static void setDirectActionRequestHandlerKey(String aKey) - { - System.setProperty( _DirectActionRequestHandlerKey, aKey ); - } - - /** - * Returns the resource request handler key, - * which is defined by the system property _ResourceRequestHandlerKey. - * The default is "wr". - */ - public static String resourceRequestHandlerKey() - { - return System.getProperty( _ResourceRequestHandlerKey, "wr" ); - } - - /** - * Sets the resource request handler key. - * @deprecated Set the system property _ResourceRequestHandlerKey. - */ - public static void setResourceRequestHandlerKey(String aKey) - { - System.setProperty( _ResourceRequestHandlerKey, aKey ); - } - - /** - * Returns whether this application should attempt to open - * a web browser on the host machine when launched standalone. - * The default is true. - */ - public static boolean autoOpenInBrowser() - { - return autoOpenInBrowser; - } - - /** - * Sets whether this application should attempt to open - * a web browser on the host machine when launched standalone. - */ - public static void setAutoOpenInBrowser(boolean autoOpen) - { - autoOpenInBrowser = autoOpen; - } - - /** - * Gets the port used when run as a standalone server. - * Returns the value of the system property WOPort. - * By default, this is zero, which causes the application - * to automatically select an available port. - */ - public static Number port () - { - return Integer.getInteger( WOPort, 0 ); - } - - /** - * Gets the smtp server that will be used to send email. - * Returns the system property WOSMTPHost. - */ - public static String SMTPHost() - { - return System.getProperty( WOSMTPHost ); - } - - /** - * Sets the smtp server that will be used to send email. - * @deprecated Set the system property WOSMTPHost. - */ - public static void setSMTPHost( String aHost ) - { - System.setProperty( WOSMTPHost, aHost ); - } -/* - public static boolean isDirectConnectEnabled (); - public static void setDirectConnectEnabled (boolean); - public static String cgiAdaptorURL (); - public static void setCGIAdaptorURL (String); - public static String applicationBaseURL (); - public static void setApplicationBaseURL (String); - public static String frameworksBaseURL (); - public static void setFrameworksBaseURL (String); - public static String recordingPath (); - public static void setRecordingPath (String); - public static NSArray projectSearchPath (); - public static void setProjectSearchPath (NSArray); - public static boolean isMonitorEnabled (); - public static void setMonitorEnabled (boolean); - public static String monitorHost (); - public static String adaptor (); - public String number (); // deprecated - public static Number listenQueueSize (); - public static void setListenQueueSize (Number); - public static NSArray additionalAdaptors (); - public static void setAdditionalAdaptors (NSArray); - public static boolean includeCommentsInResponses (); - public static void setIncludeCommentsInResponses (boolean); - public static void setSessionTimeOut (Number); - public static Number sessionTimeOut (); - public static void logString (String); - public static void debugString (String); - public static void logToMonitorString (String); -*/ + /** + * Returns whether templates are cached. If true, templates are read once per + * application lifetime. Otherwise, templates are read each time this class is + * instantiated. + */ + public static boolean isCachingEnabled() { + return cachingEnabled; + } + + // configuration + + /** + * Returns the component request handler key, which is defined by the system + * property _ComponentRequestHandlerKey. The default is "wo". + */ + public static String componentRequestHandlerKey() { + return System.getProperty(_ComponentRequestHandlerKey, "wo"); + } + + /** + * Sets the component request handler key. + * + * @deprecated Set the system property _ComponentRequestHandlerKey. + */ + public static void setComponentRequestHandlerKey(String aKey) { + System.setProperty(_ComponentRequestHandlerKey, aKey); + } + + /** + * Returns the direct action request handler key, which is defined by the system + * property _DirectActionRequestHandlerKey. The default is "wa". + */ + public static String directActionRequestHandlerKey() { + return System.getProperty(_DirectActionRequestHandlerKey, "wa"); + } + + /** + * Sets the direct action request handler key. + * + * @deprecated Set the system property _DirectActionRequestHandlerKey. + */ + public static void setDirectActionRequestHandlerKey(String aKey) { + System.setProperty(_DirectActionRequestHandlerKey, aKey); + } + + /** + * Returns the resource request handler key, which is defined by the system + * property _ResourceRequestHandlerKey. The default is "wr". + */ + public static String resourceRequestHandlerKey() { + return System.getProperty(_ResourceRequestHandlerKey, "wr"); + } - /** - * Main entry point for applications that do not subclass WOApplication. - */ - public static void main( String[] argv ) - { - main( argv, WOApplication.class ); - } - - /** - * Subclasses may call this method to start a self-hosted - * web server to serve themselves directly (for testing). - */ - public static void main( String[] argv, Class subclass ) - { - try - { - int port = 0; - boolean open = false; - try - { - port = ((Number)subclass.getMethod( "port", - new Class[0]).invoke(subclass,new Object[0])).intValue(); - open = ((Boolean)subclass.getMethod( "autoOpenInBrowser", - new Class[0]).invoke(subclass,new Object[0])).booleanValue(); - } - catch ( Throwable t ) - { - System.err.print("Error reading configuration:" ); - t.printStackTrace(); - } - - HttpServer server = new HttpServer(); - HttpListener listener = server.addListener(new InetAddrPort(port)); - org.mortbay.http.HttpContext context = server.getContext("/"); - ServletHandler handler = new ServletHandler(); - handler.addServlet("/",subclass.getName()); - context.addHandler(handler); - server.start(); - port = listener.getPort(); - System.out.println("Waiting for requests: http://127.0.0.1:" + port); - if ( open ) - { - BrowserLauncher.openURL( "http://127.0.0.1:" + port ); - } - } - catch ( Throwable t ) - { - t.printStackTrace(); - } - } + /** + * Sets the resource request handler key. + * + * @deprecated Set the system property _ResourceRequestHandlerKey. + */ + public static void setResourceRequestHandlerKey(String aKey) { + System.setProperty(_ResourceRequestHandlerKey, aKey); + } + + /** + * Returns whether this application should attempt to open a web browser on the + * host machine when launched standalone. The default is true. + */ + public static boolean autoOpenInBrowser() { + return autoOpenInBrowser; + } + + /** + * Sets whether this application should attempt to open a web browser on the + * host machine when launched standalone. + */ + public static void setAutoOpenInBrowser(boolean autoOpen) { + autoOpenInBrowser = autoOpen; + } + + /** + * Gets the port used when run as a standalone server. Returns the value of the + * system property WOPort. By default, this is zero, which causes the + * application to automatically select an available port. + */ + public static Number port() { + return Integer.getInteger(WOPort, 0); + } + + /** + * Gets the smtp server that will be used to send email. Returns the system + * property WOSMTPHost. + */ + public static String SMTPHost() { + return System.getProperty(WOSMTPHost); + } + + /** + * Sets the smtp server that will be used to send email. + * + * @deprecated Set the system property WOSMTPHost. + */ + public static void setSMTPHost(String aHost) { + System.setProperty(WOSMTPHost, aHost); + } + /* + * public static boolean isDirectConnectEnabled (); public static void + * setDirectConnectEnabled (boolean); public static String cgiAdaptorURL (); + * public static void setCGIAdaptorURL (String); public static String + * applicationBaseURL (); public static void setApplicationBaseURL (String); + * public static String frameworksBaseURL (); public static void + * setFrameworksBaseURL (String); public static String recordingPath (); public + * static void setRecordingPath (String); public static NSArray + * projectSearchPath (); public static void setProjectSearchPath (NSArray); + * public static boolean isMonitorEnabled (); public static void + * setMonitorEnabled (boolean); public static String monitorHost (); public + * static String adaptor (); public String number (); // deprecated public + * static Number listenQueueSize (); public static void setListenQueueSize + * (Number); public static NSArray additionalAdaptors (); public static void + * setAdditionalAdaptors (NSArray); public static boolean + * includeCommentsInResponses (); public static void + * setIncludeCommentsInResponses (boolean); public static void setSessionTimeOut + * (Number); public static Number sessionTimeOut (); public static void + * logString (String); public static void debugString (String); public static + * void logToMonitorString (String); + */ + + /** + * Main entry point for applications that do not subclass WOApplication. + */ + public static void main(String[] argv) { + main(argv, WOApplication.class); + } + + /** + * Subclasses may call this method to start a self-hosted web server to serve + * themselves directly (for testing). + */ + public static void main(String[] argv, Class subclass) { + try { + int port = 0; + boolean open = false; + try { + port = ((Number) subclass.getMethod("port", new Class[0]).invoke(subclass, new Object[0])).intValue(); + open = ((Boolean) subclass.getMethod("autoOpenInBrowser", new Class[0]).invoke(subclass, new Object[0])) + .booleanValue(); + } catch (Throwable t) { + System.err.print("Error reading configuration:"); + t.printStackTrace(); + } + + HttpServer server = new HttpServer(); + HttpListener listener = server.addListener(new InetAddrPort(port)); + org.mortbay.http.HttpContext context = server.getContext("/"); + ServletHandler handler = new ServletHandler(); + handler.addServlet("/", subclass.getName()); + context.addHandler(handler); + server.start(); + port = listener.getPort(); + System.out.println("Waiting for requests: http://127.0.0.1:" + port); + if (open) { + BrowserLauncher.openURL("http://127.0.0.1:" + port); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.30 2003/03/28 18:01:19 mpowers - * Now defaulting port to zero. + * Revision 1.30 2003/03/28 18:01:19 mpowers Now defaulting port to zero. * - * Revision 1.29 2003/03/28 17:31:58 mpowers - * Implemented support for autoselection of free port. (thanks gmuth!) + * Revision 1.29 2003/03/28 17:31:58 mpowers Implemented support for + * autoselection of free port. (thanks gmuth!) * - * Revision 1.28 2003/03/28 17:26:17 mpowers - * Implemented package support: Applications can now live in packages. - * Better support for locating package local classes. + * Revision 1.28 2003/03/28 17:26:17 mpowers Implemented package support: + * Applications can now live in packages. Better support for locating package + * local classes. * - * Revision 1.27 2003/02/21 16:40:22 mpowers - * Now reading port and smtp host from system properties. - * Implemented WOApplication.main. + * Revision 1.27 2003/02/21 16:40:22 mpowers Now reading port and smtp host from + * system properties. Implemented WOApplication.main. * - * Revision 1.26 2003/02/14 22:33:18 mpowers - * Better handling for standalone mode. + * Revision 1.26 2003/02/14 22:33:18 mpowers Better handling for standalone + * mode. * - * Revision 1.25 2003/02/14 15:18:27 mpowers - * Now launching standalone app as a servlet, not a webapp. - * Disabled jetty's event logging. + * Revision 1.25 2003/02/14 15:18:27 mpowers Now launching standalone app as a + * servlet, not a webapp. Disabled jetty's event logging. * - * Revision 1.24 2003/02/13 22:41:04 mpowers - * WOApplications can now be self-serving. Added configuration params too. + * Revision 1.24 2003/02/13 22:41:04 mpowers WOApplications can now be + * self-serving. Added configuration params too. * - * Revision 1.23 2003/01/28 19:33:51 mpowers - * Implemented the rest of WOResourceManager. - * Implemented support for java-style i18n. - * Components now use the resource manager to load templates. + * Revision 1.23 2003/01/28 19:33:51 mpowers Implemented the rest of + * WOResourceManager. Implemented support for java-style i18n. Components now + * use the resource manager to load templates. * - * Revision 1.22 2003/01/27 15:08:00 mpowers - * Implemented WOResourceManager, using java resources for now. + * Revision 1.22 2003/01/27 15:08:00 mpowers Implemented WOResourceManager, + * using java resources for now. * - * Revision 1.21 2003/01/24 20:13:22 mpowers - * Now accepting immutable NSDictionary in constructor, not Map. + * Revision 1.21 2003/01/24 20:13:22 mpowers Now accepting immutable + * NSDictionary in constructor, not Map. * - * Revision 1.20 2003/01/20 17:50:11 mpowers - * Caught a loop condition when same declaration was used twice. + * Revision 1.20 2003/01/20 17:50:11 mpowers Caught a loop condition when same + * declaration was used twice. * - * Revision 1.19 2003/01/19 22:33:25 mpowers - * Fixed problems with classpath and dynamic class loading. - * Dynamic elements now pass on ensureAwakeInContext. + * Revision 1.19 2003/01/19 22:33:25 mpowers Fixed problems with classpath and + * dynamic class loading. Dynamic elements now pass on ensureAwakeInContext. * Parser how handles tags. * - * Revision 1.18 2003/01/18 23:54:50 mpowers - * Implemented debugging enabled. + * Revision 1.18 2003/01/18 23:54:50 mpowers Implemented debugging enabled. * - * Revision 1.17 2003/01/17 20:58:18 mpowers - * Fixed up WOHyperlink. + * Revision 1.17 2003/01/17 20:58:18 mpowers Fixed up WOHyperlink. * - * Revision 1.16 2003/01/17 20:34:17 mpowers - * Rudimentary support for resource requests. + * Revision 1.16 2003/01/17 20:34:17 mpowers Rudimentary support for resource + * requests. * - * Revision 1.15 2003/01/17 15:31:56 mpowers - * Removed spurious error message. + * Revision 1.15 2003/01/17 15:31:56 mpowers Removed spurious error message. * - * Revision 1.14 2003/01/17 14:39:00 mpowers - * Now calling preferred constructor: WOComponent(WOContext) + * Revision 1.14 2003/01/17 14:39:00 mpowers Now calling preferred constructor: + * WOComponent(WOContext) * - * Revision 1.13 2003/01/16 20:10:46 mpowers - * - components now synchronize bindings - * - support for WOComponentContent - * - implemented performParentAction + * Revision 1.13 2003/01/16 20:10:46 mpowers - components now synchronize + * bindings - support for WOComponentContent - implemented performParentAction * - * Revision 1.12 2003/01/16 15:50:43 mpowers - * More robust declaration parsing. - * Subcomponents are now supported. - * dynamicElementWithName can now return subcomponents. + * Revision 1.12 2003/01/16 15:50:43 mpowers More robust declaration parsing. + * Subcomponents are now supported. dynamicElementWithName can now return + * subcomponents. * - * Revision 1.11 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.11 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.10 2003/01/13 22:24:18 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.10 2003/01/13 22:24:18 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.9 2003/01/10 20:17:41 mpowers - * Component action urls are now working. + * Revision 1.9 2003/01/10 20:17:41 mpowers Component action urls are now + * working. * - * Revision 1.8 2003/01/10 19:16:40 mpowers - * Implemented support for page caching. + * Revision 1.8 2003/01/10 19:16:40 mpowers Implemented support for page + * caching. * - * Revision 1.4 2002/12/19 17:58:52 mpowers - * Rewrote the template parsing - no longer confused about "root element". + * Revision 1.4 2002/12/19 17:58:52 mpowers Rewrote the template parsing - no + * longer confused about "root element". * - * Revision 1.3 2002/12/18 14:12:38 mpowers - * Support for differentiated request handlers. - * Support url generation for WOContext and WORequest. + * Revision 1.3 2002/12/18 14:12:38 mpowers Support for differentiated request + * handlers. Support url generation for WOContext and WORequest. * - * Revision 1.2 2002/12/17 14:57:41 mpowers - * Minor corrections to WORequests's parsing, and updated javadocs. + * Revision 1.2 2002/12/17 14:57:41 mpowers Minor corrections to WORequests's + * parsing, and updated javadocs. * - * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java index 608f9fa..89623a6 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java @@ -19,150 +19,138 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web; /** -* A pure java implementation of WOAssociation.

-* -* A WOAssociation represents the mapping of a property on a -* WOComponent to a property on a WOAssociation. For example:

-* -* MyAssociation: WOString { value = currentCustomer.location.city };

-* -* This example represents a WOAssociation between the value field -* on a WOString element and the city property of the location property -* of the currentCustomer property of a WOComponent.

-* -* To resolve values, a property accessor method will be used in -* preference to a public field, if both exist. Any null value -* in the path will produce null.

-* -* A mapping represented in quotation marks: { value = "This is a test." } -* is considered a constant value. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public class WOAssociation implements java.io.Serializable -{ + * A pure java implementation of WOAssociation.
+ *
+ * + * A WOAssociation represents the mapping of a property on a WOComponent to a + * property on a WOAssociation. For example:
+ *
+ * + * MyAssociation: WOString { value = currentCustomer.location.city };
+ *
+ * + * This example represents a WOAssociation between the value field on a WOString + * element and the city property of the location property of the currentCustomer + * property of a WOComponent.
+ *
+ * + * To resolve values, a property accessor method will be used in preference to a + * public field, if both exist. Any null value in the path will produce null. + *
+ *
+ * + * A mapping represented in quotation marks: { value = "This is a test." } is + * considered a constant value. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public class WOAssociation implements java.io.Serializable { protected Object value; protected String path; - + + /** + * The default constructor. The static factory methods should be used to create + * instances of WOAssociation. + */ + protected WOAssociation() { + value = null; + path = null; + } + /** - * The default constructor. The static factory methods should - * be used to create instances of WOAssociation. - */ - protected WOAssociation () - { - value = null; - path = null; - } + * Creates a WOAssociation that maps to a constant value. + */ + public static WOAssociation associationWithValue(Object anObject) { + WOAssociation result = new WOAssociation(); + result.value = anObject; + return result; + } /** - * Creates a WOAssociation that maps to a constant value. - */ - public static WOAssociation associationWithValue (Object anObject) - { - WOAssociation result = new WOAssociation(); - result.value = anObject; - return result; - } - - /** - * Creates a WOAssociation that maps to the specified key path. - * If the path is null, the association will map to null. - * Throws an exception if the property cannot be resolved. - */ - public static WOAssociation associationWithKeyPath (String aString) - { - WOAssociation result = new WOAssociation(); - result.path = aString; - return result; - } + * Creates a WOAssociation that maps to the specified key path. If the path is + * null, the association will map to null. Throws an exception if the property + * cannot be resolved. + */ + public static WOAssociation associationWithKeyPath(String aString) { + WOAssociation result = new WOAssociation(); + result.path = aString; + return result; + } /** - * Returns the value for this association's key path in the - * specified component, or null if any value in the path is - * null or if the key path is null. - */ - public Object valueInComponent (WOComponent aComponent) - { - if ( aComponent == null ) return null; - if ( value != null ) return value; - if ( path != null ) return aComponent.valueForKey( path ); - throw new RuntimeException( - "WOAssociation: neither value nor path specified!" ); - } - - /** - * Sets the property in the specified component to the specified value. - * Throws an exception if the property cannot be resolved. - */ - public void setValue (Object aValue, WOComponent aComponent) - { - if ( path != null ) - { - aComponent.takeValueForKey( aValue, path ); + * Returns the value for this association's key path in the specified component, + * or null if any value in the path is null or if the key path is null. + */ + public Object valueInComponent(WOComponent aComponent) { + if (aComponent == null) + return null; + if (value != null) + return value; + if (path != null) + return aComponent.valueForKey(path); + throw new RuntimeException("WOAssociation: neither value nor path specified!"); + } + + /** + * Sets the property in the specified component to the specified value. Throws + * an exception if the property cannot be resolved. + */ + public void setValue(Object aValue, WOComponent aComponent) { + if (path != null) { + aComponent.takeValueForKey(aValue, path); return; } - throw new RuntimeException( - "WOAssociation: tried to set value but no path was specified!" ); + throw new RuntimeException("WOAssociation: tried to set value but no path was specified!"); + } + + /** + * Returns true if this association is writable; that is, returns true if this + * association is not constant. + */ + public boolean isValueSettable() { + return (path != null); } /** - * Returns true if this association is writable; that is, - * returns true if this association is not constant. - */ - public boolean isValueSettable () - { - return ( path != null ); - } - - /** - * Returns true if this association is constant - * and therefore read-only. - */ - public boolean isValueConstant () - { - return ( path == null ); - } - - /** - * For debugging purposes. - */ - public String toString() - { - if ( path != null ) - { - return "[WOAssociation:" + path + "]"; - } - return "[WOAssociation:\"" + value + "\"]"; - } -} + * Returns true if this association is constant and therefore read-only. + */ + public boolean isValueConstant() { + return (path == null); + } + + /** + * For debugging purposes. + */ + public String toString() { + if (path != null) { + return "[WOAssociation:" + path + "]"; + } + return "[WOAssociation:\"" + value + "\"]"; + } +} /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/01/24 20:13:22 mpowers - * Now accepting immutable NSDictionary in constructor, not Map. + * Revision 1.6 2003/01/24 20:13:22 mpowers Now accepting immutable NSDictionary + * in constructor, not Map. * - * Revision 1.5 2003/01/17 22:55:08 mpowers - * Straighted out the parent binding issue (I think). - * Fixes for woextensions compatibility. + * Revision 1.5 2003/01/17 22:55:08 mpowers Straighted out the parent binding + * issue (I think). Fixes for woextensions compatibility. * - * Revision 1.3 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.3 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.2 2003/01/14 15:51:48 mpowers - * Removed value() method from WOAssociaton. + * Revision 1.2 2003/01/14 15:51:48 mpowers Removed value() method from + * WOAssociaton. * - * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java index 9f0707d..131d223 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java @@ -23,99 +23,91 @@ import net.wotonomy.foundation.NSData; import net.wotonomy.foundation.NSDictionary; /** -* WOBody represents a page's "body" tag and is used to dynamically -* set the background image for a page, and therefore works much like WOImage. -* -* Bindings are: -*
    -*
  • src: A static URL for the image source.
  • -*
  • data: A NSData object with the image content. Must be used with mimeType.
  • -*
  • mimeType: The MIME type for the image data. Can be used with filename or data bindings.
  • -*
  • filename: The path to a file containing an image.
  • -*
  • framework: The optional framework from whence the image should be retrieved (used in conjunction with filename).
  • -*
  • key: A key under which this resource will be cached for repeated access.
  • -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ + * WOBody represents a page's "body" tag and is used to dynamically set the + * background image for a page, and therefore works much like WOImage. + * + * Bindings are: + *
      + *
    • src: A static URL for the image source.
    • + *
    • data: A NSData object with the image content. Must be used with + * mimeType.
    • + *
    • mimeType: The MIME type for the image data. Can be used with filename or + * data bindings.
    • + *
    • filename: The path to a file containing an image.
    • + *
    • framework: The optional framework from whence the image should be + * retrieved (used in conjunction with filename).
    • + *
    • key: A key under which this resource will be cached for repeated + * access.
    • + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOBody extends WOImage { - protected String src; - protected String filename; - protected String framework; - protected NSData data; - protected String mimeType; + protected String src; + protected String filename; + protected String framework; + protected NSData data; + protected String mimeType; - protected WOBody() { - super(); - } + protected WOBody() { + super(); + } - public WOBody(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } + public WOBody(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } - void ensureAwakeInContext (WOContext aContext) - { - if ( rootElement != null ) - { - rootElement.ensureAwakeInContext( aContext ); - } - } + void ensureAwakeInContext(WOContext aContext) { + if (rootElement != null) { + rootElement.ensureAwakeInContext(aContext); + } + } - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - if ( rootElement != null ) - { - rootElement.takeValuesFromRequest( aRequest, aContext ); - } - } - - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - if ( rootElement != null ) - { - return rootElement.invokeAction( aRequest, aContext ); - } - return null; - } - - public void appendToResponse(WOResponse r, WOContext c) - { - r.appendContentString(""); - if ( rootElement != null ) - { - rootElement.appendToResponse( r, c ); - } - r.appendContentString(""); - } + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + if (rootElement != null) { + rootElement.takeValuesFromRequest(aRequest, aContext); + } + } + + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + if (rootElement != null) { + return rootElement.invokeAction(aRequest, aContext); + } + return null; + } + + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(""); + if (rootElement != null) { + rootElement.appendToResponse(r, c); + } + r.appendContentString(""); + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.4 2003/08/07 00:15:14 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.4 2003/08/07 00:15:14 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.3 2003/01/27 15:57:28 mpowers - * Now participates in all parts of request/response cycle. + * Revision 1.3 2003/01/27 15:57:28 mpowers Now participates in all parts of + * request/response cycle. * - * Revision 1.2 2003/01/27 15:08:23 mpowers - * Now appending to response. + * Revision 1.2 2003/01/27 15:08:23 mpowers Now appending to response. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java index 5d22d36..8e706c1 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java @@ -7,75 +7,75 @@ import net.wotonomy.foundation.NSMutableArray; public class WOCheckBox extends WOInput { - protected boolean checked = false; + protected boolean checked = false; - public WOCheckBox() { - super(); - } + public WOCheckBox() { + super(); + } - public WOCheckBox(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WOCheckBox(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "CHECKBOX"; - } + protected String inputType() { + return "CHECKBOX"; + } - protected Object value(WOContext c) { - Object val = null; - boolean checked = false; - if (associations.objectForKey("value") != null) { - val = valueForProperty("value", c.component()); - Object sel = valueForProperty("selection", c.component()); - if (sel != null && val != null && sel.equals(val)) - checked = true; - } - if (val == null) { - val = c.elementID(); - } - return val; - } + protected Object value(WOContext c) { + Object val = null; + boolean checked = false; + if (associations.objectForKey("value") != null) { + val = valueForProperty("value", c.component()); + Object sel = valueForProperty("selection", c.component()); + if (sel != null && val != null && sel.equals(val)) + checked = true; + } + if (val == null) { + val = c.elementID(); + } + return val; + } - protected void appendExtras(WOResponse r, WOContext c) { - checked |= booleanForProperty("checked", c.component()); - if (checked) - r.appendContentString(" CHECKED"); - } + protected void appendExtras(WOResponse r, WOContext c) { + checked |= booleanForProperty("checked", c.component()); + if (checked) + r.appendContentString(" CHECKED"); + } - protected NSMutableArray additionalAttributes() { - NSMutableArray a = super.additionalAttributes(); - a.addObject("checked"); - a.addObject("selection"); - return a; - } + protected NSMutableArray additionalAttributes() { + NSMutableArray a = super.additionalAttributes(); + a.addObject("checked"); + a.addObject("selection"); + return a; + } - public void appendToResponse(WOResponse r, WOContext c) { - checked = false; - super.appendToResponse(r, c); - } + public void appendToResponse(WOResponse r, WOContext c) { + checked = false; + super.appendToResponse(r, c); + } - public void takeValuesFromRequest(WORequest r, WOContext c) { - if (disabled(c)) - return; - NSArray values = r.formValuesForKey(inputName(c)); - Object val = valueForProperty("value", c.component()); - if (val == null) - val = c.elementID(); - java.util.Enumeration enumerator = values.objectEnumerator(); - checked = false; - while (enumerator.hasMoreElements()) { - Object nextval = enumerator.nextElement(); - if (nextval.equals(val)) - checked = true; - } - if (associations.objectForKey("value") != null && associations.objectForKey("selection") != null) { - if (checked) - setValueForProperty("selection", val, c.component()); - else if (valueForProperty("selection", c.component()) != null) - setValueForProperty("selection", null, c.component()); - } - if (associations.objectForKey("checked") != null) - setValueForProperty("checked", checked ? Boolean.TRUE : Boolean.FALSE, c.component()); - } + public void takeValuesFromRequest(WORequest r, WOContext c) { + if (disabled(c)) + return; + NSArray values = r.formValuesForKey(inputName(c)); + Object val = valueForProperty("value", c.component()); + if (val == null) + val = c.elementID(); + java.util.Enumeration enumerator = values.objectEnumerator(); + checked = false; + while (enumerator.hasMoreElements()) { + Object nextval = enumerator.nextElement(); + if (nextval.equals(val)) + checked = true; + } + if (associations.objectForKey("value") != null && associations.objectForKey("selection") != null) { + if (checked) + setValueForProperty("selection", val, c.component()); + else if (valueForProperty("selection", c.component()) != null) + setValueForProperty("selection", null, c.component()); + } + if (associations.objectForKey("checked") != null) + setValueForProperty("checked", checked ? Boolean.TRUE : Boolean.FALSE, c.component()); + } } \ No newline at end of file diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java index 20d8b0a..c99768c 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java @@ -42,1271 +42,1037 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSSelector; /** -* Pure java implementation of WOComponent. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOComponent - extends WOElement - implements WOActionResults, - net.wotonomy.control.EOKeyValueCodingAdditions, - net.wotonomy.control.EOKeyValueCoding -{ + * Pure java implementation of WOComponent. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOComponent extends WOElement implements WOActionResults, net.wotonomy.control.EOKeyValueCodingAdditions, + net.wotonomy.control.EOKeyValueCoding { WOElement rootElement; - - private static final String DIRECTORY_SUFFIX = ".wo"; - private static final String TEMPLATE_SUFFIX = ".html"; - private static final String DECLARATION_SUFFIX = ".wod"; - - private static final String OPEN_TAG = "webobject"; - private static final String CLOSE_TAG = "/webobject"; - private static final String NAME_KEY = "name"; - - protected transient WOContext context; // don't persist - protected boolean cachingEnabled; - protected WOElement template; - protected WOComponent parent; + + private static final String DIRECTORY_SUFFIX = ".wo"; + private static final String TEMPLATE_SUFFIX = ".html"; + private static final String DECLARATION_SUFFIX = ".wod"; + + private static final String OPEN_TAG = "webobject"; + private static final String CLOSE_TAG = "/webobject"; + private static final String NAME_KEY = "name"; + + protected transient WOContext context; // don't persist + protected boolean cachingEnabled; + protected WOElement template; + protected WOComponent parent; + + /** + * Default constructor. Deprecated in latest spec. + */ + public WOComponent() { + parent = null; + cachingEnabled = true; + template = null; + } + + /** + * Constructor specifying a context. + */ + public WOComponent(WOContext aContext) { + this(); + context = aContext; + } + + /** + * Returns the name of the component, which is usually just the class name. + */ + public String name() { + return justTheClassName(); + } /** - * Default constructor. Deprecated in latest spec. - */ - public WOComponent () - { - parent = null; - cachingEnabled = true; - template = null; - } - - /** - * Constructor specifying a context. - */ - public WOComponent( WOContext aContext ) - { - this(); - context = aContext; - } - + * Returns the system-dependent file path to the current component directory, + * including the ".wo" extension. + */ + public String path() { + throw new RuntimeException("Not implemented yet."); + } + /** - * Returns the name of the component, which is usually just the class name. - */ - public String name () - { - return justTheClassName(); - } - - /** - * Returns the system-dependent file path to the current component - * directory, including the ".wo" extension. - */ - public String path () - { - throw new RuntimeException( "Not implemented yet." ); - } + * Returns the URL for this component, relative to the server's document root on + * the server's file system. This is not an http url. + */ + public String baseURL() { + throw new RuntimeException("Not implemented yet."); + } /** - * Returns the URL for this component, relative to the server's - * document root on the server's file system. - * This is not an http url. - */ - public String baseURL () - { - throw new RuntimeException( "Not implemented yet." ); - } + * Returns the name of the framework that contains this component, or null if + * the component does not belong to a framework. This currently returns the + * package path of the class, or null if it does not belong to a package. + */ + public String frameworkName() { + return justTheResourcePath(); + } /** - * Returns the name of the framework that contains this component, - * or null if the component does not belong to a framework. - * This currently returns the package path of the class, or - * null if it does not belong to a package. - */ - public String frameworkName () - { - return justTheResourcePath(); - } - - /** - * Sets whether templates are cached. If true, templates will - * only be read once per application lifetime. Otherwise, templates - * will be read each time this class is instantiated. Defaults to false. - */ - public void setCachingEnabled (boolean enabled) - { + * Sets whether templates are cached. If true, templates will only be read once + * per application lifetime. Otherwise, templates will be read each time this + * class is instantiated. Defaults to false. + */ + public void setCachingEnabled(boolean enabled) { cachingEnabled = enabled; } - /** - * Returns whether templates are cached. If true, templates are - * read once per application lifetime. Otherwise, templates are - * read each time this class is instantiated. - */ - public boolean isCachingEnabled () - { - return cachingEnabled && WOApplication.application().isCachingEnabled(); - } - - /** - * Returns the root of the tree of elements produced by parsing - * the templates in the component directory for this component. - */ - public WOElement template() - { - return template; - } - - /** - * Returns the root of the tree of elements produced by parsing - * the templates in the component directory for the named component. - * @deprecated Use template() instead. - */ - public WOElement templateWithName(String aComponentName) - { - return templateWithName( aComponentName, null ); - } - - /** - * Returns the root of the tree of elements produced by parsing - * the templates in the component directory for the named component. - */ - WOElement templateWithName(String aComponentName, String aFramework) - { - NSArray languages = null; - WOContext context = context(); - if ( context != null ) - { - languages = context.request().browserLanguages(); - } - WOElement result = templateWithHTMLString( - readTemplateResource( aComponentName, aFramework, TEMPLATE_SUFFIX, languages ), - readTemplateResource( aComponentName, aFramework, DECLARATION_SUFFIX, languages ), - languages ); - if ( result == null ) - { - System.out.println( "WOComponent.templateWithName: failed for " + aComponentName ); - } - return result; - } - - /** - * Returns the root of the tree of elements produced by parsing - * the specfified HTML string and bindings declaration string. - * Note: language list is currently ignored. - */ - public static WOElement templateWithHTMLString ( - String anHTMLString, String aDeclaration, List aLanguageList) - { - if ( anHTMLString == null ) return null; - WOElement result = null; - try - { - NSDictionary bindings = processDeclaration( aDeclaration ); - List elements = new LinkedList(); - int index = processTemplate( elements, anHTMLString, 0, bindings, aLanguageList ); - if ( index == -1 ) - { - if ( elements.size() == 1 ) - { - result = (WOElement) elements.get(0); - } - else - { - result = new WOParentElement( elements ); - } - } - else // entire template did not process - { - throw new RuntimeException( "No closing tag: " + anHTMLString.substring( index ) ); - } - } - catch ( Exception exc ) - { - exc.printStackTrace(); - } + /** + * Returns whether templates are cached. If true, templates are read once per + * application lifetime. Otherwise, templates are read each time this class is + * instantiated. + */ + public boolean isCachingEnabled() { + return cachingEnabled && WOApplication.application().isCachingEnabled(); + } + + /** + * Returns the root of the tree of elements produced by parsing the templates in + * the component directory for this component. + */ + public WOElement template() { + return template; + } + + /** + * Returns the root of the tree of elements produced by parsing the templates in + * the component directory for the named component. + * + * @deprecated Use template() instead. + */ + public WOElement templateWithName(String aComponentName) { + return templateWithName(aComponentName, null); + } + + /** + * Returns the root of the tree of elements produced by parsing the templates in + * the component directory for the named component. + */ + WOElement templateWithName(String aComponentName, String aFramework) { + NSArray languages = null; + WOContext context = context(); + if (context != null) { + languages = context.request().browserLanguages(); + } + WOElement result = templateWithHTMLString( + readTemplateResource(aComponentName, aFramework, TEMPLATE_SUFFIX, languages), + readTemplateResource(aComponentName, aFramework, DECLARATION_SUFFIX, languages), languages); + if (result == null) { + System.out.println("WOComponent.templateWithName: failed for " + aComponentName); + } return result; - } - - /** - * Called at the beginning of a request-response cycle. - * Override to perform any necessary initialization. - * This implementation does nothing. - */ - public void awake () - { - } - - /** - * Package access only. Called to initialize the component with - * the proper context before the start of the request-response cycle. - * If the context has a current component, that component becomes - * this component's parent. - */ - void ensureAwakeInContext (WOContext aContext) - { + } + + /** + * Returns the root of the tree of elements produced by parsing the specfified + * HTML string and bindings declaration string. Note: language list is currently + * ignored. + */ + public static WOElement templateWithHTMLString(String anHTMLString, String aDeclaration, List aLanguageList) { + if (anHTMLString == null) + return null; + WOElement result = null; + try { + NSDictionary bindings = processDeclaration(aDeclaration); + List elements = new LinkedList(); + int index = processTemplate(elements, anHTMLString, 0, bindings, aLanguageList); + if (index == -1) { + if (elements.size() == 1) { + result = (WOElement) elements.get(0); + } else { + result = new WOParentElement(elements); + } + } else // entire template did not process + { + throw new RuntimeException("No closing tag: " + anHTMLString.substring(index)); + } + } catch (Exception exc) { + exc.printStackTrace(); + } + return result; + } + + /** + * Called at the beginning of a request-response cycle. Override to perform any + * necessary initialization. This implementation does nothing. + */ + public void awake() { + } + + /** + * Package access only. Called to initialize the component with the proper + * context before the start of the request-response cycle. If the context has a + * current component, that component becomes this component's parent. + */ + void ensureAwakeInContext(WOContext aContext) { context = aContext; parent = aContext.parent(); - if ( template == null ) - { - template = templateWithName( name(), frameworkName() ); - } - if ( template != null ) - { - template.ensureAwakeInContext( aContext ); - } - awake(); - } - - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - if ( synchronizesVariablesWithBindings() ) - { - pullValuesFromParent(); - if ( template != null ) - { - template.takeValuesFromRequest( aRequest, aContext ); - } - pushValuesToParent(); - } - else - if ( template != null ) - { - template.takeValuesFromRequest( aRequest, aContext ); - } - } - - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - WOActionResults result = null; - if ( synchronizesVariablesWithBindings() ) - { - pullValuesFromParent(); - if ( template != null ) - { - result = template.invokeAction( aRequest, aContext ); - } - pushValuesToParent(); - } - else - if ( template != null ) - { - result = template.invokeAction( aRequest, aContext ); - } + if (template == null) { + template = templateWithName(name(), frameworkName()); + } + if (template != null) { + template.ensureAwakeInContext(aContext); + } + awake(); + } + + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + if (synchronizesVariablesWithBindings()) { + pullValuesFromParent(); + if (template != null) { + template.takeValuesFromRequest(aRequest, aContext); + } + pushValuesToParent(); + } else if (template != null) { + template.takeValuesFromRequest(aRequest, aContext); + } + } + + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + WOActionResults result = null; + if (synchronizesVariablesWithBindings()) { + pullValuesFromParent(); + if (template != null) { + result = template.invokeAction(aRequest, aContext); + } + pushValuesToParent(); + } else if (template != null) { + result = template.invokeAction(aRequest, aContext); + } return result; - } - - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - if ( synchronizesVariablesWithBindings() ) - { - pullValuesFromParent(); - if ( template != null ) - { - template.appendToResponse( aResponse, aContext ); - } - pushValuesToParent(); - } - else - if ( template != null ) - { - template.appendToResponse( aResponse, aContext ); - } - context = null; - } - - /** - * Called at the end of a request-response cycle. - * Override to perform any necessary clean-up. - * This implementation does nothing. - */ - public void sleep () - { - } - - /** - * Generates a WOResponse and calls appendToResponse() on it. - */ - public WOResponse generateResponse () - { - WOResponse response = new WOResponse(); - WOContext context = context(); - appendToResponse( response, context ); // nulls out context - context.session().savePage( this ); //?is this the right place for this? - return response; - } + } + + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + if (synchronizesVariablesWithBindings()) { + pullValuesFromParent(); + if (template != null) { + template.appendToResponse(aResponse, aContext); + } + pushValuesToParent(); + } else if (template != null) { + template.appendToResponse(aResponse, aContext); + } + context = null; + } + + /** + * Called at the end of a request-response cycle. Override to perform any + * necessary clean-up. This implementation does nothing. + */ + public void sleep() { + } /** - * Returns this component's parent component, or null if none. - */ - public WOComponent parent() - { - return parent; - } - - /** - * Invokes the specified action on this component's parent. - * Variables will be synchronized when this method returns. - */ - public WOActionResults performParentAction(String anAction) - { - WOActionResults result = parent().performAction( anAction ); - if ( synchronizesVariablesWithBindings() ) - { - pullValuesFromParent(); - } - return result; - } - - /** - * Invokes the specified action on this component. - */ - WOActionResults performAction( String anAction ) - { - try - { - return (WOActionResults) NSSelector.invoke( anAction, this ); + * Generates a WOResponse and calls appendToResponse() on it. + */ + public WOResponse generateResponse() { + WOResponse response = new WOResponse(); + WOContext context = context(); + appendToResponse(response, context); // nulls out context + context.session().savePage(this); // ?is this the right place for this? + return response; + } + + /** + * Returns this component's parent component, or null if none. + */ + public WOComponent parent() { + return parent; + } + + /** + * Invokes the specified action on this component's parent. Variables will be + * synchronized when this method returns. + */ + public WOActionResults performParentAction(String anAction) { + WOActionResults result = parent().performAction(anAction); + if (synchronizesVariablesWithBindings()) { + pullValuesFromParent(); } - catch ( NoSuchMethodException exc ) - { + return result; + } + + /** + * Invokes the specified action on this component. + */ + WOActionResults performAction(String anAction) { + try { + return (WOActionResults) NSSelector.invoke(anAction, this); + } catch (NoSuchMethodException exc) { // returns below - } - catch ( InvocationTargetException exc ) - { + } catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); - exc.printStackTrace(); - throw new RuntimeException( t.toString() ); + exc.printStackTrace(); + throw new RuntimeException(t.toString()); + } catch (Exception exc) { + exc.printStackTrace(); + throw new RuntimeException(exc.toString()); } - catch ( Exception exc ) - { - exc.printStackTrace(); - throw new RuntimeException( exc.toString() ); + return null; + } + + /** + * Called before each phase of the request-response cycle, if + * synchronizesVariablesWithBindings is true and the component is not stateless. + */ + public void pullValuesFromParent() { + if (associations == null) + return; + String key; + Enumeration e = associations.keyEnumerator(); + while (e.hasMoreElements()) { + key = e.nextElement().toString(); + takeValueForKey(valueForBinding(key), key); + } + } + + /** + * Called after each phase of the request-response cycle, if + * synchronizesVariablesWithBindings is true and the component is not stateless. + */ + public void pushValuesToParent() { + if (associations == null) + return; + String key; + Enumeration e = associations.keyEnumerator(); + while (e.hasMoreElements()) { + key = e.nextElement().toString(); + setValueForBinding(valueForKey(key), key); } - return null; - } - - /** - * Called before each phase of the request-response cycle, - * if synchronizesVariablesWithBindings is true and the - * component is not stateless. - */ - public void pullValuesFromParent() - { - if ( associations == null ) return; - String key; - Enumeration e = associations.keyEnumerator(); - while ( e.hasMoreElements() ) - { - key = e.nextElement().toString(); - takeValueForKey( valueForBinding( key ), key ); - } - } - - /** - * Called after each phase of the request-response cycle, - * if synchronizesVariablesWithBindings is true and the - * component is not stateless. - */ - public void pushValuesToParent() - { - if ( associations == null ) return; - String key; - Enumeration e = associations.keyEnumerator(); - while ( e.hasMoreElements() ) - { - key = e.nextElement().toString(); - setValueForBinding( valueForKey( key ), key ); - } - } - - /** - * Returns whether this component should be considered stateless. - * Stateless components are shared between sessions to conserve memory. - * This implementation returns false; override to return true. - */ - public boolean isStateless() - { - return false; - } - - /** - * Called only on stateless components to tell themselves to reset - * themselves for another invocation using a different context. - * This implementation does nothing. - */ - public void reset() - { - // does nothing - } - - /** - * Returns the application containing this instance of the class. - */ - public WOApplication application () - { - return context.application(); - } - - /** - * Returns whether a session has been created for this user. - */ - public boolean hasSession () - { - return context.hasSession(); - } - - /** - * Returns the current session object, creating it if it doesn't exist. - */ - public WOSession session () - { - return context.session(); - } + } + + /** + * Returns whether this component should be considered stateless. Stateless + * components are shared between sessions to conserve memory. This + * implementation returns false; override to return true. + */ + public boolean isStateless() { + return false; + } + + /** + * Called only on stateless components to tell themselves to reset themselves + * for another invocation using a different context. This implementation does + * nothing. + */ + public void reset() { + // does nothing + } /** - * Returns the current context for this component. - */ - public WOContext context () - { - return context; - } + * Returns the application containing this instance of the class. + */ + public WOApplication application() { + return context.application(); + } /** - * Returns a new WOComponent with the specified name. - * If null, returns the component named "Main". - * If the named component doesn't exist, returns null. - */ - public WOComponent pageWithName (String aName) - { - return application().pageWithName( aName, context() ); - } + * Returns whether a session has been created for this user. + */ + public boolean hasSession() { + return context.hasSession(); + } /** - * Called when exceptions are raised by assigning values - * to this object. This implementation does nothing, but - * subclasses may override to do something useful. - */ - public void validationFailedWithException ( - Throwable anException, Object aValue, String aPath) - { - // does nothing - } + * Returns the current session object, creating it if it doesn't exist. + */ + public WOSession session() { + return context.session(); + } /** - * Called on the component that represents the requested page. - * Override to return logging information specific to your - * component. This implementation returns the component's name. - */ - public String descriptionForResponse ( - WOResponse aResponse, WOContext aContext) - { - return name(); - } + * Returns the current context for this component. + */ + public WOContext context() { + return context; + } /** - * Returns true if this component should get and set values - * in its parent. This implementation returns true. - * Override to create a component that does not automatically - * synchronize bindings with its parent, useful if you wish - * to handle synchronization manually. - */ - public boolean synchronizesVariablesWithBindings () - { - return true; - } - - /** - * Returns whether this component has a readable value that maps - * to the specified binding. This implementation calls - * hasBinding(aBinding). - */ - public boolean canGetValueForBinding(String aBinding) - { - return hasBinding( aBinding ); - } - - /** - * Returns whether this component has a writable value that maps - * to the specified binding. - */ - public boolean canSetValueForBinding(String aBinding) - { - WOAssociation assoc = - (WOAssociation)associations.objectForKey(aBinding); - if (assoc != null) - { - if ( assoc.isValueSettable() ) return true; - } - return false; - } - - /** - * Returns whether this component has the specified binding. - */ - public boolean hasBinding (String aBinding) - { - if ( associations == null ) return false; - return associations.containsKey( aBinding ); - } + * Returns a new WOComponent with the specified name. If null, returns the + * component named "Main". If the named component doesn't exist, returns null. + */ + public WOComponent pageWithName(String aName) { + return application().pageWithName(aName, context()); + } + + /** + * Called when exceptions are raised by assigning values to this object. This + * implementation does nothing, but subclasses may override to do something + * useful. + */ + public void validationFailedWithException(Throwable anException, Object aValue, String aPath) { + // does nothing + } + + /** + * Called on the component that represents the requested page. Override to + * return logging information specific to your component. This implementation + * returns the component's name. + */ + public String descriptionForResponse(WOResponse aResponse, WOContext aContext) { + return name(); + } /** - * Returns the value for the specified binding for this component. - * The parent component is expected to have set the binding for - * this component. If no such binding exists, the binding is - * treated as a property is and obtained using valueForKey. - * If the property is not found, this method returns null. - */ - public Object valueForBinding (String aBinding) - { - WOComponent parent = parent(); - if ( associations != null ) - { - WOAssociation assoc = - (WOAssociation)associations.objectForKey(aBinding); - if (assoc != null && parent != null) - { - return assoc.valueInComponent( parent ); - } - } - if ( parent != null ) - { - return parent.valueForKey( aBinding ); - } - return null; - } + * Returns true if this component should get and set values in its parent. This + * implementation returns true. Override to create a component that does not + * automatically synchronize bindings with its parent, useful if you wish to + * handle synchronization manually. + */ + public boolean synchronizesVariablesWithBindings() { + return true; + } + + /** + * Returns whether this component has a readable value that maps to the + * specified binding. This implementation calls hasBinding(aBinding). + */ + public boolean canGetValueForBinding(String aBinding) { + return hasBinding(aBinding); + } + + /** + * Returns whether this component has a writable value that maps to the + * specified binding. + */ + public boolean canSetValueForBinding(String aBinding) { + WOAssociation assoc = (WOAssociation) associations.objectForKey(aBinding); + if (assoc != null) { + if (assoc.isValueSettable()) + return true; + } + return false; + } + + /** + * Returns whether this component has the specified binding. + */ + public boolean hasBinding(String aBinding) { + if (associations == null) + return false; + return associations.containsKey(aBinding); + } + + /** + * Returns the value for the specified binding for this component. The parent + * component is expected to have set the binding for this component. If no such + * binding exists, the binding is treated as a property is and obtained using + * valueForKey. If the property is not found, this method returns null. + */ + public Object valueForBinding(String aBinding) { + WOComponent parent = parent(); + if (associations != null) { + WOAssociation assoc = (WOAssociation) associations.objectForKey(aBinding); + if (assoc != null && parent != null) { + return assoc.valueInComponent(parent); + } + } + if (parent != null) { + return parent.valueForKey(aBinding); + } + return null; + } /** - * Sets the value for the specified binding for this component. - * The parent component is expected to have set the binding - * for this component. If no such binding exists, the binding - * is treated as a property and is set using takeValueForKey. - * If the property is not found, this method fails silently. - */ - public void setValueForBinding (Object aValue, String aBinding) - { - if ( associations == null ) return; - - WOComponent parent = parent(); - - if ( associations != null ) - { - WOAssociation assoc = - (WOAssociation)associations.objectForKey(aBinding); - if (assoc != null && parent != null) - { - if ( assoc.isValueSettable() ) - { - assoc.setValue( aValue, parent ); - return; - } - } - } - if ( parent != null ) - { - parent.takeValueForKey( aValue, aBinding ); - } - } - - public static void logString (String aString) - { - System.out.println( aString ); - } - - public static void debugString (String aString) - { - System.err.println( aString ); - } - - public Object valueForKeyPath (String aPath) - { - // currently key value coding support also handles keypaths - return valueForKey( aPath ); - } - - public void takeValueForKeyPath (Object aValue, String aPath) - { - // currently key value coding support also handles keypaths - takeValueForKey( aValue, aPath ); - } - - public NSDictionary valuesForKeys (List aKeyList) - { - throw new RuntimeException( "Not implemented yet." ); - } - - public void takeValuesFromDictionary (Map aValueMap) - { - throw new RuntimeException( "Not implemented yet." ); - } - - public Object valueForKey (String aKey) - { // System.out.println( "valueForKey: " + aKey + "->" + this ); - // handle "^" property keys - if ( aKey.startsWith( "^" ) ) - { - return valueForBinding( aKey.substring(1) ); - } - return EOKeyValueCodingSupport.valueForKey( this, aKey ); - } - - public void takeValueForKey (Object aValue, String aKey) - { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); - // handle "^" property keys - if ( aKey.startsWith( "^" ) ) - { - setValueForBinding( aValue, aKey.substring(1) ); - return; - } - EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey ); - } - - public Object storedValueForKey (String aKey) - { - return EOKeyValueCodingSupport.storedValueForKey( this, aKey ); - } - - public void takeStoredValueForKey (Object aValue, String aKey) - { - EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey ); - } - - public Object handleQueryWithUnboundKey (String aKey) - { - return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); - } - - public void handleTakeValueForUnboundKey (Object aValue, String aKey) - { - EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); - } - - public void unableToSetNullForKey (String aKey) - { - EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); - } - - public Object validateTakeValueForKeyPath (Object aValue, String aKey) - { - throw new RuntimeException( "Not implemented yet." ); - } + * Sets the value for the specified binding for this component. The parent + * component is expected to have set the binding for this component. If no such + * binding exists, the binding is treated as a property and is set using + * takeValueForKey. If the property is not found, this method fails silently. + */ + public void setValueForBinding(Object aValue, String aBinding) { + if (associations == null) + return; + + WOComponent parent = parent(); + + if (associations != null) { + WOAssociation assoc = (WOAssociation) associations.objectForKey(aBinding); + if (assoc != null && parent != null) { + if (assoc.isValueSettable()) { + assoc.setValue(aValue, parent); + return; + } + } + } + if (parent != null) { + parent.takeValueForKey(aValue, aBinding); + } + } + + public static void logString(String aString) { + System.out.println(aString); + } + + public static void debugString(String aString) { + System.err.println(aString); + } + + public Object valueForKeyPath(String aPath) { + // currently key value coding support also handles keypaths + return valueForKey(aPath); + } + + public void takeValueForKeyPath(Object aValue, String aPath) { + // currently key value coding support also handles keypaths + takeValueForKey(aValue, aPath); + } + + public NSDictionary valuesForKeys(List aKeyList) { + throw new RuntimeException("Not implemented yet."); + } + + public void takeValuesFromDictionary(Map aValueMap) { + throw new RuntimeException("Not implemented yet."); + } + public Object valueForKey(String aKey) { // System.out.println( "valueForKey: " + aKey + "->" + this ); + // handle "^" property keys + if (aKey.startsWith("^")) { + return valueForBinding(aKey.substring(1)); + } + return EOKeyValueCodingSupport.valueForKey(this, aKey); + } + + public void takeValueForKey(Object aValue, String aKey) { // System.out.println( "takeValueForKey: " + aKey + " : " + // + aValue + "->" + this ); + // handle "^" property keys + if (aKey.startsWith("^")) { + setValueForBinding(aValue, aKey.substring(1)); + return; + } + EOKeyValueCodingSupport.takeValueForKey(this, aValue, aKey); + } + + public Object storedValueForKey(String aKey) { + return EOKeyValueCodingSupport.storedValueForKey(this, aKey); + } + + public void takeStoredValueForKey(Object aValue, String aKey) { + EOKeyValueCodingSupport.takeStoredValueForKey(this, aValue, aKey); + } + + public Object handleQueryWithUnboundKey(String aKey) { + return EOKeyValueCodingSupport.handleQueryWithUnboundKey(this, aKey); + } + + public void handleTakeValueForUnboundKey(Object aValue, String aKey) { + EOKeyValueCodingSupport.handleTakeValueForUnboundKey(this, aValue, aKey); + } + + public void unableToSetNullForKey(String aKey) { + EOKeyValueCodingSupport.unableToSetNullForKey(this, aKey); + } + + public Object validateTakeValueForKeyPath(Object aValue, String aKey) { + throw new RuntimeException("Not implemented yet."); + } // Template Processing - /** - * Takes a template string and a location to begin parsing, - * looking only for interesting tags, and calling itself recursively - * as necessary. Returns the index to resume parsing, or -1 if done. - */ - static private int processTemplate( - List elements, String template, int index, - Map bindings, List aLanguageList ) - throws java.io.IOException - { //System.out.println( "processTemplate: " + index ); - if ( template == null ) return -1; - - int start = index; - - while ( true ) - { - // search for start of next tag - start = template.indexOf( '<', start ); - - if ( start == -1 ) - { - // if no tags, send output and return - elements.add( new WOStaticElement( template.substring( index ) ) ); - return -1; - } - - // search for end of opening tag - int end = template.indexOf( ">", start + 1 ); - if ( end == -1 ) - { - // if no end to tag - throw new RuntimeException( "No end to tag: " - + template.substring( start ) ); - } - - boolean hasBody = true; - if ( template.charAt( end - 1 ) == '/' ) - { - // tag is standalone - no body - end = end - 1; - hasBody = false; - } - - // search for name of tag - int endName = start + 1; - while ( endName < end ) - { - if ( Character.isWhitespace( - template.charAt(endName) ) ) break; - endName++; - } - - String name = template.substring( start + 1, endName ); - - if ( name.toLowerCase().startsWith( OPEN_TAG ) ) - { - // add the contents before the tag - //System.out.println( index + " : " + start + " : " + hasBody ); - elements.add( new WOStaticElement( template.substring( index, start ) ) ); - - // interesting tag; parse parameters - Map params = new HashMap( 5 ); // arbitrary init length - if ( endName < end ) - { - // delimit by whitespace - StringTokenizer tokens = new StringTokenizer( - template.substring( endName+1, end ) ); - int equals; - String token; - String value; - while ( tokens.hasMoreTokens() ) - { - token = tokens.nextToken(); - equals = token.indexOf( '=' ); - if ( equals != -1 ) - { - value = token.substring( equals+1 ); - - if ( value.startsWith( "\"" ) ) - { - // handle spaces within parameter names - while ( ! value.endsWith( "\"" ) ) - { - value = value + " " + tokens.nextToken(); - } - - // strip quotation marks - if ( value.endsWith( "\"" ) ) - { - value = value.substring( 1, value.length()-1 ); - } - } - - // register key with specified value - params.put( - token.substring( 0, equals ).toLowerCase(), value ); - - } - else - { - // no value found, register the key name - params.put( token.toLowerCase(), "" ); - } - } - } - - index = end + (hasBody?1:2); - - WOElement body = null; - if ( hasBody ) - { - List childElements = new LinkedList(); - - index = processTemplate( - childElements, template, index, - bindings, aLanguageList ); - start = index; - - if ( index == -1 ) - { - throw new RuntimeException( - "No closing tag found: " + template.substring( end ) ); - } - - if ( childElements.size() == 1 ) - { - body = (WOElement) childElements.get(0); - } - else - { - body = new WOParentElement( childElements ); - } - } - - WOElement element = null; - String nameProperty = (String) params.get( NAME_KEY ); - NSDictionary original = (NSDictionary) bindings.get( nameProperty ); - //System.out.println( nameProperty + " : " + associations ); - if ( original == null ) - { - original = NSDictionary.EmptyDictionary; - System.err.println( "No associations for: " + nameProperty ); - System.err.println( bindings ); - } - - NSDictionary associations = new NSMutableDictionary( original ); - String elementClass = (String) associations.remove( WOApplication.ELEMENT_CLASS ); - - WOApplication application = WOApplication.application(); - element = application.dynamicElementWithName( - elementClass, associations, body, aLanguageList ); - if ( element == null ) - { - // unable to create element: show assocs in static element - element = new WOStaticElement( associations.toString() ); - } - - //System.out.println( element ); - elements.add( element ); - - if ( !hasBody ) - { - start = end + 2; - } - } - else - if ( name.toLowerCase().startsWith( CLOSE_TAG ) ) - { - // add any contents before the tag - elements.add( new WOStaticElement( template.substring( index, start ) ) ); - + /** + * Takes a template string and a location to begin parsing, looking only for + * interesting tags, and calling itself recursively as necessary. Returns the + * index to resume parsing, or -1 if done. + */ + static private int processTemplate(List elements, String template, int index, Map bindings, List aLanguageList) + throws java.io.IOException { // System.out.println( "processTemplate: " + index ); + if (template == null) + return -1; + + int start = index; + + while (true) { + // search for start of next tag + start = template.indexOf('<', start); + + if (start == -1) { + // if no tags, send output and return + elements.add(new WOStaticElement(template.substring(index))); + return -1; + } + + // search for end of opening tag + int end = template.indexOf(">", start + 1); + if (end == -1) { + // if no end to tag + throw new RuntimeException("No end to tag: " + template.substring(start)); + } + + boolean hasBody = true; + if (template.charAt(end - 1) == '/') { + // tag is standalone - no body + end = end - 1; + hasBody = false; + } + + // search for name of tag + int endName = start + 1; + while (endName < end) { + if (Character.isWhitespace(template.charAt(endName))) + break; + endName++; + } + + String name = template.substring(start + 1, endName); + + if (name.toLowerCase().startsWith(OPEN_TAG)) { + // add the contents before the tag + // System.out.println( index + " : " + start + " : " + hasBody ); + elements.add(new WOStaticElement(template.substring(index, start))); + + // interesting tag; parse parameters + Map params = new HashMap(5); // arbitrary init length + if (endName < end) { + // delimit by whitespace + StringTokenizer tokens = new StringTokenizer(template.substring(endName + 1, end)); + int equals; + String token; + String value; + while (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + equals = token.indexOf('='); + if (equals != -1) { + value = token.substring(equals + 1); + + if (value.startsWith("\"")) { + // handle spaces within parameter names + while (!value.endsWith("\"")) { + value = value + " " + tokens.nextToken(); + } + + // strip quotation marks + if (value.endsWith("\"")) { + value = value.substring(1, value.length() - 1); + } + } + + // register key with specified value + params.put(token.substring(0, equals).toLowerCase(), value); + + } else { + // no value found, register the key name + params.put(token.toLowerCase(), ""); + } + } + } + + index = end + (hasBody ? 1 : 2); + + WOElement body = null; + if (hasBody) { + List childElements = new LinkedList(); + + index = processTemplate(childElements, template, index, bindings, aLanguageList); + start = index; + + if (index == -1) { + throw new RuntimeException("No closing tag found: " + template.substring(end)); + } + + if (childElements.size() == 1) { + body = (WOElement) childElements.get(0); + } else { + body = new WOParentElement(childElements); + } + } + + WOElement element = null; + String nameProperty = (String) params.get(NAME_KEY); + NSDictionary original = (NSDictionary) bindings.get(nameProperty); + // System.out.println( nameProperty + " : " + associations ); + if (original == null) { + original = NSDictionary.EmptyDictionary; + System.err.println("No associations for: " + nameProperty); + System.err.println(bindings); + } + + NSDictionary associations = new NSMutableDictionary(original); + String elementClass = (String) associations.remove(WOApplication.ELEMENT_CLASS); + + WOApplication application = WOApplication.application(); + element = application.dynamicElementWithName(elementClass, associations, body, aLanguageList); + if (element == null) { + // unable to create element: show assocs in static element + element = new WOStaticElement(associations.toString()); + } + + // System.out.println( element ); + elements.add(element); + + if (!hasBody) { + start = end + 2; + } + } else if (name.toLowerCase().startsWith(CLOSE_TAG)) { + // add any contents before the tag + elements.add(new WOStaticElement(template.substring(index, start))); + // return end + name.length() + 1; // "<" + ">" - 1 = 1 - return end + (hasBody?1:2); // "<" + ">" - 1 = 1 - } - else - { - // tag not interesting: continue - start = end + (hasBody?1:2); - } - } - } - - - // Utility Methods - - static private void rewriteTag( String tagName, - Map properties, String body, StringBuffer context ) - throws java.io.IOException - { - context.append( "<"+tagName ); + return end + (hasBody ? 1 : 2); // "<" + ">" - 1 = 1 + } else { + // tag not interesting: continue + start = end + (hasBody ? 1 : 2); + } + } + } + + // Utility Methods + + static private void rewriteTag(String tagName, Map properties, String body, StringBuffer context) + throws java.io.IOException { + context.append("<" + tagName); Iterator it = properties.keySet().iterator(); String key; - while ( it.hasNext() ) - { + while (it.hasNext()) { key = (String) it.next(); - context.append( " " + key + "=\"" + properties.get( key ) + "\"" ); + context.append(" " + key + "=\"" + properties.get(key) + "\""); } - - if ( body == null ) - { - context.append( "/>" ); + + if (body == null) { + context.append("/>"); return; } - - context.append( ">" + body + "" ); + + context.append(">" + body + ""); + } + + private String justTheClassName() { + String className = getClass().getName(); + int index = className.lastIndexOf("."); + if (index == -1) + return className; + return className.substring(index + 1); + } + + private String justTheResourcePath() { + int last = -1; + char[] src = getClass().getName().toCharArray(); + char[] dst = new char[src.length]; + for (int i = 0; i < src.length; i++) { + if (src[i] == '.') { + dst[i] = '/'; + last = i; + } else { + dst[i] = src[i]; + } + } + if (last == -1) + return null; + return new String(dst, 0, last); } - - private String justTheClassName() - { - String className = getClass().getName(); - int index = className.lastIndexOf( "." ); - if ( index == -1 ) return className; - return className.substring( index+1 ); - } - - private String justTheResourcePath() - { - int last = -1; - char[] src = getClass().getName().toCharArray(); - char[] dst = new char[ src.length ]; - for ( int i = 0; i < src.length; i++ ) - { - if ( src[i] == '.' ) - { - dst[i] = '/'; - last = i; - } - else - { - dst[i] = src[i]; - } - } - if ( last == -1 ) return null; - return new String( dst, 0, last ); - } - - private String readTemplateResource( String name, String framework, String suffix, NSArray languages ) - { - if ( name == null ) return null; - name = name + DIRECTORY_SUFFIX + '/' + name + suffix; - InputStream is = null; - if ( isCachingEnabled() ) - { - byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed( - name, framework, languages ); - if ( data != null ) - { - is = new ByteArrayInputStream( data ); - } - } - else - { - is = WOApplication.application().resourceManager().inputStreamForResourceNamed( - name, framework, languages ); - } - if ( is == null ) - { - System.err.println( "No resources found for: " + name ); - return null; - } - - // try to autodetect encoding - String encoding = "ISO8859_1"; - try - { - byte[] header = new byte[4]; - is = new PushbackInputStream( is, 4 ); - is.read( header ); - if ( header[0] < 33 || header[0] > 126 ) - { - // if any funny characters, presume UTF-16 - encoding = "UTF-16"; - if (!( header[1] < 33 || header[1] > 126 )) - { - // if second character is valid, presume UTF-8 - encoding = "UTF-8"; - } - } - // check byte-order-mark - if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf) // utf-8 - { - encoding = "UTF-8"; - } - else - if (header[0] == 0xfe && header[1] == 0xff) // utf-16 - { - encoding = "UTF-16"; - } - else - if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff) // ucs-4 - { - encoding = "UCS-4"; //?? - } - else - if (header[0] == 0xff && header[1] == 0xfe) // ucs-2le, ucs-4le, and - { - encoding = "UCS-16le"; //?? - } - // put back the header - ((PushbackInputStream)is).unread( header ); - } - catch ( Throwable t ) - { - t.printStackTrace(); - System.err.println( - "Error while autodetecting encoding: should never happen" ); - } - - try - { - String line; - StringBuffer buf = new StringBuffer(); - BufferedReader r = new BufferedReader( new InputStreamReader( is, encoding ) ); - while ( ( line = r.readLine() ) != null ) - { - buf.append( line ); - buf.append( '\n' ); - } - is.close(); // release the resource - return buf.toString(); - } - catch ( IOException exc ) - { - System.err.println( "Error while reading: " + name ); - exc.printStackTrace(); - return null; - } - } - - // Declaration Parsing - - /** - * Parses the declarations in the specified content and returns a map of element names - * to maps of attribute names to WOAssociations. - */ - private static NSDictionary processDeclaration( String content ) - { - int index; - NSMutableDictionary result = new NSMutableDictionary(); - - // strip out comments - StringBuffer stripped = new StringBuffer(); - try - { - LineNumberReader reader = - new LineNumberReader( new StringReader( content ) ); - String line; - while ( ( line = reader.readLine() ) != null ) - { - index = line.indexOf("//"); - while (index > -1) { - //(chochos) This used to truncate lines with quoted URLs - //in them. We have to check that the "//" is not inside quotes. - boolean quoted = false; - if (index > 0) { - for (int _position = 0; _position < index; _position++) - if (line.charAt(_position) == '"') - quoted = !quoted; - } - if (!quoted) { - line = line.substring( 0, index ); - index = -1; - } else { - //if we didn't truncate the line it's because the // - //were quoted. let's look for more, and check if they're not quoted... - index = line.indexOf("\"", index); - if (index > 0) { - index = line.indexOf("//", index); - } - } - } - stripped.append( line ); - } + + private String readTemplateResource(String name, String framework, String suffix, NSArray languages) { + if (name == null) + return null; + name = name + DIRECTORY_SUFFIX + '/' + name + suffix; + InputStream is = null; + if (isCachingEnabled()) { + byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed(name, framework, + languages); + if (data != null) { + is = new ByteArrayInputStream(data); + } + } else { + is = WOApplication.application().resourceManager().inputStreamForResourceNamed(name, framework, languages); } - catch ( IOException exc ) - { - throw new RuntimeException( - "Error while stripping comments from declaration: " + stripped ); + if (is == null) { + System.err.println("No resources found for: " + name); + return null; } - while ( (index = stripped.toString().indexOf( "/*" )) != -1 ) - { - int j = stripped.toString().indexOf( "*/", index+1 ); - if ( j == -1 ) break; - stripped.delete( index, j+2 ); - } - - String token; - StringTokenizer tokens = new StringTokenizer( stripped.toString(), "{}", true ); - while ( tokens.hasMoreTokens() ) - { - token = tokens.nextToken(); - - // next token is the name and class - String name, cl; - index = token.indexOf( ":" ); - if ( index > -1 ) - { - name = token.substring( 0, index ).trim(); - cl = token.substring( index+1 ).trim(); + + // try to autodetect encoding + String encoding = "ISO8859_1"; + try { + byte[] header = new byte[4]; + is = new PushbackInputStream(is, 4); + is.read(header); + if (header[0] < 33 || header[0] > 126) { + // if any funny characters, presume UTF-16 + encoding = "UTF-16"; + if (!(header[1] < 33 || header[1] > 126)) { + // if second character is valid, presume UTF-8 + encoding = "UTF-8"; + } } - else + // check byte-order-mark + if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf) // utf-8 + { + encoding = "UTF-8"; + } else if (header[0] == 0xfe && header[1] == 0xff) // utf-16 + { + encoding = "UTF-16"; + } else if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff) // ucs-4 { - System.err.println( "Could not parse declaration:" ); - System.err.println( content ); - throw new RuntimeException( - "Could not parse declaration: " + token ); - } + encoding = "UCS-4"; // ?? + } else if (header[0] == 0xff && header[1] == 0xfe) // ucs-2le, ucs-4le, and + { + encoding = "UCS-16le"; // ?? + } + // put back the header + ((PushbackInputStream) is).unread(header); + } catch (Throwable t) { + t.printStackTrace(); + System.err.println("Error while autodetecting encoding: should never happen"); + } + + try { + String line; + StringBuffer buf = new StringBuffer(); + BufferedReader r = new BufferedReader(new InputStreamReader(is, encoding)); + while ((line = r.readLine()) != null) { + buf.append(line); + buf.append('\n'); + } + is.close(); // release the resource + return buf.toString(); + } catch (IOException exc) { + System.err.println("Error while reading: " + name); + exc.printStackTrace(); + return null; + } + } + + // Declaration Parsing + + /** + * Parses the declarations in the specified content and returns a map of element + * names to maps of attribute names to WOAssociations. + */ + private static NSDictionary processDeclaration(String content) { + int index; + NSMutableDictionary result = new NSMutableDictionary(); + + // strip out comments + StringBuffer stripped = new StringBuffer(); + try { + LineNumberReader reader = new LineNumberReader(new StringReader(content)); + String line; + while ((line = reader.readLine()) != null) { + index = line.indexOf("//"); + while (index > -1) { + // (chochos) This used to truncate lines with quoted URLs + // in them. We have to check that the "//" is not inside quotes. + boolean quoted = false; + if (index > 0) { + for (int _position = 0; _position < index; _position++) + if (line.charAt(_position) == '"') + quoted = !quoted; + } + if (!quoted) { + line = line.substring(0, index); + index = -1; + } else { + // if we didn't truncate the line it's because the // + // were quoted. let's look for more, and check if they're not quoted... + index = line.indexOf("\"", index); + if (index > 0) { + index = line.indexOf("//", index); + } + } + } + stripped.append(line); + } + } catch (IOException exc) { + throw new RuntimeException("Error while stripping comments from declaration: " + stripped); + } + while ((index = stripped.toString().indexOf("/*")) != -1) { + int j = stripped.toString().indexOf("*/", index + 1); + if (j == -1) + break; + stripped.delete(index, j + 2); + } + + String token; + StringTokenizer tokens = new StringTokenizer(stripped.toString(), "{}", true); + while (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + + // next token is the name and class + String name, cl; + index = token.indexOf(":"); + if (index > -1) { + name = token.substring(0, index).trim(); + cl = token.substring(index + 1).trim(); + } else { + System.err.println("Could not parse declaration:"); + System.err.println(content); + throw new RuntimeException("Could not parse declaration: " + token); + } // next token is the declaration for the name and class - if ( ! tokens.hasMoreTokens() ) - { - System.err.println( "Could not find associations for declaration:" ); - System.err.println( content ); - throw new RuntimeException( - "Could not find associations for declaration: " + name ); + if (!tokens.hasMoreTokens()) { + System.err.println("Could not find associations for declaration:"); + System.err.println(content); + throw new RuntimeException("Could not find associations for declaration: " + name); + } + + token = tokens.nextToken(); + if (token.equals("{")) { + if (!tokens.hasMoreTokens()) + throw new RuntimeException("Error parsing declaration: expected { but found: '" + token + "'"); + token = tokens.nextToken(); + } + + NSMutableDictionary associations = new NSMutableDictionary(); + + if (!token.equals("}")) { + String line, key, value; + StringTokenizer lines = new StringTokenizer(token, ";"); + while (lines.hasMoreElements()) { + line = lines.nextToken(); + index = line.indexOf("="); + if (index > -1) { + if (line.length() == index + 1) + line += " "; + key = line.substring(0, index).trim(); + value = line.substring(index + 1).trim(); + } else { + // not a valid key: skip + key = null; + value = null; + } + + if (key != null) { + // if in quotation marks + if ((value.startsWith("\"")) && (value.endsWith("\""))) { + // it's a constant value association + value = value.substring(1, value.length() - 1); + associations.put(key, WOAssociation.associationWithValue(value)); + } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { + // HACK: needed to be compatible with woextensions + // apparently true and false are allowed without quotes + associations.put(key, WOAssociation.associationWithValue(value)); + } else { + // HACK: needed to be compatible with woextensions: + // apparently a standalone integer is allowed without quotes. + try { + Integer.parseInt(value); // does it parse? + associations.put(key, WOAssociation.associationWithValue(value)); + } catch (NumberFormatException nfe) { + // did not parse: + // it's a key path association + associations.put(key, WOAssociation.associationWithKeyPath(value)); + } + } + } + } + if (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + if (!token.equals("}")) + throw new RuntimeException("Error parsing declaration: expected } but found: '" + token + "'"); + } } - - token = tokens.nextToken(); - if ( token.equals( "{" ) ) - { - if ( !tokens.hasMoreTokens() ) throw new RuntimeException( - "Error parsing declaration: expected { but found: '" + token + "'" ); - token = tokens.nextToken(); - } - - NSMutableDictionary associations = new NSMutableDictionary(); - - if ( !token.equals( "}" ) ) - { - String line, key, value; - StringTokenizer lines = - new StringTokenizer( token, ";" ); - while ( lines.hasMoreElements() ) - { - line = lines.nextToken(); - index = line.indexOf( "=" ); - if ( index > -1 ) - { - if ( line.length() == index+ 1 ) line += " "; - key = line.substring( 0, index ).trim(); - value = line.substring( index+1 ).trim(); - } - else - { - // not a valid key: skip - key = null; - value = null; - } - - if ( key != null ) - { - // if in quotation marks - if ( ( value.startsWith( "\"" ) ) && ( value.endsWith( "\"" ) ) ) - { - // it's a constant value association - value = value.substring( 1, value.length()-1 ); - associations.put( key, - WOAssociation.associationWithValue( value ) ); - } - else - if ( value.equalsIgnoreCase( "true" ) || value.equalsIgnoreCase( "false" ) ) - { - //HACK: needed to be compatible with woextensions - // apparently true and false are allowed without quotes - associations.put( key, - WOAssociation.associationWithValue( value ) ); - } - else - { - //HACK: needed to be compatible with woextensions: - // apparently a standalone integer is allowed without quotes. - try - { - Integer.parseInt( value ); // does it parse? - associations.put( key, - WOAssociation.associationWithValue( value ) ); - } - catch ( NumberFormatException nfe ) - { - // did not parse: - // it's a key path association - associations.put( key, - WOAssociation.associationWithKeyPath( value ) ); - } - } - } - } - if ( tokens.hasMoreTokens() ) - { - token = tokens.nextToken(); - if ( !token.equals( "}" ) ) throw new RuntimeException( - "Error parsing declaration: expected } but found: '" + token + "'" ); - } - } - associations.put( WOApplication.ELEMENT_CLASS, cl ); // store classname - result.put( name, associations ); - - } - //System.out.println( "processDeclaration: " + result ); - return result; - } + associations.put(WOApplication.ELEMENT_CLASS, cl); // store classname + result.put(name, associations); + + } + // System.out.println( "processDeclaration: " + result ); + return result; + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.32 2003/08/07 00:15:14 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.32 2003/08/07 00:15:14 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.31 2003/07/24 00:23:21 chochos - * fixed problem with parsing wod files that have //-type comments. Quotes URL's would be truncated. + * Revision 1.31 2003/07/24 00:23:21 chochos fixed problem with parsing wod + * files that have //-type comments. Quotes URL's would be truncated. * - * Revision 1.30 2003/03/28 15:33:11 mpowers - * Now using a PushBackInputStream for auto detection of content encoding. - * No longer relying on markSupported() since jar input streams don't have it. + * Revision 1.30 2003/03/28 15:33:11 mpowers Now using a PushBackInputStream for + * auto detection of content encoding. No longer relying on markSupported() + * since jar input streams don't have it. * - * Revision 1.29 2003/03/03 16:41:52 mpowers - * Bad characters in cvs log. + * Revision 1.29 2003/03/03 16:41:52 mpowers Bad characters in cvs log. * - * Revision 1.28 2003/03/03 16:37:35 mpowers - * Better handling for string encodings. - * Now trying to autodetect unicode-formatted templates and declarations. - * Now handlings block-style comments in declarations. + * Revision 1.28 2003/03/03 16:37:35 mpowers Better handling for string + * encodings. Now trying to autodetect unicode-formatted templates and + * declarations. Now handlings block-style comments in declarations. * - * Revision 1.27 2003/01/28 19:33:51 mpowers - * Implemented the rest of WOResourceManager. - * Implemented support for java-style i18n. - * Components now use the resource manager to load templates. + * Revision 1.27 2003/01/28 19:33:51 mpowers Implemented the rest of + * WOResourceManager. Implemented support for java-style i18n. Components now + * use the resource manager to load templates. * - * Revision 1.26 2003/01/24 20:13:22 mpowers - * Now accepting immutable NSDictionary in constructor, not Map. + * Revision 1.26 2003/01/24 20:13:22 mpowers Now accepting immutable + * NSDictionary in constructor, not Map. * - * Revision 1.25 2003/01/21 17:53:45 mpowers - * Now correctly reporting error for missing bindings. + * Revision 1.25 2003/01/21 17:53:45 mpowers Now correctly reporting error for + * missing bindings. * - * Revision 1.24 2003/01/20 17:50:11 mpowers - * Caught a loop condition when same declaration was used twice. + * Revision 1.24 2003/01/20 17:50:11 mpowers Caught a loop condition when same + * declaration was used twice. * - * Revision 1.23 2003/01/19 22:33:25 mpowers - * Fixed problems with classpath and dynamic class loading. - * Dynamic elements now pass on ensureAwakeInContext. + * Revision 1.23 2003/01/19 22:33:25 mpowers Fixed problems with classpath and + * dynamic class loading. Dynamic elements now pass on ensureAwakeInContext. * Parser how handles tags. * - * Revision 1.22 2003/01/17 22:55:09 mpowers - * Straighted out the parent binding issue (I think). - * Fixes for woextensions compatibility. + * Revision 1.22 2003/01/17 22:55:09 mpowers Straighted out the parent binding + * issue (I think). Fixes for woextensions compatibility. * - * Revision 1.21 2003/01/17 20:34:57 mpowers - * Better handling for components and parents in the context's element stack. + * Revision 1.21 2003/01/17 20:34:57 mpowers Better handling for components and + * parents in the context's element stack. * - * Revision 1.19 2003/01/17 15:32:22 mpowers - * Changes to better support generic elements and containers. - * Now preserving newlines in templates. + * Revision 1.19 2003/01/17 15:32:22 mpowers Changes to better support generic + * elements and containers. Now preserving newlines in templates. * - * Revision 1.17 2003/01/16 22:47:30 mpowers - * Compatibility changes to support compiling woextensions source. - * (34 out of 56 classes compile!) + * Revision 1.17 2003/01/16 22:47:30 mpowers Compatibility changes to support + * compiling woextensions source. (34 out of 56 classes compile!) * - * Revision 1.15 2003/01/16 15:50:43 mpowers - * More robust declaration parsing. - * Subcomponents are now supported. - * dynamicElementWithName can now return subcomponents. + * Revision 1.15 2003/01/16 15:50:43 mpowers More robust declaration parsing. + * Subcomponents are now supported. dynamicElementWithName can now return + * subcomponents. * - * Revision 1.14 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.14 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.13 2003/01/15 14:33:48 mpowers - * Refactoring: element id handling is now confined to WOParentElement. - * Other elements/components should not have to do element id incrementing. + * Revision 1.13 2003/01/15 14:33:48 mpowers Refactoring: element id handling is + * now confined to WOParentElement. Other elements/components should not have to + * do element id incrementing. * - * Revision 1.12 2003/01/14 16:05:12 mpowers - * Removed extraneous printlns. + * Revision 1.12 2003/01/14 16:05:12 mpowers Removed extraneous printlns. * - * Revision 1.11 2003/01/13 22:24:25 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.11 2003/01/13 22:24:25 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.10 2003/01/10 19:33:28 mpowers - * Added contextID for the component url generation. + * Revision 1.10 2003/01/10 19:33:28 mpowers Added contextID for the component + * url generation. * - * Revision 1.9 2003/01/10 19:16:40 mpowers - * Implemented support for page caching. + * Revision 1.9 2003/01/10 19:16:40 mpowers Implemented support for page + * caching. * - * Revision 1.8 2003/01/09 21:16:48 mpowers - * Bringing request-response cycle more into conformance. + * Revision 1.8 2003/01/09 21:16:48 mpowers Bringing request-response cycle more + * into conformance. * - * Revision 1.7 2003/01/09 16:13:55 mpowers - * Implemented WOComponentRequestHandler: - * Bringing the request-response cycle more into conformance. + * Revision 1.7 2003/01/09 16:13:55 mpowers Implemented + * WOComponentRequestHandler: Bringing the request-response cycle more into + * conformance. * - * Revision 1.6 2002/12/20 22:56:33 mpowers - * Reimplemented the template parsing again. - * Nested components are now correctly parsed. - * ElementID numbering is now working. + * Revision 1.6 2002/12/20 22:56:33 mpowers Reimplemented the template parsing + * again. Nested components are now correctly parsed. ElementID numbering is now + * working. * - * Revision 1.3 2002/12/18 14:12:38 mpowers - * Support for differentiated request handlers. - * Support url generation for WOContext and WORequest. + * Revision 1.3 2002/12/18 14:12:38 mpowers Support for differentiated request + * handlers. Support url generation for WOContext and WORequest. * - * Revision 1.2 2002/11/07 18:52:33 mpowers - * New components courtesy of ezamudio@nasoft.com. Many thanks! + * Revision 1.2 2002/11/07 18:52:33 mpowers New components courtesy of + * ezamudio@nasoft.com. Many thanks! * - * Revision 1.1.1.1 2000/12/21 15:53:01 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:01 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java index 1544934..909212c 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java @@ -21,25 +21,28 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** - * Used to include a component's content inside another component. This way, you can create reusable components - * with content that surrounds the component it's in. In reality all it does is forward the appendToResponse - * method to the WOElement containing the HTML between the WEBOBJECT tags that define this element. + * Used to include a component's content inside another component. This way, you + * can create reusable components with content that surrounds the component it's + * in. In reality all it does is forward the appendToResponse method to the + * WOElement containing the HTML between the WEBOBJECT tags that define this + * element. + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOComponentContent extends WODynamicElement { - public WOComponentContent() { - super(); - } + public WOComponentContent() { + super(); + } - public WOComponentContent(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOComponentContent(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void appendToResponse(WOResponse r, WOContext c) { - c.component().rootElement.appendToResponse(r, c); - } + public void appendToResponse(WOResponse r, WOContext c) { + c.component().rootElement.appendToResponse(r, c); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java index 9f79987..9d35ab4 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java @@ -18,212 +18,168 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web; - /** -* An implementation of WORequestHandler that dispatches Component actions. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOComponentRequestHandler - extends WORequestHandler -{ - public WOResponse handleRequest( WORequest aRequest ) - { - WOApplication application = aRequest.application(); - WOContext context = WOContext.contextWithRequest( aRequest ); - WOResponse response = null; - - // no concurrent access is allowed to a user's session: - // a user/browser/machine with multiple requests will have to wait - synchronized ( aRequest.request.getSession() ) - { - try - { - application.awake(); - - WOSession session = null; - /* - // the NeXT way - String sessionID = aRequest.sessionID(); - if ( sessionID != null ) - { - session = application.restoreSessionWithID( sessionID, context ); - if ( session == null ) - { - response = application.handleSessionRestorationErrorInContext( context ); - } - } - else - { - session = application.createSessionForRequest( aRequest ); - if ( session == null ) - { - response = application.handleSessionCreationErrorInContext( context ); - } - else - { - session.setContext( context ); - } - } - */ - // the servlet way - String sessionID = aRequest.sessionID(); - if ( sessionID != null ) - { - session = application.restoreSessionWithID( sessionID, context ); - } - - if ( session == null ) - { - session = application.createSessionForRequest( aRequest ); - if ( session == null ) - { - response = application.handleSessionCreationErrorInContext( context ); - } - else - { - session.setContext( context ); - } - } - - context.setSession( session ); - - session.awake(); - - if ( response == null ) - { - - WOComponent page; - String contextID = aRequest.contextID(); - - if ( contextID != null ) - { - page = session.restorePageForContextID( contextID ); - } - else - { - page = application.pageWithName( aRequest.pageName(), context ); - } - - if ( page == null ) - { - //FIXME: should we call this a restoration error, or do we - // allow a bookmark with an expired context id to resume gracefully? - page = application.pageWithName( aRequest.pageName(), context ); - //!response = application.handlePageRestorationErrorInContext( context ); - } - //!else - { - context.pushElement( page ); //? needed? - page.ensureAwakeInContext( context ); //? shouldn't this be in WOApplication? - - // only take values from request if there are values in the request - System.out.println("should I takeValuesFromRequest ? " + ( aRequest.formValueKeys().count() > 0 )); - if ( aRequest.formValueKeys().count() > 0 ) - { - application.takeValuesFromRequest( aRequest, context ); - } - - // only invoke action if there is a sender id to invoke - WOActionResults result; - System.out.println("senderID: " + aRequest.senderID()); - if ( aRequest.senderID() != null ) - { - result = application.invokeAction( aRequest, context ); - } - else - { - result = null; - } - - if ( result == null || result == page ) // same page is returned - { - result = page; - - // generate response - response = new WOResponse(); - application.appendToResponse( response, context ); - page.sleep(); - } - else // different page is returned - if ( result instanceof WOComponent ) - { - page.sleep(); - page = (WOComponent) result; - page.ensureAwakeInContext( context ); - context.popElement(); // removes page - context.pushElement( page ); - - // generate response - response = new WOResponse(); - application.appendToResponse( response, context ); - page.sleep(); - } - else // WOResponse was returnd - { - response = (WOResponse) result; - } - - context.popElement(); - session.sleep(); - session.savePage( page ); - session.setContext( null ); - application.saveSessionForContext( context ); - } - } - } - catch ( Throwable t ) - { - response = application.handleException( t, context ); - } - - application.sleep(); - } - return response; - } + * An implementation of WORequestHandler that dispatches Component actions. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOComponentRequestHandler extends WORequestHandler { + public WOResponse handleRequest(WORequest aRequest) { + WOApplication application = aRequest.application(); + WOContext context = WOContext.contextWithRequest(aRequest); + WOResponse response = null; + + // no concurrent access is allowed to a user's session: + // a user/browser/machine with multiple requests will have to wait + synchronized (aRequest.request.getSession()) { + try { + application.awake(); + + WOSession session = null; + /* + * // the NeXT way String sessionID = aRequest.sessionID(); if ( sessionID != + * null ) { session = application.restoreSessionWithID( sessionID, context ); if + * ( session == null ) { response = + * application.handleSessionRestorationErrorInContext( context ); } } else { + * session = application.createSessionForRequest( aRequest ); if ( session == + * null ) { response = application.handleSessionCreationErrorInContext( context + * ); } else { session.setContext( context ); } } + */ + // the servlet way + String sessionID = aRequest.sessionID(); + if (sessionID != null) { + session = application.restoreSessionWithID(sessionID, context); + } + + if (session == null) { + session = application.createSessionForRequest(aRequest); + if (session == null) { + response = application.handleSessionCreationErrorInContext(context); + } else { + session.setContext(context); + } + } + + context.setSession(session); + + session.awake(); + + if (response == null) { + + WOComponent page; + String contextID = aRequest.contextID(); + + if (contextID != null) { + page = session.restorePageForContextID(contextID); + } else { + page = application.pageWithName(aRequest.pageName(), context); + } + + if (page == null) { + // FIXME: should we call this a restoration error, or do we + // allow a bookmark with an expired context id to resume gracefully? + page = application.pageWithName(aRequest.pageName(), context); + // !response = application.handlePageRestorationErrorInContext( context ); + } + // !else + { + context.pushElement(page); // ? needed? + page.ensureAwakeInContext(context); // ? shouldn't this be in WOApplication? + + // only take values from request if there are values in the request + System.out + .println("should I takeValuesFromRequest ? " + (aRequest.formValueKeys().count() > 0)); + if (aRequest.formValueKeys().count() > 0) { + application.takeValuesFromRequest(aRequest, context); + } + + // only invoke action if there is a sender id to invoke + WOActionResults result; + System.out.println("senderID: " + aRequest.senderID()); + if (aRequest.senderID() != null) { + result = application.invokeAction(aRequest, context); + } else { + result = null; + } + + if (result == null || result == page) // same page is returned + { + result = page; + + // generate response + response = new WOResponse(); + application.appendToResponse(response, context); + page.sleep(); + } else // different page is returned + if (result instanceof WOComponent) { + page.sleep(); + page = (WOComponent) result; + page.ensureAwakeInContext(context); + context.popElement(); // removes page + context.pushElement(page); + + // generate response + response = new WOResponse(); + application.appendToResponse(response, context); + page.sleep(); + } else // WOResponse was returnd + { + response = (WOResponse) result; + } + + context.popElement(); + session.sleep(); + session.savePage(page); + session.setContext(null); + application.saveSessionForContext(context); + } + } + } catch (Throwable t) { + response = application.handleException(t, context); + } + + application.sleep(); + } + return response; + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.8 2003/08/07 00:15:15 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.8 2003/08/07 00:15:15 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.7 2003/01/17 20:34:57 mpowers - * Better handling for components and parents in the context's element stack. + * Revision 1.7 2003/01/17 20:34:57 mpowers Better handling for components and + * parents in the context's element stack. * - * Revision 1.6 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.6 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.4 2003/01/14 15:51:09 mpowers - * No longer calling takeValues or invokeAction if there are no request params + * Revision 1.4 2003/01/14 15:51:09 mpowers No longer calling takeValues or + * invokeAction if there are no request params * - * Revision 1.3 2003/01/13 22:24:34 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.3 2003/01/13 22:24:34 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.1 2003/01/09 16:13:59 mpowers - * Implemented WOComponentRequestHandler: - * Bringing the request-response cycle more into conformance. + * Revision 1.1 2003/01/09 16:13:59 mpowers Implemented + * WOComponentRequestHandler: Bringing the request-response cycle more into + * conformance. * - * Revision 1.2 2002/12/17 14:57:44 mpowers - * Minor corrections to WORequests's parsing, and updated javadocs. + * Revision 1.2 2002/12/17 14:57:44 mpowers Minor corrections to WORequests's + * parsing, and updated javadocs. * - * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:50 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:50 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java index 124ca11..d0ab35e 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java @@ -21,14 +21,13 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** -* WOConditional renders whatever is inside its opening and closing tags - * only if a condition is met. - * Bindings are: + * WOConditional renders whatever is inside its opening and closing tags only if + * a condition is met. Bindings are: *
        - *
      • condition: a boolean property that indicates whether the contents of the element - * should be displayed, invoked, or passed the request form values.
      • - *
      • negate: if this is true, then the behavior of the element is reversed, showing its - * contents only if the condition is NOT met.
      • + *
      • condition: a boolean property that indicates whether the contents of the + * element should be displayed, invoked, or passed the request form values.
      • + *
      • negate: if this is true, then the behavior of the element is reversed, + * showing its contents only if the condition is NOT met.
      • *
      * * @author ezamudio@nasoft.com @@ -37,63 +36,65 @@ import net.wotonomy.foundation.NSDictionary; */ public class WOConditional extends WODynamicElement { - public boolean condition; - public boolean negate; - - protected WOConditional() { - super(); - } - - public WOConditional(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } - - public void setCondition(boolean value) { - condition = value; - } - public boolean condition() { - return condition; - } - - public void setNegate(boolean value) { - negate = value; - } - public boolean negate() { - return negate; - } - - protected void pullValuesFromParent(WOComponent c) { - condition = booleanForProperty("condition", c); - negate = booleanForProperty("negate", c); - } - - public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { - if (rootElement == null) - return; - pullValuesFromParent(aContext.component()); - if ((condition && !negate) || (!condition && negate)) { - rootElement.takeValuesFromRequest(aRequest, aContext); - } - } - - public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { - if (rootElement == null) - return null; - pullValuesFromParent(aContext.component()); - WOActionResults el = null; - if ((condition && !negate) || (!condition && negate)) { - el = rootElement.invokeAction(aRequest, aContext); - } - return el; - } - - public void appendToResponse(WOResponse aResponse, WOContext aContext) { - if (rootElement == null) - return; - pullValuesFromParent(aContext.component()); - if ((condition && !negate) || (!condition && negate)) { - rootElement.appendToResponse(aResponse, aContext); - } - } + public boolean condition; + public boolean negate; + + protected WOConditional() { + super(); + } + + public WOConditional(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } + + public void setCondition(boolean value) { + condition = value; + } + + public boolean condition() { + return condition; + } + + public void setNegate(boolean value) { + negate = value; + } + + public boolean negate() { + return negate; + } + + protected void pullValuesFromParent(WOComponent c) { + condition = booleanForProperty("condition", c); + negate = booleanForProperty("negate", c); + } + + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + if (rootElement == null) + return; + pullValuesFromParent(aContext.component()); + if ((condition && !negate) || (!condition && negate)) { + rootElement.takeValuesFromRequest(aRequest, aContext); + } + } + + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + if (rootElement == null) + return null; + pullValuesFromParent(aContext.component()); + WOActionResults el = null; + if ((condition && !negate) || (!condition && negate)) { + el = rootElement.invokeAction(aRequest, aContext); + } + return el; + } + + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + if (rootElement == null) + return; + pullValuesFromParent(aContext.component()); + if ((condition && !negate) || (!condition && negate)) { + rootElement.appendToResponse(aResponse, aContext); + } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java index 4f774e6..40c5f5c 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java @@ -25,548 +25,455 @@ import java.util.List; import java.util.Map; /** -* A pure java implementation of WOContext. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOContext -{ + * A pure java implementation of WOContext. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOContext { private WOSession session; private WORequest request; private WOResponse response; - private List elementStack; + private List elementStack; private boolean isInForm; private boolean isDistributionEnabled; - private static final String EMPTY = ""; - private static final String SEP = "."; - private static final String ZERO = "0"; - private static final String HTTP = "http://"; - private static final String HTTPS = "https://"; + private static final String EMPTY = ""; + private static final String SEP = "."; + private static final String ZERO = "0"; + private static final String HTTP = "http://"; + private static final String HTTPS = "https://"; // package access WOComponent page; WOComponent component; StringBuffer elementID; - - private static volatile int contextCounter = 0; - private String contextID; - - /** - * Default constructor performs necessary initialization. - * Subclassers should override the static factory method below. - */ - public WOContext () - { - contextID = null; - elementID = new StringBuffer(); - elementStack = new LinkedList(); - } - - /** - * Preferred constructor performs necessary initialization. - * Subclassers should override this method. - */ - public WOContext (WORequest aRequest) - { - this(); - request = aRequest; - response = new WOResponse(); - } - - /** - * Simply calls the preferred constructor. - * Included for compatibility. - */ - public static WOContext contextWithRequest (WORequest aRequest) - { - String id = Integer.toString( contextCounter++ ); - WOContext result = new WOContext( aRequest ); - result.contextID = id; - return result; - } - - /** - * Returns the context id. - */ - public String contextID () - { - return contextID; - } - - /** - * Returns the sender id, or null if it doesn't exist. - */ - public String senderID () - { - return request.senderID(); - } - - /** - * Returns the element id, or null if it doesn't exist. - */ - public String elementID () - { - return elementID.toString(); - } - - /** - * Returns this context's application, or null if it doesn't exist. - */ - public WOApplication application () - { - return request.application(); - } - - /** - * Returns whether a session has been created for the associated request. - */ - public boolean hasSession () - { - return ( session != null ); - } - - /** - * Returns this context's session, creating one if it doesn't exist. - */ - public WOSession session () - { - if ( session == null ) - { - // so far we can't figure out how the direct action handler can avoid creating a session - throw new RuntimeException( "WOContext.session: Lazy instantiation not yet implemented." ); -/* - session = application().restoreSessionWithID( - request.sessionID(), this ); - if ( session == null ) - { - session = application().createSessionForRequest( request ); - session.setContext( this ); - } -*/ - } - return session; - } - - /** - * Package access only. - */ - void setSession( WOSession aSession ) - { - session = aSession; - } - - /** - * Returns this context's request. - */ - public WORequest request () - { - return request; - } - - /** - * Returns this context's response. - */ - public WOResponse response () - { - return response; - } - - /** - * Returns the current page. - */ - public WOComponent page () - { - return (WOComponent) elementStack.get( elementStack.size()-1 ); - } - - /** - * Returns the current component. - */ - public WOComponent component () - { - Object o; - Iterator i = elementStack.iterator(); - while ( i.hasNext() ) - { - o = i.next(); - if ( o instanceof WOComponent ) return (WOComponent) o; - } - return null; - } - - /** - * Returns the current component's parent. - */ - WOComponent parent () - { - Object o; - Iterator i = elementStack.iterator(); - if ( i.hasNext() ) - { - // skip current component - o = i.next(); - } - while ( i.hasNext() ) - { - o = i.next(); - if ( o instanceof WOComponent ) - { - return (WOComponent) o; - } - } - return null; - } - - /** - * Pushes an element onto the stack. - * Package access only. - */ - void pushElement( WOElement aComponent ) - { - elementStack.add( 0, aComponent ); - } - - /** - * Pops an element off the stack. - * Package access only. - */ - WOElement popElement() - { - return (WOElement) elementStack.remove(0); - } - - /** - * Returns whether the current context is in a form. - */ - public boolean isInForm () - { - return isInForm; - } - - /** - * Sets whether the current context is in a WOForm. - */ - public void setInForm (boolean inForm) - { - isInForm = inForm; - } - - /** - * Appends the specified string to the end of the element id. - * For example, if the element id is "0.1", sending "Test" - * changes the element id to "0.1.Test". - */ - public void appendElementIDComponent (String aString) - { - if ( elementID.length() > 0 ) - { - elementID.append( SEP ); - } - elementID.append( aString ); - } - - /** - * Appends a zero to the element id to represent the first - * new child component. For example, if the element id is "0.1", - * calling this changes the element id to "0.1.0". - */ - public void appendZeroElementIDComponent () - { - if ( elementID.length() > 0 ) - { - elementID.append( SEP ); - } - elementID.append( ZERO ); - } - - /** - * Increments the last component of the element id. - * For example, if the element id is "0.1", calling this - * changes the element id to "0.2". - */ - public void incrementLastElementIDComponent () - { - String last; - String id = elementID.toString(); - int index = id.lastIndexOf( SEP ); - if ( index == -1 ) - { - last = id; - } - else - { - last = id.substring( index + 1 ); - } - - deleteLastElementIDComponent(); - - try - { - appendElementIDComponent( - Integer.toString( Integer.parseInt( last ) + 1 ) ); - } - catch ( Exception exc ) - { - System.err.println( "Error parsing id: " + last ); - appendZeroElementIDComponent(); - } - //System.out.println( "WOContext: " + elementID ); - } - - /** - * Deletes the last component of the element id. - * For example, if the element id is "0.1", callling this - * changes the element id to "0". - */ - public void deleteLastElementIDComponent () - { - int index = elementID.toString().lastIndexOf( SEP ); - if ( index == -1 ) - { - elementID.setLength( 0 ); - } - else - { - elementID.setLength( index ); - } - } - - /** - * Deletes all components of the element id. - * This makes the element id an empty string. - */ - public void deleteAllElementIDComponents () - { - elementID.setLength( 0 ); - } - - /** - * Returns a URL for the named action with query parameters - * as specified by the dictionary. - */ - public String directActionURLForActionNamed ( - String anActionName, Map aQueryDict) - { - StringBuffer query = new StringBuffer(); - - try - { - String key; - Iterator i = aQueryDict.keySet().iterator(); - while ( i.hasNext() ) - { - key = i.next().toString(); - query.append( URI.encode( key, - URI.allowed_within_query ) ); - query.append( '=' ); - query.append( URI.encode( aQueryDict.get( key ).toString(), - URI.allowed_within_query ) ); - if ( i.hasNext() ) - { - query.append( '&' ); - } - } - } - catch ( IOException exc ) - { - // report error - System.err.println( - "directActionURLForActionNamed: " + anActionName + " : " + aQueryDict ); - System.err.println( exc ); - - // delete query string - query = new StringBuffer(); - } - - return urlWithRequestHandlerKey( - WOApplication.directActionRequestHandlerKey(), - anActionName, query.toString() ); - } - - /** - * Returns the complete URL for the current component action. - */ - public String componentActionURL () - { - StringBuffer buffer = new StringBuffer(); - buffer.append( request().applicationName() ); - buffer.append( '/' ); - buffer.append( WOApplication.application().componentRequestHandlerKey() ); - buffer.append( '/' ); - buffer.append( page().name() ); - buffer.append( '/' ); - buffer.append( contextID ); - buffer.append( '/' ); - buffer.append( elementID ); - return buffer.toString(); - } - - /** - * Returns a URL relative to the servlet for the specified - * request handler, action, and query string. - */ - public String urlWithRequestHandlerKey ( - String aRequestHandlerKey, String aPath, String aQueryString) - { - StringBuffer buffer = new StringBuffer(); - buffer.append( request().applicationName() ); - buffer.append( '/' ); - buffer.append( aRequestHandlerKey ); - buffer.append( '/' ); - buffer.append( aPath ); - if ( aQueryString != null && aQueryString.trim().length() > 0 ) - { - buffer.append( '?' ); - buffer.append( aQueryString ); - } - return buffer.toString(); - } - - /** - * Returns the complete URL for the specified request handler, - * path, and query string. isSecure determines the protocol: - * http or https. port is appended to the protocol, if zero, - * the port is omitted from the URL. - */ - public String completeURLWithRequestHandlerKey ( - String aRequestHandlerKey, String aPath, String aQueryString, - boolean isSecure, int port) - { - StringBuffer buffer = new StringBuffer(); - - if ( isSecure ) - { - buffer.append( HTTPS ); - } - else - { - buffer.append( HTTP ); - } - - buffer.append( request.applicationHost() ); - - if ( port != 0 && port != 80 ) - { - buffer.append( ':' ); - buffer.append( Integer.toString( port ) ); - } - - buffer.append( urlWithRequestHandlerKey( - aRequestHandlerKey, aPath, aQueryString ) ); - - return buffer.toString(); - } - - /** - * Sets whether distribution is enabled. - */ - public void setDistributionEnabled (boolean distributionEnabled) - { - isDistributionEnabled = distributionEnabled; - } - - /** - * Returns whether distribution is enabled. - */ - public boolean isDistributionEnabled () - { - return isDistributionEnabled; - } - - /** - * This method is not included in the WOContext specification. - * This implementation returns "" since only cookie ids are - * currently supported. - */ - String urlSessionPrefix () - { - return EMPTY; // "/" + sessionid; - } - - /** - * Returns the relative URL for the current page. - */ - String url () - { - throw new RuntimeException( "Not implemented yet." ); - } - - public String toString() - { - return "[WOContext@"+Integer.toHexString(System.identityHashCode(this)) - +":id=" + contextID +":page=" + page + ":component=" + component + ":element=" + elementID + "]"; - } + + private static volatile int contextCounter = 0; + private String contextID; + + /** + * Default constructor performs necessary initialization. Subclassers should + * override the static factory method below. + */ + public WOContext() { + contextID = null; + elementID = new StringBuffer(); + elementStack = new LinkedList(); + } + + /** + * Preferred constructor performs necessary initialization. Subclassers should + * override this method. + */ + public WOContext(WORequest aRequest) { + this(); + request = aRequest; + response = new WOResponse(); + } + + /** + * Simply calls the preferred constructor. Included for compatibility. + */ + public static WOContext contextWithRequest(WORequest aRequest) { + String id = Integer.toString(contextCounter++); + WOContext result = new WOContext(aRequest); + result.contextID = id; + return result; + } + + /** + * Returns the context id. + */ + public String contextID() { + return contextID; + } + + /** + * Returns the sender id, or null if it doesn't exist. + */ + public String senderID() { + return request.senderID(); + } + + /** + * Returns the element id, or null if it doesn't exist. + */ + public String elementID() { + return elementID.toString(); + } + + /** + * Returns this context's application, or null if it doesn't exist. + */ + public WOApplication application() { + return request.application(); + } + + /** + * Returns whether a session has been created for the associated request. + */ + public boolean hasSession() { + return (session != null); + } + + /** + * Returns this context's session, creating one if it doesn't exist. + */ + public WOSession session() { + if (session == null) { + // so far we can't figure out how the direct action handler can avoid creating a + // session + throw new RuntimeException("WOContext.session: Lazy instantiation not yet implemented."); + /* + * session = application().restoreSessionWithID( request.sessionID(), this ); if + * ( session == null ) { session = application().createSessionForRequest( + * request ); session.setContext( this ); } + */ + } + return session; + } + + /** + * Package access only. + */ + void setSession(WOSession aSession) { + session = aSession; + } + + /** + * Returns this context's request. + */ + public WORequest request() { + return request; + } + + /** + * Returns this context's response. + */ + public WOResponse response() { + return response; + } + + /** + * Returns the current page. + */ + public WOComponent page() { + return (WOComponent) elementStack.get(elementStack.size() - 1); + } + + /** + * Returns the current component. + */ + public WOComponent component() { + Object o; + Iterator i = elementStack.iterator(); + while (i.hasNext()) { + o = i.next(); + if (o instanceof WOComponent) + return (WOComponent) o; + } + return null; + } + + /** + * Returns the current component's parent. + */ + WOComponent parent() { + Object o; + Iterator i = elementStack.iterator(); + if (i.hasNext()) { + // skip current component + o = i.next(); + } + while (i.hasNext()) { + o = i.next(); + if (o instanceof WOComponent) { + return (WOComponent) o; + } + } + return null; + } + + /** + * Pushes an element onto the stack. Package access only. + */ + void pushElement(WOElement aComponent) { + elementStack.add(0, aComponent); + } + + /** + * Pops an element off the stack. Package access only. + */ + WOElement popElement() { + return (WOElement) elementStack.remove(0); + } + + /** + * Returns whether the current context is in a form. + */ + public boolean isInForm() { + return isInForm; + } + + /** + * Sets whether the current context is in a WOForm. + */ + public void setInForm(boolean inForm) { + isInForm = inForm; + } + + /** + * Appends the specified string to the end of the element id. For example, if + * the element id is "0.1", sending "Test" changes the element id to "0.1.Test". + */ + public void appendElementIDComponent(String aString) { + if (elementID.length() > 0) { + elementID.append(SEP); + } + elementID.append(aString); + } + + /** + * Appends a zero to the element id to represent the first new child component. + * For example, if the element id is "0.1", calling this changes the element id + * to "0.1.0". + */ + public void appendZeroElementIDComponent() { + if (elementID.length() > 0) { + elementID.append(SEP); + } + elementID.append(ZERO); + } + + /** + * Increments the last component of the element id. For example, if the element + * id is "0.1", calling this changes the element id to "0.2". + */ + public void incrementLastElementIDComponent() { + String last; + String id = elementID.toString(); + int index = id.lastIndexOf(SEP); + if (index == -1) { + last = id; + } else { + last = id.substring(index + 1); + } + + deleteLastElementIDComponent(); + + try { + appendElementIDComponent(Integer.toString(Integer.parseInt(last) + 1)); + } catch (Exception exc) { + System.err.println("Error parsing id: " + last); + appendZeroElementIDComponent(); + } + // System.out.println( "WOContext: " + elementID ); + } + + /** + * Deletes the last component of the element id. For example, if the element id + * is "0.1", callling this changes the element id to "0". + */ + public void deleteLastElementIDComponent() { + int index = elementID.toString().lastIndexOf(SEP); + if (index == -1) { + elementID.setLength(0); + } else { + elementID.setLength(index); + } + } + + /** + * Deletes all components of the element id. This makes the element id an empty + * string. + */ + public void deleteAllElementIDComponents() { + elementID.setLength(0); + } + + /** + * Returns a URL for the named action with query parameters as specified by the + * dictionary. + */ + public String directActionURLForActionNamed(String anActionName, Map aQueryDict) { + StringBuffer query = new StringBuffer(); + + try { + String key; + Iterator i = aQueryDict.keySet().iterator(); + while (i.hasNext()) { + key = i.next().toString(); + query.append(URI.encode(key, URI.allowed_within_query)); + query.append('='); + query.append(URI.encode(aQueryDict.get(key).toString(), URI.allowed_within_query)); + if (i.hasNext()) { + query.append('&'); + } + } + } catch (IOException exc) { + // report error + System.err.println("directActionURLForActionNamed: " + anActionName + " : " + aQueryDict); + System.err.println(exc); + + // delete query string + query = new StringBuffer(); + } + + return urlWithRequestHandlerKey(WOApplication.directActionRequestHandlerKey(), anActionName, query.toString()); + } + + /** + * Returns the complete URL for the current component action. + */ + public String componentActionURL() { + StringBuffer buffer = new StringBuffer(); + buffer.append(request().applicationName()); + buffer.append('/'); + buffer.append(WOApplication.application().componentRequestHandlerKey()); + buffer.append('/'); + buffer.append(page().name()); + buffer.append('/'); + buffer.append(contextID); + buffer.append('/'); + buffer.append(elementID); + return buffer.toString(); + } + + /** + * Returns a URL relative to the servlet for the specified request handler, + * action, and query string. + */ + public String urlWithRequestHandlerKey(String aRequestHandlerKey, String aPath, String aQueryString) { + StringBuffer buffer = new StringBuffer(); + buffer.append(request().applicationName()); + buffer.append('/'); + buffer.append(aRequestHandlerKey); + buffer.append('/'); + buffer.append(aPath); + if (aQueryString != null && aQueryString.trim().length() > 0) { + buffer.append('?'); + buffer.append(aQueryString); + } + return buffer.toString(); + } + + /** + * Returns the complete URL for the specified request handler, path, and query + * string. isSecure determines the protocol: http or https. port is appended to + * the protocol, if zero, the port is omitted from the URL. + */ + public String completeURLWithRequestHandlerKey(String aRequestHandlerKey, String aPath, String aQueryString, + boolean isSecure, int port) { + StringBuffer buffer = new StringBuffer(); + + if (isSecure) { + buffer.append(HTTPS); + } else { + buffer.append(HTTP); + } + + buffer.append(request.applicationHost()); + + if (port != 0 && port != 80) { + buffer.append(':'); + buffer.append(Integer.toString(port)); + } + + buffer.append(urlWithRequestHandlerKey(aRequestHandlerKey, aPath, aQueryString)); + + return buffer.toString(); + } + + /** + * Sets whether distribution is enabled. + */ + public void setDistributionEnabled(boolean distributionEnabled) { + isDistributionEnabled = distributionEnabled; + } + + /** + * Returns whether distribution is enabled. + */ + public boolean isDistributionEnabled() { + return isDistributionEnabled; + } + + /** + * This method is not included in the WOContext specification. This + * implementation returns "" since only cookie ids are currently supported. + */ + String urlSessionPrefix() { + return EMPTY; // "/" + sessionid; + } + + /** + * Returns the relative URL for the current page. + */ + String url() { + throw new RuntimeException("Not implemented yet."); + } + + public String toString() { + return "[WOContext@" + Integer.toHexString(System.identityHashCode(this)) + ":id=" + contextID + ":page=" + page + + ":component=" + component + ":element=" + elementID + "]"; + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.19 2003/08/07 00:15:15 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.19 2003/08/07 00:15:15 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.18 2003/02/21 16:40:23 mpowers - * Now reading port and smtp host from system properties. - * Implemented WOApplication.main. + * Revision 1.18 2003/02/21 16:40:23 mpowers Now reading port and smtp host from + * system properties. Implemented WOApplication.main. * - * Revision 1.17 2003/01/24 20:12:54 mpowers - * Better parent determination. + * Revision 1.17 2003/01/24 20:12:54 mpowers Better parent determination. * - * Revision 1.16 2003/01/21 22:27:02 mpowers - * Corrected context id usage. + * Revision 1.16 2003/01/21 22:27:02 mpowers Corrected context id usage. * Implemented backtracking. * - * Revision 1.15 2003/01/17 20:58:19 mpowers - * Fixed up WOHyperlink. + * Revision 1.15 2003/01/17 20:58:19 mpowers Fixed up WOHyperlink. * - * Revision 1.14 2003/01/17 20:34:57 mpowers - * Better handling for components and parents in the context's element stack. + * Revision 1.14 2003/01/17 20:34:57 mpowers Better handling for components and + * parents in the context's element stack. * - * Revision 1.13 2003/01/14 19:48:36 mpowers - * - fixes to property synchronization - * - forms now pass takeValuesFromRequest to children - * - fixes to action invocation - * - now supporting multipleSubmit in forms + * Revision 1.13 2003/01/14 19:48:36 mpowers - fixes to property synchronization + * - forms now pass takeValuesFromRequest to children - fixes to action + * invocation - now supporting multipleSubmit in forms * - * Revision 1.12 2003/01/13 22:24:44 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.12 2003/01/13 22:24:44 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.11 2003/01/10 20:17:41 mpowers - * Component action urls are now working. + * Revision 1.11 2003/01/10 20:17:41 mpowers Component action urls are now + * working. * - * Revision 1.8 2003/01/09 21:16:48 mpowers - * Bringing request-response cycle more into conformance. + * Revision 1.8 2003/01/09 21:16:48 mpowers Bringing request-response cycle more + * into conformance. * - * Revision 1.7 2003/01/09 16:13:59 mpowers - * Implemented WOComponentRequestHandler: - * Bringing the request-response cycle more into conformance. + * Revision 1.7 2003/01/09 16:13:59 mpowers Implemented + * WOComponentRequestHandler: Bringing the request-response cycle more into + * conformance. * - * Revision 1.4 2002/12/20 22:56:33 mpowers - * Reimplemented the template parsing again. - * Nested components are now correctly parsed. - * ElementID numbering is now working. + * Revision 1.4 2002/12/20 22:56:33 mpowers Reimplemented the template parsing + * again. Nested components are now correctly parsed. ElementID numbering is now + * working. * - * Revision 1.3 2002/12/18 14:12:38 mpowers - * Support for differentiated request handlers. - * Support url generation for WOContext and WORequest. + * Revision 1.3 2002/12/18 14:12:38 mpowers Support for differentiated request + * handlers. Support url generation for WOContext and WORequest. * - * Revision 1.2 2002/12/17 14:57:42 mpowers - * Minor corrections to WORequests's parsing, and updated javadocs. + * Revision 1.2 2002/12/17 14:57:42 mpowers Minor corrections to WORequests's + * parsing, and updated javadocs. * - * Revision 1.1.1.1 2000/12/21 15:53:04 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:04 mpowers Contributing wotonomy. * - * Revision 1.3 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.3 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java index 78427f0..4c5f498 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java @@ -21,183 +21,159 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDate; /** -* A pure java implementation of WOCookie that extends -* javax.servlet.httpd.Cookie for greater compatibility. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOCookie - extends javax.servlet.http.Cookie -{ - /** - * Default constructor. - */ - public WOCookie () - { - super( "", "" ); - } - - /** - * Constructs a cookie with the specified name and value. - */ - public WOCookie( String aName, String aValue ) - { - super( aName, aValue ); - } - - /** - * Constructs a cookie with the specified name and value. - * Also sets the path to the current application's path. - */ - public static WOCookie cookieWithName (String aName, String aValue) - { - WOCookie result = new WOCookie( aName, aValue ); - //TODO: Set the path to the current application's path. - return result; - } - - /** - * Constructs a cookie with the specified attributes. - */ - public static WOCookie cookieWithName (String aName, String aValue, - String aPath, String aDomain, NSDate expirationDate, boolean secure) - { - WOCookie result = new WOCookie( aName, aValue ); - result.setPath( aPath ); - result.setDomain( aDomain ); - result.setExpires( expirationDate ); - result.setSecure( secure ); + * A pure java implementation of WOCookie that extends + * javax.servlet.httpd.Cookie for greater compatibility. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOCookie extends javax.servlet.http.Cookie { + /** + * Default constructor. + */ + public WOCookie() { + super("", ""); + } + + /** + * Constructs a cookie with the specified name and value. + */ + public WOCookie(String aName, String aValue) { + super(aName, aValue); + } + + /** + * Constructs a cookie with the specified name and value. Also sets the path to + * the current application's path. + */ + public static WOCookie cookieWithName(String aName, String aValue) { + WOCookie result = new WOCookie(aName, aValue); + // TODO: Set the path to the current application's path. + return result; + } + + /** + * Constructs a cookie with the specified attributes. + */ + public static WOCookie cookieWithName(String aName, String aValue, String aPath, String aDomain, + NSDate expirationDate, boolean secure) { + WOCookie result = new WOCookie(aName, aValue); + result.setPath(aPath); + result.setDomain(aDomain); + result.setExpires(expirationDate); + result.setSecure(secure); return result; } - - /** - * Returns the name of the cookie. - */ - public String name () - { - return this.getName(); - } - - /** - * Sets the name of the cookie. - */ - public void setName (String aString) - { - // super.setName( aString ); - throw new RuntimeException( "Not yet implemented." ); - } - - /** - * Returns the value of the cookie. - */ - public String value () - { - return this.getValue(); - } - - /** - * Sets the value of the cookie. - */ - public void setValue (String aString) - { - super.setValue( aString ); - } - - /** - * Gets the domain of the cookie. - */ - public String domain () - { - return this.getDomain(); - } - - /** - * Sets the domain of the cookie. - */ - public void setDomain (String aString) - { - super.setDomain( aString ); - } - - /** - * Gets the path of the cookie. - */ - public String path () - { - return this.getPath(); - } - - /** - * Sets the path of the cookie. - */ - public void setPath (String aString) - { - super.setPath( aString ); - } - - /** - * Gets the expiration date of the cookie. - * If in the past, the cookie will persist until browser shutdown. - */ - public NSDate expires () - { - return new NSDate( this.getMaxAge() ); - } - - /** - * Sets the expiration date of the cookie. - */ - public void setExpires (NSDate aDate) - { - this.setMaxAge( (int) aDate.timeIntervalSinceNow() ); - } - - /** - * Returns whether the cookie will only be sent over a secure protocol. - */ - public boolean isSecure () - { - return this.getSecure(); - } - - /** - * Sets whether the cookie will only be sent over a secure protocol. - */ - public void setIsSecure (boolean isSecure) - { - this.setSecure( isSecure ); - } - - /** - * Returns the string as it appears in the HTTP header of the response. - * This would normally be called by WOResponse, but is handled automatically - * by the servlet implementation. - */ - public String headerString () - { - new RuntimeException( "Not implemented yet." ); - return null; - } + + /** + * Returns the name of the cookie. + */ + public String name() { + return this.getName(); + } + + /** + * Sets the name of the cookie. + */ + public void setName(String aString) { + // super.setName( aString ); + throw new RuntimeException("Not yet implemented."); + } + + /** + * Returns the value of the cookie. + */ + public String value() { + return this.getValue(); + } + + /** + * Sets the value of the cookie. + */ + public void setValue(String aString) { + super.setValue(aString); + } + + /** + * Gets the domain of the cookie. + */ + public String domain() { + return this.getDomain(); + } + + /** + * Sets the domain of the cookie. + */ + public void setDomain(String aString) { + super.setDomain(aString); + } + + /** + * Gets the path of the cookie. + */ + public String path() { + return this.getPath(); + } + + /** + * Sets the path of the cookie. + */ + public void setPath(String aString) { + super.setPath(aString); + } + + /** + * Gets the expiration date of the cookie. If in the past, the cookie will + * persist until browser shutdown. + */ + public NSDate expires() { + return new NSDate(this.getMaxAge()); + } + + /** + * Sets the expiration date of the cookie. + */ + public void setExpires(NSDate aDate) { + this.setMaxAge((int) aDate.timeIntervalSinceNow()); + } + + /** + * Returns whether the cookie will only be sent over a secure protocol. + */ + public boolean isSecure() { + return this.getSecure(); + } + + /** + * Sets whether the cookie will only be sent over a secure protocol. + */ + public void setIsSecure(boolean isSecure) { + this.setSecure(isSecure); + } + + /** + * Returns the string as it appears in the HTTP header of the response. This + * would normally be called by WOResponse, but is handled automatically by the + * servlet implementation. + */ + public String headerString() { + new RuntimeException("Not implemented yet."); + return null; + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * 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:53:04 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:04 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:50 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:50 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java index 09d0131..08b591f 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java @@ -29,289 +29,241 @@ import net.wotonomy.foundation.internal.Introspector; import net.wotonomy.foundation.internal.ValueConverter; /** -* A pure java implementation of WODirectAction. -* This class provides a number of services to subclasses, -* including access to the query parameters, the request, -* the session, and logging and debugging facilities. -* A new instance of the class is created and then destroyed -* for every request-response cycle.

      -* -* Subclass this to implement direct actions for your -* application. Subclasses would typically override the -* constructor to initialize state based on the request, -* and then provide additional methods that would be invoked -* based on the value at the end of the URI.

      -* -* Example: "http://www/MyApp.woa/MyActions/search" would call -* the method named "searchAction" on the DirectAction subclass -* named "MyActions". If the subclass name is omitted, a subclass -* named "DirectAction" is assumed to exist within the application. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WODirectAction -{ + * A pure java implementation of WODirectAction. This class provides a number of + * services to subclasses, including access to the query parameters, the + * request, the session, and logging and debugging facilities. A new instance of + * the class is created and then destroyed for every request-response cycle.
      + *
      + * + * Subclass this to implement direct actions for your application. Subclasses + * would typically override the constructor to initialize state based on the + * request, and then provide additional methods that would be invoked based on + * the value at the end of the URI.
      + *
      + * + * Example: "http://www/MyApp.woa/MyActions/search" would call the method named + * "searchAction" on the DirectAction subclass named "MyActions". If the + * subclass name is omitted, a subclass named "DirectAction" is assumed to exist + * within the application. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WODirectAction { private WORequest request; - WOContext context; + WOContext context; /** - * Default constructor. This is called implicitly by - * subclasses in all cases. Package access only. - */ - WODirectAction () - { - - } + * Default constructor. This is called implicitly by subclasses in all cases. + * Package access only. + */ + WODirectAction() { + + } /** - * Request constructor. This is the constructor used - * to create an action in response to a user request. - * Override to perform any necessary initialization in - * your subclass. - */ - public WODirectAction (WORequest aRequest) - { - this(); - request = aRequest; - } + * Request constructor. This is the constructor used to create an action in + * response to a user request. Override to perform any necessary initialization + * in your subclass. + */ + public WODirectAction(WORequest aRequest) { + this(); + request = aRequest; + } - /** - * Returns the response from the component named "Main". - */ - public WOActionResults defaultAction() - { - return pageWithName("Main").generateResponse(); - } - /** - * Returns the WORequest object for the current request. - */ - public WORequest request () - { - return request; - } + * Returns the response from the component named "Main". + */ + public WOActionResults defaultAction() { + return pageWithName("Main").generateResponse(); + } /** - * Returns the existing session, or null if no session exists. - */ - public WOSession existingSession () - { - //FIXME: this is incorrect - HttpSession session = request.servletRequest().getSession(); - if ( session == null ) return null; - WOSession wosession = new WOSession(); - wosession.setServletSession( session ); - return wosession; - } + * Returns the WORequest object for the current request. + */ + public WORequest request() { + return request; + } /** - * Returns the existing session if it exists. If no session - * exists, returns a newly created session. Note that if the - * user client does not support session ids/cookies, this will - * create a new session with each request. - */ - public WOSession session () - { - //FIXME: this is incorrect - WOSession wosession = new WOSession(); - wosession.setServletSession( - request.servletRequest().getSession( true ) ); - return wosession; - } + * Returns the existing session, or null if no session exists. + */ + public WOSession existingSession() { + // FIXME: this is incorrect + HttpSession session = request.servletRequest().getSession(); + if (session == null) + return null; + WOSession wosession = new WOSession(); + wosession.setServletSession(session); + return wosession; + } /** - * Returns the named WOComponent. - */ - public WOComponent pageWithName (String aString) - { - return request.application().pageWithName( aString, context ); - } + * Returns the existing session if it exists. If no session exists, returns a + * newly created session. Note that if the user client does not support session + * ids/cookies, this will create a new session with each request. + */ + public WOSession session() { + // FIXME: this is incorrect + WOSession wosession = new WOSession(); + wosession.setServletSession(request.servletRequest().getSession(true)); + return wosession; + } /** - * Appends "Action" to the specified string and tries to invoke - * method with that name and no arguments. Returns null if - * the method does not exist. If anAction is null, "defaultAction" - * will be assumed. - */ - public WOActionResults performActionNamed (String anAction) - { - if ( anAction == null ) anAction = "default"; - try - { - NSSelector sel = new NSSelector( anAction+"Action" ); - return (WOActionResults) sel.invoke( this ); - } - catch ( NoSuchMethodException exc ) - { + * Returns the named WOComponent. + */ + public WOComponent pageWithName(String aString) { + return request.application().pageWithName(aString, context); + } + + /** + * Appends "Action" to the specified string and tries to invoke method with that + * name and no arguments. Returns null if the method does not exist. If anAction + * is null, "defaultAction" will be assumed. + */ + public WOActionResults performActionNamed(String anAction) { + if (anAction == null) + anAction = "default"; + try { + NSSelector sel = new NSSelector(anAction + "Action"); + return (WOActionResults) sel.invoke(this); + } catch (NoSuchMethodException exc) { // returns below - } - catch ( InvocationTargetException exc ) - { + } catch (InvocationTargetException exc) { Throwable e = exc.getTargetException(); - exc.printStackTrace(); - throw new RuntimeException( e.toString() ); - } - catch ( Exception exc ) - { - exc.printStackTrace(); - throw new RuntimeException( exc.toString() ); + exc.printStackTrace(); + throw new RuntimeException(e.toString()); + } catch (Exception exc) { + exc.printStackTrace(); + throw new RuntimeException(exc.toString()); } - WOResponse error = new WOResponse(); - error.setStatus( 404 ); // not found - error.appendContentString( - "Could not find method named \"" + anAction + "Action\"." ); - return error; - } + WOResponse error = new WOResponse(); + error.setStatus(404); // not found + error.appendContentString("Could not find method named \"" + anAction + "Action\"."); + return error; + } /** - * Assigns the arrays of form values for the specified keys - * to properties on this object with matching names whose type - * is NSArray or is convertable from a Collection. - */ - public void takeFormValueArraysForKeyArray (NSArray anArray) - { - if ( anArray == null ) return; - - Method m; - Object key; - Object value; - java.util.Enumeration e = anArray.objectEnumerator(); - while ( e.hasMoreElements() ) - { - key = e.nextElement(); - value = request.formValuesForKey( key.toString() ); - try - { - // obtain setter method for this class - m = Introspector.getPropertyWriteMethod( - this.getClass(), key.toString(), - new Class[] { Introspector.WILD } ); - if ( m != null ) - { - // if value isn't null, try to convert it to type - if ( value != null ) - { - Class[] paramTypes = m.getParameterTypes(); - if ( ! paramTypes[0].isAssignableFrom( - value.getClass() ) ) - { - //TODO: find a constructor whose parameter - // is assignable from value.getClass() - // and instantiate it in the place of value. + * Assigns the arrays of form values for the specified keys to properties on + * this object with matching names whose type is NSArray or is convertable from + * a Collection. + */ + public void takeFormValueArraysForKeyArray(NSArray anArray) { + if (anArray == null) + return; + + Method m; + Object key; + Object value; + java.util.Enumeration e = anArray.objectEnumerator(); + while (e.hasMoreElements()) { + key = e.nextElement(); + value = request.formValuesForKey(key.toString()); + try { + // obtain setter method for this class + m = Introspector.getPropertyWriteMethod(this.getClass(), key.toString(), + new Class[] { Introspector.WILD }); + if (m != null) { + // if value isn't null, try to convert it to type + if (value != null) { + Class[] paramTypes = m.getParameterTypes(); + if (!paramTypes[0].isAssignableFrom(value.getClass())) { + // TODO: find a constructor whose parameter + // is assignable from value.getClass() + // and instantiate it in the place of value. } - } - // set property to value - m.invoke( this, new Object[] { value } ); - } - } - catch ( Exception exc ) - { - // show error and continue - debugString( "WODirectAction.takeFormValuesForKeyArray: " + exc ); - } + } + // set property to value + m.invoke(this, new Object[] { value }); + } + } catch (Exception exc) { + // show error and continue + debugString("WODirectAction.takeFormValuesForKeyArray: " + exc); + } } - - } + + } /** - * Assigns the form values for the specified keys to properties - * on this object with matching names. - */ - public void takeFormValuesForKeyArray (NSArray anArray) - { - if ( anArray == null ) return; - - Method m; - Object key; - Object value; - java.util.Enumeration e = anArray.objectEnumerator(); - while ( e.hasMoreElements() ) - { - key = e.nextElement(); - value = request.formValueForKey( key.toString() ); - try - { - // obtain setter method for this class - m = Introspector.getPropertyWriteMethod( - this.getClass(), key.toString(), - new Class[] { Introspector.WILD } ); - if ( m != null ) - { - // if value isn't null, try to convert it to type - if ( value != null ) - { - Class[] paramTypes = m.getParameterTypes(); - Object convertedValue = - ValueConverter.convertObjectToClass( - value, paramTypes[0] ); - if ( convertedValue != null ) - { - value = convertedValue; - } - } - // set property to value - m.invoke( this, new Object[] { value } ); - } - } - catch ( Exception exc ) - { - // show error and continue - debugString( "WODirectAction.takeFormValuesForKeyArray: " + exc ); - } + * Assigns the form values for the specified keys to properties on this object + * with matching names. + */ + public void takeFormValuesForKeyArray(NSArray anArray) { + if (anArray == null) + return; + + Method m; + Object key; + Object value; + java.util.Enumeration e = anArray.objectEnumerator(); + while (e.hasMoreElements()) { + key = e.nextElement(); + value = request.formValueForKey(key.toString()); + try { + // obtain setter method for this class + m = Introspector.getPropertyWriteMethod(this.getClass(), key.toString(), + new Class[] { Introspector.WILD }); + if (m != null) { + // if value isn't null, try to convert it to type + if (value != null) { + Class[] paramTypes = m.getParameterTypes(); + Object convertedValue = ValueConverter.convertObjectToClass(value, paramTypes[0]); + if (convertedValue != null) { + value = convertedValue; + } + } + // set property to value + m.invoke(this, new Object[] { value }); + } + } catch (Exception exc) { + // show error and continue + debugString("WODirectAction.takeFormValuesForKeyArray: " + exc); + } } - - } + + } /** - * Writes a message to the standard error stream. - */ - public static void logString (String aString) - { - System.err.println( aString ); - } + * Writes a message to the standard error stream. + */ + public static void logString(String aString) { + System.err.println(aString); + } /** - * Writes a message to the standard error stream - * if debugging is activated. - */ - public static void debugString (String aString) - { - // TODO: Check to see if debugging is enabled. - System.err.println( aString ); - } + * Writes a message to the standard error stream if debugging is activated. + */ + public static void debugString(String aString) { + // TODO: Check to see if debugging is enabled. + System.err.println(aString); + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/01/13 22:24:51 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.5 2003/01/13 22:24:51 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.4 2003/01/09 21:16:48 mpowers - * Bringing request-response cycle more into conformance. + * Revision 1.4 2003/01/09 21:16:48 mpowers Bringing request-response cycle more + * into conformance. * - * Revision 1.3 2003/01/07 15:58:11 mpowers - * Implementing the request-response cycle. - * WOSession refactored to support distribution. + * Revision 1.3 2003/01/07 15:58:11 mpowers Implementing the request-response + * cycle. WOSession refactored to support distribution. * - * Revision 1.2 2002/12/17 14:57:43 mpowers - * Minor corrections to WORequests's parsing, and updated javadocs. + * Revision 1.2 2002/12/17 14:57:43 mpowers Minor corrections to WORequests's + * parsing, and updated javadocs. * - * Revision 1.1.1.1 2000/12/21 15:53:18 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:18 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:50 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:50 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java index eac826b..228f387 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java @@ -21,201 +21,161 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSArray; /** -* An implementation of WORequestHandler that dispatches -* DirectActions.

      -* -* See WODirectAction for the rules for parsing a request. -* In short, className defaults to "DirectAction", and the -* action defaults to "default". The action class is expected -* to have a constructor that takes a WORequest parameter. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WODirectActionRequestHandler - extends WORequestHandler -{ - public WOResponse handleRequest (WORequest aRequest) - { - WOResponse response = null; - - // no concurrent access is allowed to a user's session: - // a user/browser/machine with multiple requests will have to wait. - // NOTE: this forces a session creation for any direct action (!) - synchronized ( aRequest.request.getSession() ) - { - WOApplication application = aRequest.application(); - WOContext context = WOContext.contextWithRequest( aRequest ); - - String className = "DirectAction"; - String actionName = "default"; - NSArray path = aRequest.requestHandlerPathArray(); - if ( path.count() > 0 ) - { - className = path.objectAtIndex( 0 ).toString(); - if ( path.count() > 1 ) - { - actionName = path.objectAtIndex( path.count() - 1 ).toString(); - } - if ( path.count() > 2 ) - { - for ( int i = 1; i < path.count()-1; i++ ) - { - className = className + "." + - path.objectAtIndex( i ).toString(); - } - } - } - - //FIXME: sessions are supposed to be lazily created for direct actions - - WOSession session = null; - - String sessionID = aRequest.sessionID(); - if ( sessionID != null ) - { - session = application.restoreSessionWithID( sessionID, context ); - } - - if ( session == null ) - { - session = application.createSessionForRequest( aRequest ); - if ( session == null ) - { - response = application.handleSessionCreationErrorInContext( context ); - } - else - { - session.setContext( context ); - } - } - - context.setSession( session ); - - application.awake(); - session.awake(); - - try - { - - if ( response == null ) - { - Class c = null; - c = application.getLocalClass( className ); - if ( ( c == null ) && ( path.count() == 1 ) ) - { - actionName = className; - className = "DirectAction"; - c = application.getLocalClass( className ); - } - if ( c == null ) - { - throw new RuntimeException( - "Could not find class named \"" + className - + "\": " ); - } - java.lang.reflect.Constructor ctor = - c.getConstructor( new Class[] { WORequest.class } ); - - WODirectAction action = (WODirectAction) - ctor.newInstance( new Object[] { aRequest } ); - action.context = context; //HACK: how else can action call pageWithName? - - WOActionResults result = action.performActionNamed( actionName ); - if ( result instanceof WOComponent ) - { - //HACK: I'm not sure where this should have gone: seems hackish here. - context.pushElement( (WOComponent) result ); - ((WOComponent)result).ensureAwakeInContext( context ); - //context.popElement(); - } - response = result.generateResponse(); // calls session.savePage (?) - if ( result instanceof WOComponent ) - { - //HACK: I'm not sure where this should have gone: seems hackish here. - ((WOComponent)result).sleep(); - } - } - } - catch ( NoSuchMethodException exc ) - { - WOResponse error = new WOResponse(); - exc.printStackTrace(); - error.setStatus( 404 ); // not found - error.appendContentString( - "Could not find request constructor for class named \"" - + className + "\": " ); - response = error; - } - catch ( Exception exc ) - { - WOResponse error = new WOResponse(); - error.setStatus( 500 ); // error - if ( exc.getMessage() != null ) - { - error.appendContentString( exc.getMessage() ); - exc.printStackTrace(); - } - else - { - error.appendContentString( exc.toString() ); - exc.printStackTrace(); - } - response = error; - } - - session.sleep(); - session.setContext( null ); - application.saveSessionForContext( context ); - application.sleep(); - } - return response; - } - + * An implementation of WORequestHandler that dispatches DirectActions.
      + *
      + * + * See WODirectAction for the rules for parsing a request. In short, className + * defaults to "DirectAction", and the action defaults to "default". The action + * class is expected to have a constructor that takes a WORequest parameter. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WODirectActionRequestHandler extends WORequestHandler { + public WOResponse handleRequest(WORequest aRequest) { + WOResponse response = null; + + // no concurrent access is allowed to a user's session: + // a user/browser/machine with multiple requests will have to wait. + // NOTE: this forces a session creation for any direct action (!) + synchronized (aRequest.request.getSession()) { + WOApplication application = aRequest.application(); + WOContext context = WOContext.contextWithRequest(aRequest); + + String className = "DirectAction"; + String actionName = "default"; + NSArray path = aRequest.requestHandlerPathArray(); + if (path.count() > 0) { + className = path.objectAtIndex(0).toString(); + if (path.count() > 1) { + actionName = path.objectAtIndex(path.count() - 1).toString(); + } + if (path.count() > 2) { + for (int i = 1; i < path.count() - 1; i++) { + className = className + "." + path.objectAtIndex(i).toString(); + } + } + } + + // FIXME: sessions are supposed to be lazily created for direct actions + + WOSession session = null; + + String sessionID = aRequest.sessionID(); + if (sessionID != null) { + session = application.restoreSessionWithID(sessionID, context); + } + + if (session == null) { + session = application.createSessionForRequest(aRequest); + if (session == null) { + response = application.handleSessionCreationErrorInContext(context); + } else { + session.setContext(context); + } + } + + context.setSession(session); + + application.awake(); + session.awake(); + + try { + + if (response == null) { + Class c = null; + c = application.getLocalClass(className); + if ((c == null) && (path.count() == 1)) { + actionName = className; + className = "DirectAction"; + c = application.getLocalClass(className); + } + if (c == null) { + throw new RuntimeException("Could not find class named \"" + className + "\": "); + } + java.lang.reflect.Constructor ctor = c.getConstructor(new Class[] { WORequest.class }); + + WODirectAction action = (WODirectAction) ctor.newInstance(new Object[] { aRequest }); + action.context = context; // HACK: how else can action call pageWithName? + + WOActionResults result = action.performActionNamed(actionName); + if (result instanceof WOComponent) { + // HACK: I'm not sure where this should have gone: seems hackish here. + context.pushElement((WOComponent) result); + ((WOComponent) result).ensureAwakeInContext(context); + // context.popElement(); + } + response = result.generateResponse(); // calls session.savePage (?) + if (result instanceof WOComponent) { + // HACK: I'm not sure where this should have gone: seems hackish here. + ((WOComponent) result).sleep(); + } + } + } catch (NoSuchMethodException exc) { + WOResponse error = new WOResponse(); + exc.printStackTrace(); + error.setStatus(404); // not found + error.appendContentString("Could not find request constructor for class named \"" + className + "\": "); + response = error; + } catch (Exception exc) { + WOResponse error = new WOResponse(); + error.setStatus(500); // error + if (exc.getMessage() != null) { + error.appendContentString(exc.getMessage()); + exc.printStackTrace(); + } else { + error.appendContentString(exc.toString()); + exc.printStackTrace(); + } + response = error; + } + + session.sleep(); + session.setContext(null); + application.saveSessionForContext(context); + application.sleep(); + } + return response; + } + } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.10 2003/03/28 17:26:18 mpowers - * Implemented package support: Applications can now live in packages. - * Better support for locating package local classes. + * Revision 1.10 2003/03/28 17:26:18 mpowers Implemented package support: + * Applications can now live in packages. Better support for locating package + * local classes. * - * Revision 1.9 2003/01/19 22:33:26 mpowers - * Fixed problems with classpath and dynamic class loading. - * Dynamic elements now pass on ensureAwakeInContext. + * Revision 1.9 2003/01/19 22:33:26 mpowers Fixed problems with classpath and + * dynamic class loading. Dynamic elements now pass on ensureAwakeInContext. * Parser how handles tags. * - * Revision 1.8 2003/01/17 20:34:57 mpowers - * Better handling for components and parents in the context's element stack. + * Revision 1.8 2003/01/17 20:34:57 mpowers Better handling for components and + * parents in the context's element stack. * - * Revision 1.7 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.7 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.5 2003/01/13 22:25:00 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.5 2003/01/13 22:25:00 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.4 2003/01/09 21:16:49 mpowers - * Bringing request-response cycle more into conformance. + * Revision 1.4 2003/01/09 21:16:49 mpowers Bringing request-response cycle more + * into conformance. * - * Revision 1.2 2002/12/17 14:57:44 mpowers - * Minor corrections to WORequests's parsing, and updated javadocs. + * Revision 1.2 2002/12/17 14:57:44 mpowers Minor corrections to WORequests's + * parsing, and updated javadocs. * - * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:50 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:50 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java index bda1dd5..0bdefdf 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java @@ -49,2407 +49,1950 @@ import net.wotonomy.foundation.internal.Duplicator; import net.wotonomy.foundation.internal.WotonomyException; /** -* WODisplayGroup manages a set of objects, -* allowing them to be sorted, batched, and filtered. -* WODisplay also acts as a bridge to the wotonomy's -* control package, including WODisplayGroup and -* EOEditingContext. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WODisplayGroup extends Observable - implements EOObserving, EOEditingContext.Editor, - java.io.Serializable -{ - /** - * Notification sent when the display group is about to fetch. - */ - public static final String DisplayGroupWillFetchNotification - = "DisplayGroupWillFetchNotification"; - - private static boolean - globalDefaultForValidatesChangesImmediately = true; - private static String - globalDefaultStringMatchFormat = "caseInsensitiveLike"; - private static String - globalDefaultStringMatchOperator = "%@*"; - - protected NSMutableArray allObjects; - protected NSArray allObjectsProxy; - protected NSMutableArray displayedObjects; - protected NSArray displayedObjectsProxy; - protected NSMutableArray selectedObjects; - protected NSArray selectedObjectsProxy; - protected NSMutableArray selectedIndexes; - - private String defaultStringMatchOperator; - private String defaultStringMatchFormat; - - private boolean validatesChangesImmediately; - private Object delegate; - private EODataSource dataSource; - private EOQualifier qualifier; - private NSMutableArray sortOrderings; - private NSArray sortOrderingsProxy; - - private NSArray localKeys; - private NSDictionary insertedObjectDefaultValues; - private boolean fetchesOnLoad; - private boolean selectsFirstObjectAfterFetch; - private boolean usesOptimisticRefresh; - private boolean inQueryMode; - - // change detection: package access for helper classes - boolean selectionChanged; - int updatedObjectIndex; - - // batching - private int batchIndex; - private int batchSize; - - // this property is not in the spec - private boolean compareByReference = false; - - private EOObserving lastGroupObserver; - - /** - * Creates a new display group. - */ - public WODisplayGroup() - { - validatesChangesImmediately = - globalDefaultForValidatesChangesImmediately(); - defaultStringMatchOperator = - globalDefaultStringMatchFormat(); - defaultStringMatchFormat = - globalDefaultStringMatchOperator(); - - allObjects = new ObservableArray( this ); - allObjectsProxy = NSArray.arrayBackedByList( allObjects ); - displayedObjects = new NSMutableArray(); - displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects ); - selectedObjects = new NSMutableArray(); - selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects ); - sortOrderings = new NSMutableArray(); - sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings ); - selectedIndexes = new NSMutableArray(); - - delegate = null; - dataSource = null; - qualifier = null; - - localKeys = new NSArray(); // not implemented - insertedObjectDefaultValues = new NSDictionary(); - fetchesOnLoad = false; // not implemented - selectsFirstObjectAfterFetch = false; - usesOptimisticRefresh = false; - inQueryMode = false; // not implemented - - selectionChanged = false; - updatedObjectIndex = -1; - - batchIndex = 0; - batchSize = 0; - } - - - - // specify optional data source - - /** - * Sets the data source that will be used by - * this display group. - */ - public void setDataSource ( EODataSource aDataSource ) - { - if ( ( dataSource != null ) - && ( dataSource.editingContext() != null ) ) - { - // un-register for notifications from existing parent store - NSNotificationCenter.defaultCenter().removeObserver( - this, null, dataSource.editingContext() ); - dataSource.editingContext().removeEditor( this ); - if ( dataSource.editingContext().messageHandler() == this ) - { - dataSource.editingContext().setMessageHandler( null ); - } - - } - - dataSource = aDataSource; - - if ( ( dataSource != null ) - && ( dataSource.editingContext() != null ) ) - { - // register for notifications from parent store - NSNotificationCenter.defaultCenter().addObserver( - this, new NSSelector( "objectsInvalidatedInEditingContext", - new Class[] { NSNotification.class } ), - null, dataSource.editingContext() ); - - // add ourselves as editor - dataSource.editingContext().addEditor( this ); - - // add ourselves as message handler if no such handler exists - if ( dataSource.editingContext().messageHandler() == null ) - { - dataSource.editingContext().setMessageHandler( this ); - } - } - } - - /** - * Returns the current data source backing this display group, - * or null if no dataSource is currently used. - */ - public EODataSource dataSource() - { - return dataSource; - } - - /** - * Returns the key by which this display group is bound a master - * display group, or null if this is not a detail display group. - */ - public String detailKey() - { - if ( dataSource instanceof PropertyDataSource ) - { - return ((PropertyDataSource)dataSource).key(); - } - return null; - } - - /** - * Sets the key by which this display group is bound a master - * display group. Does nothing if this is not a detail display group. - */ - public void setDetailKey( String aKey ) - { - if ( dataSource instanceof PropertyDataSource ) - { - ((PropertyDataSource)dataSource).setKey( aKey ); - } - } - - /** - * Returns whether the data source is a detail data source, - * suggesting that this is a detail display group. - */ - public boolean hasDetailDataSource() - { - return ( dataSource instanceof PropertyDataSource ); - } - - /** - * Returns the selected object in the master display group, - * or null if this is not a detail display group. - */ - public Object masterObject() - { - if ( dataSource instanceof PropertyDataSource ) - { - return ((PropertyDataSource)dataSource).source(); - } - return null; - } - - /** - * Sets the master object in the detail data source. - * Does nothing if there is no detail data source. - */ - public void setMasterObject( Object anObject ) - { - if ( dataSource instanceof PropertyDataSource ) - { - ((PropertyDataSource)dataSource).setSource( anObject ); - } - } - - // specify optional delegate - - /** - * Sets the display group delegate that - * will be used by this display group. - */ - public void setDelegate ( Object aDelegate ) - { - delegate = aDelegate; - } - - /** - * Returns the current delegate for this display group, - * or null if no delegate is currently set. - */ - public Object delegate() - { - return delegate; - } - - - - // display group configuration - - /** - * Returns the current string matching format. - * If not set, defaults to "%@*". - */ - public String defaultStringMatchFormat() - { - return defaultStringMatchFormat; - } - - /** - * Returns the current string matching operator. - * If not set, defaults to "caseInsensitiveLike". - */ - public String defaultStringMatchOperator() - { - return defaultStringMatchOperator; - } - - /** - * Sets the display group and associations to edit a - * "query by example" query object. This method is - * used for target/action connections. - */ - public void enterQueryMode ( Object aSender ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether this display group should immediate - * fetch when loaded. - */ - public boolean fetchesOnLoad() - { - return fetchesOnLoad; - } - - /** - * Returns whether this display group is in "query by - * example" mode. - */ - public boolean inQueryMode() - { - return inQueryMode; - } - - /** - * Returns a Map of default values that are applied - * to new objects that are inserted into the list. - */ - public NSDictionary insertedObjectDefaultValues() - { - return insertedObjectDefaultValues; - } - - /** - * Returns the keys that were declared when read from - * an external resource file. - */ - public NSArray localKeys() - { - return localKeys; - } - - /** - * Sets whether this display group will select the - * first object in the list after a fetch. - */ - public boolean selectsFirstObjectAfterFetch() - { - return selectsFirstObjectAfterFetch; - } - - /** - * Sets the default string matching format that - * will be used by this display group. - */ - public void setDefaultStringMatchFormat ( String aFormat ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the default string matching operator that - * will be used by this display group. - */ - public void setDefaultStringMatchOperator ( String anOperator ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets whether this display group will fetch objects - * from its data source on load. - */ - public void setFetchesOnLoad ( boolean willFetch ) - { - fetchesOnLoad = willFetch; - } - - /** - * Sets whether this display group is in "query by example" - * mode. If true, all associations will bind to a special - * "example" object. - */ - public void setInQueryMode ( boolean isInQueryMode ) - { - inQueryMode = isInQueryMode; - } - - /** - * Sets the mapping that contains the values that will - * be applied to new objects inserted into the display group. - */ - public void setInsertedObjectDefaultValues ( Map aMap ) - { - insertedObjectDefaultValues = new NSDictionary( aMap ); - } - - /** - * Sets the keys that are declared when instantiated from - * an external resource file. - */ - public void setLocalKeys ( List aKeyList ) - { - localKeys = new NSArray( (Collection) aKeyList ); - } - - /** - * Sets whether the first object in the list will be - * selected after a fetch. - */ - public void setSelectsFirstObjectAfterFetch ( - boolean selectsFirst ) - { - selectsFirstObjectAfterFetch = selectsFirst; - } - - /** - * Sets the order of the keys by which this display group - * will be ordered after a fetch or after a call to - * updateDisplayedObjects(). The elements in the display - * group will be sorted first by the first key, within - * the first key, by the second key, and so on. - */ - public void setSortOrderings ( List aList ) - { - sortOrderings.removeAllObjects(); - - Object o; - Iterator it = aList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - // handle the convenience of specifying just a key - if ( ! ( o instanceof EOSortOrdering ) ) - { - o = new EOSortOrdering( - o.toString(), EOSortOrdering.CompareAscending ); - } - sortOrderings.add( o ); - } - } - - /** - * Sets whether only changed objects are refreshed (optimistic), - * or whether all objects are refreshed (pessimistic, default). - * By default, when the display group receives notification that - * one of its objects has changed, updateDisplayedObjects is called. - */ - public void setUsesOptimisticRefresh ( boolean isOptimistic ) - { - usesOptimisticRefresh = isOptimistic; - } - - /** - * Sets whether changes made by associations are validated - * immediately, or when changes are saved. - */ - public void setValidatesChangesImmediately ( - boolean validatesImmediately ) - { - validatesChangesImmediately = validatesImmediately; - } - - /** - * Returns a read-only List of sort orderings for this display group. - */ - public NSArray sortOrderings() - { - return sortOrderingsProxy; - } - - /** - * Returns whether this display group refreshes only - * the changed objects or all objects on refresh. - */ - public boolean usesOptimisticRefresh() - { - return usesOptimisticRefresh; - } - - /** - * Returns whether this display group validates changes - * immediately. Otherwise, validation should occur when - * changes are saved. Default is the global default, - * which is initially true. - */ - public boolean validatesChangesImmediately() - { - return validatesChangesImmediately; - } - - - // qualification - - /** - * Returns a qualifier that will be applied all the objects - * in this display group to determine which objects will - * be displayed. - */ - public EOQualifier qualifier() - { - return qualifier; - } - - /** - * Returns a new qualifier built from the three query - * value maps: greater than, equal to, and less than. - */ - public EOQualifier qualifierFromQueryValues() - { - //TODO: assemble qualifier from query values - - return new EOQualifier() - { - // use inner class until we actually implement one - public EOQualifier qualifierWithBindings( - Map aMap, - boolean requireAll ) - { - return null; - } - public Throwable - validateKeysWithRootClassDescription( Class aClass ) - { - return null; - } - public boolean evaluateWithObject(Object o) - { - return false; - } - }; - } - - /** - * Calls qualifierFromQueryValues(), applies the result - * to the data source, and calls fetch(). - */ - public void qualifyDataSource() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Calls qualifierFromQueryValues(), sets the qualifier - * with setQualifier(), and calls updateDisplayedObjects(). - */ - public void qualifyDisplayGroup() - { - setQualifier( qualifierFromQueryValues() ); - updateDisplayedObjects(); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values. - */ - public NSDictionary queryBindingValues() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values. - */ - public NSMutableDictionary queryBindings() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values for a matching query. - */ - public NSMutableDictionary queryMatch() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values for a minimum value query. - */ - public NSMutableDictionary queryMin() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to binding query values for a maximum value query. - */ - public NSMutableDictionary queryMax() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to operator values. - */ - public NSMutableDictionary queryOperator() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a list containing all supported qualifier operators. - */ - public NSArray allQualifierOperators() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to operator values. - */ - public NSDictionary queryOperatorValues() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the qualifier that will be used by - * updateDisplayedObjects() to filter displayed objects. - */ - public void setQualifier ( EOQualifier aQualifier ) - { - qualifier = aQualifier; - } - - /** - * Sets the mapping that contains the mappings of keys - * to binding values. - */ - public void setQueryBindingValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to operator values. - */ - public void setQueryOperatorValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - - - // qualifier query values - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for equality. - */ - public NSDictionary equalToQueryValues() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for greater value. - */ - public NSDictionary greaterThanQueryValues() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns a Map containing the mappings of keys - * to query values that will be used to test for lesser value. - */ - public NSDictionary lessThanQueryValues() - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the Map that contains the mappings of keys - * to query values that will be used to test for equality. - */ - public void setEqualToQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to query values that will be used to test for greater value. - */ - public void setGreaterThanQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets the mapping that contains the mappings of keys - * to query values that will be used to test for lesser value. - */ - public void setLessThanQueryValues ( Map aMap ) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Deprecated: returns true. - */ - public boolean endEditing() - { - return true; - } - - - // object management - - /** - * Returns a read-only List containing all objects managed by the display group. - * This includes those objects not visible due to disqualification. - */ - public NSArray allObjects() - { //System.out.println( "avoided allocation: allObjects" ); - return allObjectsProxy; - } - - /** - * Returns the total number of batches held by this display group. - */ - public int batchCount() - { - if ( batchSize < 1 ) return 1; - int count = displayedObjects.count(); - if ( count == 0 ) return 1; - return ((count-1) / batchSize) + 1; - } - - /** - * Returns the index of the currently displayed batch. - */ - public int currentBatchIndex() - { - return batchIndex; - } - - /** - * Sets the index of the currently displayed batch. - */ - public void setCurrentBatchIndex( int aBatchIndex ) - { - batchIndex = aBatchIndex; - updateDisplayedObjects(); - } - - /** - * Sets the displayed objects to the batch containing - * the first selected object, and updates the current - * batch index, and returns null to force a page reload. - * Displays the first batch is there is no selection. - */ - public Object displayBatchContainingSelectedObject() - { - if ( batchSize < 1 ) return null; - NSArray indexes = selectionIndexes(); - if ( indexes.count() > 0 ) - { - batchIndex = - ((Number)indexes.objectAtIndex( 0 )).intValue() / batchSize; - } - else - { - batchIndex = 0; - } - updateDisplayedObjects(); - return null; - } - - /** - * Sets the displayed objects to the next batch - * and updates the current batch index, and returns null - * to force a page reload. If there is no next batch - * the first batch is displayed. - */ - public Object displayNextBatch() - { - batchIndex = (batchIndex + 1) % batchCount(); - updateDisplayedObjects(); - return null; - } - - /** - * Sets the displayed objects to the next batch - * and updates the current batch index, and returns null - * to force a page reload. If there is no previous - * batch, the last batch is displayed. - */ - public Object displayPreviousBatch() - { - batchIndex--; - if ( batchIndex < 0 ) batchIndex = batchCount() - 1; - updateDisplayedObjects(); - return null; - } - - /** - * Returns whether the displayed objects have been batched. - */ - public boolean hasMultipleBatches() - { - return batchCount() > 1; - } - - /** - * Returns the one-based index within the displayed objects list - * of the first displayed object in the current batch. - */ - public int indexOfFirstDisplayedObject() - { - if ( batchSize < 1 ) return 1; - return batchIndex * batchSize + 1; - } - - /** - * Returns the one-based index within the displayed objects list - * of the first last object in the current batch. - */ - public int indexOfLastDisplayedObject() - { - if ( batchSize < 1 ) return displayedObjects.count(); - return Math.min( - ((batchIndex+1) * batchSize), - displayedObjects.count() ); - } - - /** - * Returns the number of objects per batch. - */ - public int numberOfObjectsPerBatch() - { - return batchSize; - } - - /** - * Returns the number of objects per batch. - */ - public void setNumberOfObjectsPerBatch( int aSize ) - { - batchSize = aSize; - updateDisplayedObjects(); - } - - /** - * Clears the current selection. - * @return True is the selection was cleared, - * False if the selection could not be cleared - * @see #setSelectionIndexes - */ - public boolean clearSelection() - { - Object result = notifyDelegate( - "displayGroupShouldChangeSelection", - new Class[] { WODisplayGroup.class, List.class }, - new Object[] { this, new NSArray( selectedObjects ) } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return false; - } - - selectionChanged = true; - willChange(); - - selectedObjects.removeAllObjects(); - selectedIndexes.removeAllObjects(); - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - - return true; - } - - /** - * Convenience for binding to a component action: - * calls deleteSelection() and then displayBatchContainingSelectedObject() - * and returns null, which is a suitable result from a component action. - */ - public Object delete() - { - deleteSelection(); - displayBatchContainingSelectedObject(); - return null; - } - - /** - * Deletes the object at the specified index, - * notifying the delegate before and after the operation, - * and then updating the selection if needed. - * @return True if delete was successful, false if the - * object was not deleted. - */ - public boolean deleteObjectAtIndex ( int anIndex ) - { - Object target = displayedObjects.objectAtIndex( anIndex ); - - Object result = notifyDelegate( - "displayGroupShouldDeleteObject", - new Class[] { WODisplayGroup.class, Object.class }, - new Object[] { this, target } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return false; - } - - deleteObjectAtIndexNoNotify( anIndex ); - - if ( dataSource != null ) - { - dataSource.deleteObject( target ); - } - - notifyDelegate( - "displayGroupDidDeleteObject", - new Class[] { WODisplayGroup.class, Object.class }, - new Object[] { this, target } ); - - return true; - } - - private void deleteObjectAtIndexNoNotify ( int anIndex ) - { - Object target = displayedObjects.objectAtIndex( anIndex ); - - int i; - - // remove from selected objects if necessary - i = indexOf( selectedObjects, target ); - if ( i != NSArray.NotFound ) - { - selectionChanged = true; - willChange(); // notify before removing - selectedObjects.removeObjectAtIndex( i ); - selectedIndexes.remove( new Integer( i ) ); // comps by value - } - else // notify - no selection change needed - { - willChange(); - } - - // remove from all objects - i = indexOf( allObjects, target ); - if ( i != NSArray.NotFound ) - { - allObjects.removeObjectAtIndex( i ); - } - else // otherwise should never happen - { + * WODisplayGroup manages a set of objects, allowing them to be sorted, batched, + * and filtered. WODisplay also acts as a bridge to the wotonomy's control + * package, including WODisplayGroup and EOEditingContext. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WODisplayGroup extends Observable implements EOObserving, EOEditingContext.Editor, java.io.Serializable { + /** + * Notification sent when the display group is about to fetch. + */ + public static final String DisplayGroupWillFetchNotification = "DisplayGroupWillFetchNotification"; + + private static boolean globalDefaultForValidatesChangesImmediately = true; + private static String globalDefaultStringMatchFormat = "caseInsensitiveLike"; + private static String globalDefaultStringMatchOperator = "%@*"; + + protected NSMutableArray allObjects; + protected NSArray allObjectsProxy; + protected NSMutableArray displayedObjects; + protected NSArray displayedObjectsProxy; + protected NSMutableArray selectedObjects; + protected NSArray selectedObjectsProxy; + protected NSMutableArray selectedIndexes; + + private String defaultStringMatchOperator; + private String defaultStringMatchFormat; + + private boolean validatesChangesImmediately; + private Object delegate; + private EODataSource dataSource; + private EOQualifier qualifier; + private NSMutableArray sortOrderings; + private NSArray sortOrderingsProxy; + + private NSArray localKeys; + private NSDictionary insertedObjectDefaultValues; + private boolean fetchesOnLoad; + private boolean selectsFirstObjectAfterFetch; + private boolean usesOptimisticRefresh; + private boolean inQueryMode; + + // change detection: package access for helper classes + boolean selectionChanged; + int updatedObjectIndex; + + // batching + private int batchIndex; + private int batchSize; + + // this property is not in the spec + private boolean compareByReference = false; + + private EOObserving lastGroupObserver; + + /** + * Creates a new display group. + */ + public WODisplayGroup() { + validatesChangesImmediately = globalDefaultForValidatesChangesImmediately(); + defaultStringMatchOperator = globalDefaultStringMatchFormat(); + defaultStringMatchFormat = globalDefaultStringMatchOperator(); + + allObjects = new ObservableArray(this); + allObjectsProxy = NSArray.arrayBackedByList(allObjects); + displayedObjects = new NSMutableArray(); + displayedObjectsProxy = NSArray.arrayBackedByList(displayedObjects); + selectedObjects = new NSMutableArray(); + selectedObjectsProxy = NSArray.arrayBackedByList(selectedObjects); + sortOrderings = new NSMutableArray(); + sortOrderingsProxy = NSArray.arrayBackedByList(sortOrderings); + selectedIndexes = new NSMutableArray(); + + delegate = null; + dataSource = null; + qualifier = null; + + localKeys = new NSArray(); // not implemented + insertedObjectDefaultValues = new NSDictionary(); + fetchesOnLoad = false; // not implemented + selectsFirstObjectAfterFetch = false; + usesOptimisticRefresh = false; + inQueryMode = false; // not implemented + + selectionChanged = false; + updatedObjectIndex = -1; + + batchIndex = 0; + batchSize = 0; + } + + // specify optional data source + + /** + * Sets the data source that will be used by this display group. + */ + public void setDataSource(EODataSource aDataSource) { + if ((dataSource != null) && (dataSource.editingContext() != null)) { + // un-register for notifications from existing parent store + NSNotificationCenter.defaultCenter().removeObserver(this, null, dataSource.editingContext()); + dataSource.editingContext().removeEditor(this); + if (dataSource.editingContext().messageHandler() == this) { + dataSource.editingContext().setMessageHandler(null); + } + + } + + dataSource = aDataSource; + + if ((dataSource != null) && (dataSource.editingContext() != null)) { + // register for notifications from parent store + NSNotificationCenter.defaultCenter().addObserver(this, + new NSSelector("objectsInvalidatedInEditingContext", new Class[] { NSNotification.class }), null, + dataSource.editingContext()); + + // add ourselves as editor + dataSource.editingContext().addEditor(this); + + // add ourselves as message handler if no such handler exists + if (dataSource.editingContext().messageHandler() == null) { + dataSource.editingContext().setMessageHandler(this); + } + } + } + + /** + * Returns the current data source backing this display group, or null if no + * dataSource is currently used. + */ + public EODataSource dataSource() { + return dataSource; + } + + /** + * Returns the key by which this display group is bound a master display group, + * or null if this is not a detail display group. + */ + public String detailKey() { + if (dataSource instanceof PropertyDataSource) { + return ((PropertyDataSource) dataSource).key(); + } + return null; + } + + /** + * Sets the key by which this display group is bound a master display group. + * Does nothing if this is not a detail display group. + */ + public void setDetailKey(String aKey) { + if (dataSource instanceof PropertyDataSource) { + ((PropertyDataSource) dataSource).setKey(aKey); + } + } + + /** + * Returns whether the data source is a detail data source, suggesting that this + * is a detail display group. + */ + public boolean hasDetailDataSource() { + return (dataSource instanceof PropertyDataSource); + } + + /** + * Returns the selected object in the master display group, or null if this is + * not a detail display group. + */ + public Object masterObject() { + if (dataSource instanceof PropertyDataSource) { + return ((PropertyDataSource) dataSource).source(); + } + return null; + } + + /** + * Sets the master object in the detail data source. Does nothing if there is no + * detail data source. + */ + public void setMasterObject(Object anObject) { + if (dataSource instanceof PropertyDataSource) { + ((PropertyDataSource) dataSource).setSource(anObject); + } + } + + // specify optional delegate + + /** + * Sets the display group delegate that will be used by this display group. + */ + public void setDelegate(Object aDelegate) { + delegate = aDelegate; + } + + /** + * Returns the current delegate for this display group, or null if no delegate + * is currently set. + */ + public Object delegate() { + return delegate; + } + + // display group configuration + + /** + * Returns the current string matching format. If not set, defaults to "%@*". + */ + public String defaultStringMatchFormat() { + return defaultStringMatchFormat; + } + + /** + * Returns the current string matching operator. If not set, defaults to + * "caseInsensitiveLike". + */ + public String defaultStringMatchOperator() { + return defaultStringMatchOperator; + } + + /** + * Sets the display group and associations to edit a "query by example" query + * object. This method is used for target/action connections. + */ + public void enterQueryMode(Object aSender) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether this display group should immediate fetch when loaded. + */ + public boolean fetchesOnLoad() { + return fetchesOnLoad; + } + + /** + * Returns whether this display group is in "query by example" mode. + */ + public boolean inQueryMode() { + return inQueryMode; + } + + /** + * Returns a Map of default values that are applied to new objects that are + * inserted into the list. + */ + public NSDictionary insertedObjectDefaultValues() { + return insertedObjectDefaultValues; + } + + /** + * Returns the keys that were declared when read from an external resource file. + */ + public NSArray localKeys() { + return localKeys; + } + + /** + * Sets whether this display group will select the first object in the list + * after a fetch. + */ + public boolean selectsFirstObjectAfterFetch() { + return selectsFirstObjectAfterFetch; + } + + /** + * Sets the default string matching format that will be used by this display + * group. + */ + public void setDefaultStringMatchFormat(String aFormat) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the default string matching operator that will be used by this display + * group. + */ + public void setDefaultStringMatchOperator(String anOperator) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets whether this display group will fetch objects from its data source on + * load. + */ + public void setFetchesOnLoad(boolean willFetch) { + fetchesOnLoad = willFetch; + } + + /** + * Sets whether this display group is in "query by example" mode. If true, all + * associations will bind to a special "example" object. + */ + public void setInQueryMode(boolean isInQueryMode) { + inQueryMode = isInQueryMode; + } + + /** + * Sets the mapping that contains the values that will be applied to new objects + * inserted into the display group. + */ + public void setInsertedObjectDefaultValues(Map aMap) { + insertedObjectDefaultValues = new NSDictionary(aMap); + } + + /** + * Sets the keys that are declared when instantiated from an external resource + * file. + */ + public void setLocalKeys(List aKeyList) { + localKeys = new NSArray((Collection) aKeyList); + } + + /** + * Sets whether the first object in the list will be selected after a fetch. + */ + public void setSelectsFirstObjectAfterFetch(boolean selectsFirst) { + selectsFirstObjectAfterFetch = selectsFirst; + } + + /** + * Sets the order of the keys by which this display group will be ordered after + * a fetch or after a call to updateDisplayedObjects(). The elements in the + * display group will be sorted first by the first key, within the first key, by + * the second key, and so on. + */ + public void setSortOrderings(List aList) { + sortOrderings.removeAllObjects(); + + Object o; + Iterator it = aList.iterator(); + while (it.hasNext()) { + o = it.next(); + // handle the convenience of specifying just a key + if (!(o instanceof EOSortOrdering)) { + o = new EOSortOrdering(o.toString(), EOSortOrdering.CompareAscending); + } + sortOrderings.add(o); + } + } + + /** + * Sets whether only changed objects are refreshed (optimistic), or whether all + * objects are refreshed (pessimistic, default). By default, when the display + * group receives notification that one of its objects has changed, + * updateDisplayedObjects is called. + */ + public void setUsesOptimisticRefresh(boolean isOptimistic) { + usesOptimisticRefresh = isOptimistic; + } + + /** + * Sets whether changes made by associations are validated immediately, or when + * changes are saved. + */ + public void setValidatesChangesImmediately(boolean validatesImmediately) { + validatesChangesImmediately = validatesImmediately; + } + + /** + * Returns a read-only List of sort orderings for this display group. + */ + public NSArray sortOrderings() { + return sortOrderingsProxy; + } + + /** + * Returns whether this display group refreshes only the changed objects or all + * objects on refresh. + */ + public boolean usesOptimisticRefresh() { + return usesOptimisticRefresh; + } + + /** + * Returns whether this display group validates changes immediately. Otherwise, + * validation should occur when changes are saved. Default is the global + * default, which is initially true. + */ + public boolean validatesChangesImmediately() { + return validatesChangesImmediately; + } + + // qualification + + /** + * Returns a qualifier that will be applied all the objects in this display + * group to determine which objects will be displayed. + */ + public EOQualifier qualifier() { + return qualifier; + } + + /** + * Returns a new qualifier built from the three query value maps: greater than, + * equal to, and less than. + */ + public EOQualifier qualifierFromQueryValues() { + // TODO: assemble qualifier from query values + + return new EOQualifier() { + // use inner class until we actually implement one + public EOQualifier qualifierWithBindings(Map aMap, boolean requireAll) { + return null; + } + + public Throwable validateKeysWithRootClassDescription(Class aClass) { + return null; + } + + public boolean evaluateWithObject(Object o) { + return false; + } + }; + } + + /** + * Calls qualifierFromQueryValues(), applies the result to the data source, and + * calls fetch(). + */ + public void qualifyDataSource() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Calls qualifierFromQueryValues(), sets the qualifier with setQualifier(), and + * calls updateDisplayedObjects(). + */ + public void qualifyDisplayGroup() { + setQualifier(qualifierFromQueryValues()); + updateDisplayedObjects(); + } + + /** + * Returns a Map containing the mappings of keys to binding query values. + */ + public NSDictionary queryBindingValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to binding query values. + */ + public NSMutableDictionary queryBindings() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to binding query values for a + * matching query. + */ + public NSMutableDictionary queryMatch() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to binding query values for a + * minimum value query. + */ + public NSMutableDictionary queryMin() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to binding query values for a + * maximum value query. + */ + public NSMutableDictionary queryMax() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to operator values. + */ + public NSMutableDictionary queryOperator() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a list containing all supported qualifier operators. + */ + public NSArray allQualifierOperators() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to operator values. + */ + public NSDictionary queryOperatorValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the qualifier that will be used by updateDisplayedObjects() to filter + * displayed objects. + */ + public void setQualifier(EOQualifier aQualifier) { + qualifier = aQualifier; + } + + /** + * Sets the mapping that contains the mappings of keys to binding values. + */ + public void setQueryBindingValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to operator values. + */ + public void setQueryOperatorValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + // qualifier query values + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for equality. + */ + public NSDictionary equalToQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for greater value. + */ + public NSDictionary greaterThanQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns a Map containing the mappings of keys to query values that will be + * used to test for lesser value. + */ + public NSDictionary lessThanQueryValues() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the Map that contains the mappings of keys to query values that will be + * used to test for equality. + */ + public void setEqualToQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to query values that will + * be used to test for greater value. + */ + public void setGreaterThanQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets the mapping that contains the mappings of keys to query values that will + * be used to test for lesser value. + */ + public void setLessThanQueryValues(Map aMap) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Deprecated: returns true. + */ + public boolean endEditing() { + return true; + } + + // object management + + /** + * Returns a read-only List containing all objects managed by the display group. + * This includes those objects not visible due to disqualification. + */ + public NSArray allObjects() { // System.out.println( "avoided allocation: allObjects" ); + return allObjectsProxy; + } + + /** + * Returns the total number of batches held by this display group. + */ + public int batchCount() { + if (batchSize < 1) + return 1; + int count = displayedObjects.count(); + if (count == 0) + return 1; + return ((count - 1) / batchSize) + 1; + } + + /** + * Returns the index of the currently displayed batch. + */ + public int currentBatchIndex() { + return batchIndex; + } + + /** + * Sets the index of the currently displayed batch. + */ + public void setCurrentBatchIndex(int aBatchIndex) { + batchIndex = aBatchIndex; + updateDisplayedObjects(); + } + + /** + * Sets the displayed objects to the batch containing the first selected object, + * and updates the current batch index, and returns null to force a page reload. + * Displays the first batch is there is no selection. + */ + public Object displayBatchContainingSelectedObject() { + if (batchSize < 1) + return null; + NSArray indexes = selectionIndexes(); + if (indexes.count() > 0) { + batchIndex = ((Number) indexes.objectAtIndex(0)).intValue() / batchSize; + } else { + batchIndex = 0; + } + updateDisplayedObjects(); + return null; + } + + /** + * Sets the displayed objects to the next batch and updates the current batch + * index, and returns null to force a page reload. If there is no next batch the + * first batch is displayed. + */ + public Object displayNextBatch() { + batchIndex = (batchIndex + 1) % batchCount(); + updateDisplayedObjects(); + return null; + } + + /** + * Sets the displayed objects to the next batch and updates the current batch + * index, and returns null to force a page reload. If there is no previous + * batch, the last batch is displayed. + */ + public Object displayPreviousBatch() { + batchIndex--; + if (batchIndex < 0) + batchIndex = batchCount() - 1; + updateDisplayedObjects(); + return null; + } + + /** + * Returns whether the displayed objects have been batched. + */ + public boolean hasMultipleBatches() { + return batchCount() > 1; + } + + /** + * Returns the one-based index within the displayed objects list of the first + * displayed object in the current batch. + */ + public int indexOfFirstDisplayedObject() { + if (batchSize < 1) + return 1; + return batchIndex * batchSize + 1; + } + + /** + * Returns the one-based index within the displayed objects list of the first + * last object in the current batch. + */ + public int indexOfLastDisplayedObject() { + if (batchSize < 1) + return displayedObjects.count(); + return Math.min(((batchIndex + 1) * batchSize), displayedObjects.count()); + } + + /** + * Returns the number of objects per batch. + */ + public int numberOfObjectsPerBatch() { + return batchSize; + } + + /** + * Returns the number of objects per batch. + */ + public void setNumberOfObjectsPerBatch(int aSize) { + batchSize = aSize; + updateDisplayedObjects(); + } + + /** + * Clears the current selection. + * + * @return True is the selection was cleared, False if the selection could not + * be cleared + * @see #setSelectionIndexes + */ + public boolean clearSelection() { + Object result = notifyDelegate("displayGroupShouldChangeSelection", + new Class[] { WODisplayGroup.class, List.class }, new Object[] { this, new NSArray(selectedObjects) }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return false; + } + + selectionChanged = true; + willChange(); + + selectedObjects.removeAllObjects(); + selectedIndexes.removeAllObjects(); + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { WODisplayGroup.class }, new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { WODisplayGroup.class }, + new Object[] { this }); + + return true; + } + + /** + * Convenience for binding to a component action: calls deleteSelection() and + * then displayBatchContainingSelectedObject() and returns null, which is a + * suitable result from a component action. + */ + public Object delete() { + deleteSelection(); + displayBatchContainingSelectedObject(); + return null; + } + + /** + * Deletes the object at the specified index, notifying the delegate before and + * after the operation, and then updating the selection if needed. + * + * @return True if delete was successful, false if the object was not deleted. + */ + public boolean deleteObjectAtIndex(int anIndex) { + Object target = displayedObjects.objectAtIndex(anIndex); + + Object result = notifyDelegate("displayGroupShouldDeleteObject", + new Class[] { WODisplayGroup.class, Object.class }, new Object[] { this, target }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return false; + } + + deleteObjectAtIndexNoNotify(anIndex); + + if (dataSource != null) { + dataSource.deleteObject(target); + } + + notifyDelegate("displayGroupDidDeleteObject", new Class[] { WODisplayGroup.class, Object.class }, + new Object[] { this, target }); + + return true; + } + + private void deleteObjectAtIndexNoNotify(int anIndex) { + Object target = displayedObjects.objectAtIndex(anIndex); + + int i; + + // remove from selected objects if necessary + i = indexOf(selectedObjects, target); + if (i != NSArray.NotFound) { + selectionChanged = true; + willChange(); // notify before removing + selectedObjects.removeObjectAtIndex(i); + selectedIndexes.remove(new Integer(i)); // comps by value + } else // notify - no selection change needed + { + willChange(); + } + + // remove from all objects + i = indexOf(allObjects, target); + if (i != NSArray.NotFound) { + allObjects.removeObjectAtIndex(i); + } else // otherwise should never happen + { // throw new WotonomyException( // "Displayed object not found in allObjects" ); - } - - // remove from displayed objects - displayedObjects.removeObjectAtIndex( anIndex ); - } - - /** - * Deletes the currently selected objects. - * This implementation calls deleteObjectAtIndex() for - * each index in the selection list, immediately returning - * false if any delete operation fails. - * @return True if all selected objects were deleted, - * false if any deletion failed. - */ - public boolean deleteSelection() - { - int i; - boolean result = true; - - Enumeration e = new NSArray( selectedObjects ).objectEnumerator(); - while ( e.hasMoreElements() ) - { - i = indexOf( displayedObjects, e.nextElement() ); - if ( i == NSArray.NotFound ) - { - // should never happen - throw new WotonomyException( - "Selected object not found in displayedObjects" ); - } - result = result && deleteObjectAtIndex( i ); - } - - return result; - } - - /** - * Returns a read-only List of all objects in the display group - * that are currently displayed by the associations. - */ - public NSArray displayedObjects() - { // System.out.println( "avoided allocation: displayedObjects" ); - if ( batchSize < 1 ) return displayedObjectsProxy; - return displayedObjectsProxy.subarrayWithRange( - new NSRange( batchIndex * batchSize, batchSize ) ); - } - - /** - * Requests a list of objects from the DataSource - * and calls setObjectArray to populate the list. - * More specifically, calls endEditing(), asks the - * delegate, fetches the objects, notifies the delegate, - * and populates the list. Returns null to force a - * page reload. - */ - public Object fetch() - { - endEditing(); - - if ( dataSource == null ) - { - return null; - } - - Object result = notifyDelegate( - "displayGroupShouldFetch", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return null; - } - - NSNotificationCenter.defaultCenter().postNotification( - DisplayGroupWillFetchNotification, this, new NSDictionary() ); - - NSArray objectList = dataSource.fetchObjects(); - - notifyDelegate( - "displayGroupDidFetchObjects", - new Class[] { WODisplayGroup.class, List.class }, - new Object[] { this, objectList } ); - - setObjectArray( objectList ); - - if ( ( selectsFirstObjectAfterFetch ) && ( displayedObjects.size() > 0 ) ) - { - setSelectionIndexes( new NSArray( new Integer( 0 ) ) ); - } - - return null; - } - - /** - * Convenience to call insertNewObjectAtIndex with the current selection plus one, - * or at the end of the list if there is no selection. - * Returns null to force a page reload. - */ - public Object insert() - { - NSArray indexes = selectionIndexes(); - int size = indexes.count(); - if ( size == 0 ) - { - insertNewObjectAtIndex( displayedObjects.count() ); - } - else - { - insertNewObjectAtIndex( - ((Number)selectedIndexes.objectAtIndex( size-1 )).intValue()+1 ); - } - return null; - } - - /** - * Creates a new object at the specified index. - * Calls insertObjectAtIndex() with the result - * from sending createObject() to the data source. - * @return the newly created object. - */ - public Object insertNewObjectAtIndex ( int anIndex ) - { - Object result = null; - if ( dataSource != null ) - { - result = dataSource.createObject(); - } - if ( result != null ) - { - if ( insertedObjectDefaultValues != null ) - { - Duplicator.writePropertiesForObject( - insertedObjectDefaultValues, result ); - } - insertObjectAtIndex( result, anIndex ); - } - else // create failed - { - if ( delegate() != null ) - { - NSSelector selector = new NSSelector( - "displayGroupCreateObjectFailed", - new Class[] { WODisplayGroup.class, EODataSource.class } ); - if ( selector.implementedByObject( delegate() ) ) - { - try - { - selector.invoke( delegate(), new Object[] { this, dataSource } ); - return result; - } - catch ( Exception exc ) - { - System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" ); - exc.printStackTrace(); - } - } - } - } - return result; - } - - /** - * Inserts the specified object into the list at - * the specified index. - */ - public void insertObjectAtIndex ( Object anObject, int anIndex ) - { - Object result = notifyDelegate( - "displayGroupShouldInsertObject", - new Class[] { WODisplayGroup.class, Object.class, int.class }, - new Object[] { this, anObject, new Integer(anIndex) } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - return; - } - - updatedObjectIndex = anIndex; - willChange(); - - int i; - - // add to all objects - if ( anIndex == displayedObjects.size() ) - { - allObjects.addObject( anObject ); - } - else // insert before same object - { - Object target = displayedObjects.objectAtIndex( anIndex ); - int targetIndex = indexOf( allObjects, target ); - if ( targetIndex != NSArray.NotFound ) - { - allObjects.insertObjectAtIndex( anObject, targetIndex ); - } - else // should never happen - { - throw new WotonomyException( - "Could not find displayed object in all objects list: " - + target ); - } - } - - // add to displayed objects - displayedObjects.insertObjectAtIndex( anObject, anIndex ); - - if ( dataSource != null ) - { - if ( dataSource instanceof OrderedDataSource ) - { - ((OrderedDataSource)dataSource).insertObjectAtIndex( - anObject, anIndex ); - } - else - { - dataSource.insertObject( anObject ); - } - } - - notifyDelegate( - "displayGroupDidInsertObject", - new Class[] { WODisplayGroup.class, Object.class }, - new Object[] { this, anObject } ); - } - - /** - * Sets contentsChanged to true and notifies all observers. - */ - public void redisplay() - { - willChange(); - } - - /** - * Sets the selection to the next displayed object after the current - * selection. If the last object is selected, or if no object - * is selected, then the first object becomes selected. - * If multiple items are selected, the first selected item is - * considered the selected item for the purposes of this method. - * Does not call redisplay(). - * @return null to force a page reload. - */ - public Object selectNext() - { - int count = displayedObjects.count(); - if ( count == 0 ) return null; - if ( count == 1 ) - { - selectObject( displayedObjects.objectAtIndex( 0 ) ); - return null; - } - - int i = -1; - Object selectedObject = selectedObject(); - if ( selectedObject != null ) - { - i = indexOf( displayedObjects, selectedObject ); - } - if ( i == NSArray.NotFound ) i = -1; - - // select next object - i++; - if ( i != displayedObjects.count() ) - { - // set to next object - selectedObject = displayedObjects.objectAtIndex( i ); - } - else // out of range - { - // set to null - selectedObject = displayedObjects.objectAtIndex( 0 ); - } - - selectObject( selectedObject ); - return null; - } - - /** - * Sets the selection to the specified object. - * If the specified object is null or does not exist - * in the list of displayed objects, the selection - * will be cleared. - * @return true if the object was selected. - */ - public boolean selectObject ( Object anObject ) - { - if ( ( anObject == null ) || - ( indexOf( displayedObjects, anObject ) - == NSArray.NotFound ) ) - { - clearSelection(); - return false; - } - - selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) ); - return true; - } - - /** - * Sets the selection to the specified objects. - * If the specified list is null or if none of the objects - * in the list exist in the list of displayed objects, the - * selection will be cleared. - * @return true if all specified objects were selected. - */ - public boolean selectObjectsIdenticalTo ( List anObjectList ) - { - // optimization: check for resetting of selection - if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) ) - { - boolean identical = true; - int size = selectedObjects.size(); - for ( int i = 0; ( i < size ) && identical; i++ ) - { - // compare by reference - if ( anObjectList.get( i ) != selectedObjects.get( i ) ) - { - identical = false; - } - else if ( displayedObjects.indexOfIdenticalObject( - anObjectList.get( i ) ) == NSArray.NotFound ) - { - identical = false; - } - } - if ( identical ) - { - return true; - } - } - - Object result = notifyDelegate( - "displayGroupShouldChangeSelection", - new Class[] { WODisplayGroup.class, List.class }, - new Object[] { this, anObjectList } ); - if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) ) - { - // need to notify the calling component - // to revert back to the previous selection - selectionChanged = true; - willChange(); - return false; - } - - int i; - selectionChanged = true; - willChange(); - Object o; - selectedObjects.removeAllObjects(); - selectedIndexes.removeAllObjects(); - Iterator it = anObjectList.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - if ( ( i = displayedObjects.indexOfIdenticalObject( o ) ) - != NSArray.NotFound ) - { - selectedObjects.addObject( o ); - selectedIndexes.addObject( new Integer( i ) ); - } - } - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - - return true; - } - - /** - * Calls selectObjectsIdenticalTo and if false is returned - * and selectFirstIfNoMatch is true, selects the first object. - */ - public boolean selectObjectsIdenticalToSelectFirstOnNoMatch( - List anObjectList, boolean selectFirstIfNoMatch ) - { - if ( selectObjectsIdenticalTo( anObjectList ) ) - { - return true; - } - if ( selectFirstIfNoMatch ) - { - clearSelection(); - selectNext(); - return true; - } - return false; - } - - /** - * Sets the selection to the previous displayed object before the current - * selection. If the first object is selected, or if no object - * is selected, then the last object becomes selected. - * If multiple items are selected, the first selected item is - * considered the selected item for the purposes of this method. - * Does not call redisplay(). - * @return null to force a page reload. - */ - public Object selectPrevious() - { - int i = displayedObjects.count(); - if ( i == 0 ) return null; - if ( i == 1 ) - { - selectObject( displayedObjects.objectAtIndex( 0 ) ); - return null; - } - - Object selectedObject = selectedObject(); - if ( selectedObject != null ) - { - i = indexOf( displayedObjects, selectedObject ); - } - if ( i == NSArray.NotFound ) i = displayedObjects.count(); - - // select next object - i--; - if ( i < 0 ) - { - // out of range - select last object - i = displayedObjects.count() - 1; - } - - selectObject( displayedObjects.objectAtIndex( i ) ); - return null; - } - - /** - * Returns the currently selected object, or null if - * there is no selection. - */ - public Object selectedObject() - { - if ( selectedObjects.count() == 0 ) - { - return null; - } - return selectedObjects.objectAtIndex( 0 ); - } - - /** - * Returns a read-only List containing all selected objects, if any. - * Returns an empty list if no objects are selected. - */ - public NSArray selectedObjects() - { // System.out.println( "avoided allocation: selectedObjects" ); - return selectedObjectsProxy; - } - - /** - * Returns a read-only List containing the indexes of all selected - * objects, if any. The list contains instances of - * java.lang.Number; call intValue() to retrieve the index. - */ - public NSArray selectionIndexes() - { + } + + // remove from displayed objects + displayedObjects.removeObjectAtIndex(anIndex); + } + + /** + * Deletes the currently selected objects. This implementation calls + * deleteObjectAtIndex() for each index in the selection list, immediately + * returning false if any delete operation fails. + * + * @return True if all selected objects were deleted, false if any deletion + * failed. + */ + public boolean deleteSelection() { + int i; + boolean result = true; + + Enumeration e = new NSArray(selectedObjects).objectEnumerator(); + while (e.hasMoreElements()) { + i = indexOf(displayedObjects, e.nextElement()); + if (i == NSArray.NotFound) { + // should never happen + throw new WotonomyException("Selected object not found in displayedObjects"); + } + result = result && deleteObjectAtIndex(i); + } + + return result; + } + + /** + * Returns a read-only List of all objects in the display group that are + * currently displayed by the associations. + */ + public NSArray displayedObjects() { // System.out.println( "avoided allocation: displayedObjects" ); + if (batchSize < 1) + return displayedObjectsProxy; + return displayedObjectsProxy.subarrayWithRange(new NSRange(batchIndex * batchSize, batchSize)); + } + + /** + * Requests a list of objects from the DataSource and calls setObjectArray to + * populate the list. More specifically, calls endEditing(), asks the delegate, + * fetches the objects, notifies the delegate, and populates the list. Returns + * null to force a page reload. + */ + public Object fetch() { + endEditing(); + + if (dataSource == null) { + return null; + } + + Object result = notifyDelegate("displayGroupShouldFetch", new Class[] { WODisplayGroup.class }, + new Object[] { this }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return null; + } + + NSNotificationCenter.defaultCenter().postNotification(DisplayGroupWillFetchNotification, this, + new NSDictionary()); + + NSArray objectList = dataSource.fetchObjects(); + + notifyDelegate("displayGroupDidFetchObjects", new Class[] { WODisplayGroup.class, List.class }, + new Object[] { this, objectList }); + + setObjectArray(objectList); + + if ((selectsFirstObjectAfterFetch) && (displayedObjects.size() > 0)) { + setSelectionIndexes(new NSArray(new Integer(0))); + } + + return null; + } + + /** + * Convenience to call insertNewObjectAtIndex with the current selection plus + * one, or at the end of the list if there is no selection. Returns null to + * force a page reload. + */ + public Object insert() { + NSArray indexes = selectionIndexes(); + int size = indexes.count(); + if (size == 0) { + insertNewObjectAtIndex(displayedObjects.count()); + } else { + insertNewObjectAtIndex(((Number) selectedIndexes.objectAtIndex(size - 1)).intValue() + 1); + } + return null; + } + + /** + * Creates a new object at the specified index. Calls insertObjectAtIndex() with + * the result from sending createObject() to the data source. + * + * @return the newly created object. + */ + public Object insertNewObjectAtIndex(int anIndex) { + Object result = null; + if (dataSource != null) { + result = dataSource.createObject(); + } + if (result != null) { + if (insertedObjectDefaultValues != null) { + Duplicator.writePropertiesForObject(insertedObjectDefaultValues, result); + } + insertObjectAtIndex(result, anIndex); + } else // create failed + { + if (delegate() != null) { + NSSelector selector = new NSSelector("displayGroupCreateObjectFailed", + new Class[] { WODisplayGroup.class, EODataSource.class }); + if (selector.implementedByObject(delegate())) { + try { + selector.invoke(delegate(), new Object[] { this, dataSource }); + return result; + } catch (Exception exc) { + System.err.println("Error notifying delegate: displayGroupCreateObjectFailed"); + exc.printStackTrace(); + } + } + } + } + return result; + } + + /** + * Inserts the specified object into the list at the specified index. + */ + public void insertObjectAtIndex(Object anObject, int anIndex) { + Object result = notifyDelegate("displayGroupShouldInsertObject", + new Class[] { WODisplayGroup.class, Object.class, int.class }, + new Object[] { this, anObject, new Integer(anIndex) }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + return; + } + + updatedObjectIndex = anIndex; + willChange(); + + int i; + + // add to all objects + if (anIndex == displayedObjects.size()) { + allObjects.addObject(anObject); + } else // insert before same object + { + Object target = displayedObjects.objectAtIndex(anIndex); + int targetIndex = indexOf(allObjects, target); + if (targetIndex != NSArray.NotFound) { + allObjects.insertObjectAtIndex(anObject, targetIndex); + } else // should never happen + { + throw new WotonomyException("Could not find displayed object in all objects list: " + target); + } + } + + // add to displayed objects + displayedObjects.insertObjectAtIndex(anObject, anIndex); + + if (dataSource != null) { + if (dataSource instanceof OrderedDataSource) { + ((OrderedDataSource) dataSource).insertObjectAtIndex(anObject, anIndex); + } else { + dataSource.insertObject(anObject); + } + } + + notifyDelegate("displayGroupDidInsertObject", new Class[] { WODisplayGroup.class, Object.class }, + new Object[] { this, anObject }); + } + + /** + * Sets contentsChanged to true and notifies all observers. + */ + public void redisplay() { + willChange(); + } + + /** + * Sets the selection to the next displayed object after the current selection. + * If the last object is selected, or if no object is selected, then the first + * object becomes selected. If multiple items are selected, the first selected + * item is considered the selected item for the purposes of this method. Does + * not call redisplay(). + * + * @return null to force a page reload. + */ + public Object selectNext() { + int count = displayedObjects.count(); + if (count == 0) + return null; + if (count == 1) { + selectObject(displayedObjects.objectAtIndex(0)); + return null; + } + + int i = -1; + Object selectedObject = selectedObject(); + if (selectedObject != null) { + i = indexOf(displayedObjects, selectedObject); + } + if (i == NSArray.NotFound) + i = -1; + + // select next object + i++; + if (i != displayedObjects.count()) { + // set to next object + selectedObject = displayedObjects.objectAtIndex(i); + } else // out of range + { + // set to null + selectedObject = displayedObjects.objectAtIndex(0); + } + + selectObject(selectedObject); + return null; + } + + /** + * Sets the selection to the specified object. If the specified object is null + * or does not exist in the list of displayed objects, the selection will be + * cleared. + * + * @return true if the object was selected. + */ + public boolean selectObject(Object anObject) { + if ((anObject == null) || (indexOf(displayedObjects, anObject) == NSArray.NotFound)) { + clearSelection(); + return false; + } + + selectObjectsIdenticalTo(new NSArray(new Object[] { anObject })); + return true; + } + + /** + * Sets the selection to the specified objects. If the specified list is null or + * if none of the objects in the list exist in the list of displayed objects, + * the selection will be cleared. + * + * @return true if all specified objects were selected. + */ + public boolean selectObjectsIdenticalTo(List anObjectList) { + // optimization: check for resetting of selection + if ((anObjectList != null) && (selectedObjects.size() == anObjectList.size())) { + boolean identical = true; + int size = selectedObjects.size(); + for (int i = 0; (i < size) && identical; i++) { + // compare by reference + if (anObjectList.get(i) != selectedObjects.get(i)) { + identical = false; + } else if (displayedObjects.indexOfIdenticalObject(anObjectList.get(i)) == NSArray.NotFound) { + identical = false; + } + } + if (identical) { + return true; + } + } + + Object result = notifyDelegate("displayGroupShouldChangeSelection", + new Class[] { WODisplayGroup.class, List.class }, new Object[] { this, anObjectList }); + if ((result != null) && (Boolean.FALSE.equals(result))) { + // need to notify the calling component + // to revert back to the previous selection + selectionChanged = true; + willChange(); + return false; + } + + int i; + selectionChanged = true; + willChange(); + Object o; + selectedObjects.removeAllObjects(); + selectedIndexes.removeAllObjects(); + Iterator it = anObjectList.iterator(); + while (it.hasNext()) { + o = it.next(); + if ((i = displayedObjects.indexOfIdenticalObject(o)) != NSArray.NotFound) { + selectedObjects.addObject(o); + selectedIndexes.addObject(new Integer(i)); + } + } + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { WODisplayGroup.class }, new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { WODisplayGroup.class }, + new Object[] { this }); + + return true; + } + + /** + * Calls selectObjectsIdenticalTo and if false is returned and + * selectFirstIfNoMatch is true, selects the first object. + */ + public boolean selectObjectsIdenticalToSelectFirstOnNoMatch(List anObjectList, boolean selectFirstIfNoMatch) { + if (selectObjectsIdenticalTo(anObjectList)) { + return true; + } + if (selectFirstIfNoMatch) { + clearSelection(); + selectNext(); + return true; + } + return false; + } + + /** + * Sets the selection to the previous displayed object before the current + * selection. If the first object is selected, or if no object is selected, then + * the last object becomes selected. If multiple items are selected, the first + * selected item is considered the selected item for the purposes of this + * method. Does not call redisplay(). + * + * @return null to force a page reload. + */ + public Object selectPrevious() { + int i = displayedObjects.count(); + if (i == 0) + return null; + if (i == 1) { + selectObject(displayedObjects.objectAtIndex(0)); + return null; + } + + Object selectedObject = selectedObject(); + if (selectedObject != null) { + i = indexOf(displayedObjects, selectedObject); + } + if (i == NSArray.NotFound) + i = displayedObjects.count(); + + // select next object + i--; + if (i < 0) { + // out of range - select last object + i = displayedObjects.count() - 1; + } + + selectObject(displayedObjects.objectAtIndex(i)); + return null; + } + + /** + * Returns the currently selected object, or null if there is no selection. + */ + public Object selectedObject() { + if (selectedObjects.count() == 0) { + return null; + } + return selectedObjects.objectAtIndex(0); + } + + /** + * Returns a read-only List containing all selected objects, if any. Returns an + * empty list if no objects are selected. + */ + public NSArray selectedObjects() { // System.out.println( "avoided allocation: selectedObjects" ); + return selectedObjectsProxy; + } + + /** + * Returns a read-only List containing the indexes of all selected objects, if + * any. The list contains instances of java.lang.Number; call intValue() to + * retrieve the index. + */ + public NSArray selectionIndexes() { // return selectedIndexes; - int i; - NSMutableArray result = new NSMutableArray(); - Enumeration e = selectedObjects.objectEnumerator(); - while ( e.hasMoreElements() ) - { - i = indexOf( displayedObjects, e.nextElement() ); - if ( i != NSArray.NotFound ) - { - result.addObject( new Integer( i ) ); - } - else - { - System.err.println( - "Should never happen: selected objects not in displayed objects" ); - new RuntimeException().printStackTrace( System.err ); - } - } - return result; - } - - /** - * Sets the objects managed by this display group. - * updateDisplayedObjects() is called to filter the - * display objects. The previous selection will be - * maintained if possible. The data source is not - * notified. - */ - public void setObjectArray ( List anObjectList ) - { - if ( anObjectList == null ) anObjectList = new NSArray(); - - Object result = notifyDelegate( - "displayGroupDisplayArrayForObjects", - new Class[] { WODisplayGroup.class, List.class }, - new Object[] { this, anObjectList } ); - if ( result != null ) - { - anObjectList = (List) result; - } - - willChange(); - - NSArray oldSelectedObjects = new NSArray( selectedObjects ); // copy - - // reset allObjects to new list - allObjects.removeAllObjects(); - allObjects.addObjectsFromArray( anObjectList ); - - // update the displayed object list - updateDisplayedObjects(); - - // restore the selection if possible - selectObjectsIdenticalTo( oldSelectedObjects ); - - batchIndex = 0; - displayBatchContainingSelectedObject(); - } - - /** - * Sets the currently selected object, or clears the - * selection if the object is not found or is null. - * Note: it's not clear how this differs from - * selectObject in the spec. It is recommended that - * you call selectObject for now. - */ - public void setSelectedObject ( Object anObject ) - { - selectObject( anObject ); - } - - /** - * Sets the current selection to the specified objects. - * The previous selection is cleared, and any objects - * in the display group that are in the specified list - * are then selected. If no items in the specified list - * are found in the display group, then the selection is - * effectively cleared. - * Note: it's not clear how this differs from - * selectObjectsIdenticalTo in the spec. - * It is recommended that you call that method for now. - */ - public void setSelectedObjects ( List aList ) - { - selectObjectsIdenticalTo( aList ); - } - - /** - * Sets the current selection to the objects at the - * specified indexes. Items in the list are assumed - * to be instances of java.lang.Number. - * The previous selection is cleared, and any objects - * in the display group that are in the specified list - * are then selected. If no items in the specified list - * are found in the display group, then the selection is - * effectively cleared. - */ - public boolean setSelectionIndexes ( List aList ) - { - Object o; - int index; - NSMutableArray objects = new NSMutableArray(); - Iterator it = aList.iterator(); - while ( it.hasNext() ) - { - index = ((Number)it.next()).intValue(); - if ( index < displayedObjects.count() ) - { - o = displayedObjects.objectAtIndex( index ); - if ( o != null ) - { - objects.add( o ); - } - } - } - return selectObjectsIdenticalTo( objects ); - } - - /** - * Applies the qualifier to all objects and sorts - * the results to update the list of displayed objects. - * Observing associations are notified to reflect the changes. - */ - public void updateDisplayedObjects() - { - updatedObjectIndex = -1; - willChange(); - - displayedObjects.removeAllObjects(); - - displayedObjects.addObjectsFromArray( allObjects ); - - // apply qualifier, if any - if ( qualifier != null ) - { - EOQualifier.filterArrayWithQualifier( - displayedObjects, qualifier ); - } - - // apply sort orderings, if any - NSArray orderings = sortOrderings(); - if ( orderings != null ) - { - if ( orderings.count() > 0 ) - { - selectionChanged = true; - willChange(); - EOSortOrdering.sortArrayUsingKeyOrderArray( - displayedObjects, orderings ); - } - } - - // make sure the selectedObjects is a subset of displayedObjects - int i; - Object o; - Iterator it = new LinkedList( selectedObjects ).iterator(); - boolean removeflag = false; - selectedIndexes.removeAllObjects(); - while ( it.hasNext() ) - { - o = it.next(); - if ( ( i = displayedObjects.indexOfIdenticalObject( o ) ) - == NSArray.NotFound ) - { - selectedObjects.removeIdenticalObject( o ); - removeflag = true; - } - else - { - selectedIndexes.addObject( new Integer( i ) ); - } - } - - //Note: it is important to put the - //selectionChanged = true line below remove. - if (removeflag) - { - selectionChanged = true; - willChange(); - - notifyDelegate( - "displayGroupDidChangeSelection", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - notifyDelegate( - "displayGroupDidChangeSelectedObjects", - new Class[] { WODisplayGroup.class }, - new Object[] { this } ); - } - } - - /** - * Returns the index of the changed object. If more than - * one object has changed, -1 is returned. - */ - public int updatedObjectIndex() - { - return updatedObjectIndex; - } - - // getting and setting values in objects - - /** - * Returns a value on the selected object for the specified key. - */ - public Object selectedObjectValueForKey ( String aKey ) - { - Object selectedObject = selectedObject(); - if ( selectedObject == null ) return null; - return valueForObject( selectedObject, aKey ); - } - - /** - * Sets the specified value for the specified key on - * all selected objects. - */ - public boolean setSelectedObjectValue ( - Object aValue, String aKey ) - { - Object selectedObject = selectedObject(); - if ( selectedObject == null ) return false; - return setValueForObject( aValue, selectedObject, aKey ); - } - - /** - * Sets the specified value for the specified key on - * the specified object. Validations may be triggered, - * and error dialogs may appear to the user. - * @return True if the value was set successfully, - * false if the value could not be set and the update - * operation should not continue. - */ - public boolean setValueForObject ( - Object aValue, Object anObject, String aKey ) - { - // notify object's observers: - // this includes us, and will notify our observers - EOObserverCenter.notifyObserversObjectWillChange( anObject ); - - //TODO: if key is null, need to remove old object - // and add new object instead of simply replacing it. - - try - { - if ( anObject instanceof EOKeyValueCoding ) - { - ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey ); - } - else - { - EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey ); - } - } - catch ( RuntimeException exc ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { WODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", exc.getMessage() } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - throw exc; - } - return false; - } - - notifyDelegate( - "displayGroupDidSetValueForObject", - new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class }, - new Object[] { this, aValue, anObject, aKey } ); - - return true; - } - - /** - * Calls setValueForObject() for the object at - * the specified index. - */ - public boolean setValueForObjectAtIndex ( - Object aValue, int anIndex, String aKey ) - { - return setValueForObject( - aValue, displayedObjects.objectAtIndex( anIndex ), aKey ); - } - - /** - * Returns the value for the specified key on the specified object. - */ - public Object valueForObject ( Object anObject, String aKey ) - { - // empty string is considered the identity property - if ( aKey == null ) return anObject; - if ( aKey.equals( "" ) ) return anObject; - - try - { - if ( anObject instanceof EOKeyValueCoding ) - { - return ((EOKeyValueCoding)anObject).valueForKey( aKey ); - } - else - { - return EOKeyValueCodingSupport.valueForKey( anObject, aKey ); - } - } - catch ( RuntimeException exc ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { WODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", exc.getMessage() } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - throw exc; - } - return null; - } - } - - /** - * Calls valueForObject() for the object at the specified index. - * Returns null if out of bounds. - */ - public Object valueForObjectAtIndex ( int anIndex, String aKey ) - { - if ( displayedObjects.count() <= anIndex ) return null; - Object o = displayedObjects.objectAtIndex( anIndex ); - return valueForObject( o, aKey ); - } - - /** - * Prints out the list of displayed objects. - */ - public String toString() - { - return displayedObjects.toString(); - } - - - /** - * Handles notifications from the data source's editing context, - * looking for InvalidatedAllObjectsInStoreNotification and - * ObjectsChangedInEditingContextNotification, refetching in - * the former case and updating displayed objects in the latter. - * Note: This method is not in the public specification. - */ - public void objectsInvalidatedInEditingContext( NSNotification aNotification ) - { - if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification - .equals( aNotification.name() ) ) - { - Object result = notifyDelegate( - "displayGroupShouldRefetch", - new Class[] { WODisplayGroup.class, NSNotification.class }, - new Object[] { this, aNotification } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - fetch(); - } - } - else - if ( EOEditingContext.ObjectsChangedInEditingContextNotification - .equals( aNotification.name() ) ) - { - Object result = notifyDelegate( - "displayGroupShouldRedisplay", - new Class[] { WODisplayGroup.class, NSNotification.class }, - new Object[] { this, aNotification } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - int index; - Enumeration e; - boolean didChange = false; - NSDictionary userInfo = aNotification.userInfo(); - - // inserts are ignored - - // mark updated objects as updated - NSArray updates = (NSArray) userInfo.objectForKey( - EOObjectStore.UpdatedKey ); - e = updates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - index = indexOf( displayedObjects, e.nextElement() ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "WODisplayGroup: updated: " + index ); - if ( ! didChange ) - { - didChange = true; - willChange(); - updatedObjectIndex = index; - } - else - { - updatedObjectIndex = -1; - } - } - } - - // treat invalidated objects as updated - NSArray invalidates = (NSArray) userInfo.objectForKey( - EOObjectStore.InvalidatedKey ); - e = invalidates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - index = indexOf( displayedObjects, e.nextElement() ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "WODisplayGroup: invalidated: " + index ); - if ( ! didChange ) - { - didChange = true; - willChange(); - updatedObjectIndex = index; - } - else - { - updatedObjectIndex = -1; - } - } - } - - // remove deletes from display group if they exist - NSArray deletes = (NSArray) userInfo.objectForKey( - EOObjectStore.DeletedKey ); - e = deletes.objectEnumerator(); - Object o; - while ( e.hasMoreElements() ) - { - o = e.nextElement(); - index = indexOf( displayedObjects, o ); - if ( index != NSArray.NotFound ) - { - //System.out.println( "WODisplayGroup: deleted: " + o ); - deleteObjectAtIndexNoNotify( index ); - } - } - - if ( !usesOptimisticRefresh() ) - { - updateDisplayedObjects(); - } - } - } - - } - - // static methods - - /** - * Specifies the default behavior for whether changes - * should be validated immediately for all display groups. - */ - public static boolean - globalDefaultForValidatesChangesImmediately() - { - return globalDefaultForValidatesChangesImmediately; - } - - /** - * Specifies the default string matching format for all - * display groups. - */ - public static String globalDefaultStringMatchFormat() - { - return globalDefaultStringMatchFormat; - } - - /** - * Specifies the default string matching operator for all - * display groups. - */ - public static String globalDefaultStringMatchOperator() - { - return globalDefaultStringMatchOperator; - } - - /** - * Sets the default behavior for validating changes - * for all display groups. - */ - public static void - setGlobalDefaultForValidatesChangesImmediately ( - boolean validatesImmediately ) - { - globalDefaultForValidatesChangesImmediately = - validatesImmediately; - } - - /** - * Sets the default string matching format that - * will be used by all display groups. - */ - public static void - setGlobalDefaultStringMatchFormat ( String aFormat ) - { - globalDefaultStringMatchFormat = aFormat; - } - - /** - * Sets the default string matching operator that - * will be used by all display groups. - */ - public static void - setGlobalDefaultStringMatchOperator ( String anOperator ) - { - globalDefaultStringMatchOperator = anOperator; - } - - /** - * Needed because we don't inherit from NSObject. - * Calls EOObserverCenter.notifyObserversObjectWillChange. - */ - protected void willChange() - { - EOObserverCenter.notifyObserversObjectWillChange( this ); - } - - /** - * Returns the index of the specified object in the - * specified NSArray, comparing by value or by reference - * as determined by the private instance variable - * compareByReference. If not found, returns NSArray.NotFound. - */ - private int indexOf( NSArray anArray, Object anObject ) - { - if ( compareByReference ) - { - return anArray.indexOfIdenticalObject( anObject ); - } - else - { - return anArray.indexOf( anObject ); - } - } - - // interface EOObserving - - /** - * Receives notifications of changes from objects that - * are managed by this display group. This implementation - * sets updatedObjectIndex as appropriate. - */ - public void objectWillChange(Object anObject) - { - int index = indexOf( displayedObjects, anObject ); - if ( index != NSArray.NotFound ) - { - updatedObjectIndex = index; - willChange(); - } - } - - // interface EOEditingContext.Editor - - /** - * Called before the editing context begins to save changes. - * This implementation calls endEditing(). - */ - public void editingContextWillSaveChanges( - EOEditingContext anEditingContext ) - { - endEditing(); - } - - /** - * Called to determine whether this editor has changes - * that have not been committed to the object in the context. - * This implementation returns false. - */ - public boolean editorHasChangesForEditingContext( - EOEditingContext anEditingContext ) - { - return false; - } - - // interface EOEditingContext.MessageHandler - - /** - * Called to display a message for an error that occurred - * in the specified editing context. If the delegate allows, - * this implementation writes a message to the standard output. - * Override to customize. - */ - public void editingContextPresentErrorMessage( - EOEditingContext anEditingContext, - String aMessage ) - { - Object result = notifyDelegate( - "displayGroupShouldDisplayAlert", - new Class[] { WODisplayGroup.class, String.class, String.class }, - new Object[] { this, "Error", aMessage } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - System.out.println( 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. - * This implementation returns true. Override to customize. - */ - public boolean editingContextShouldContinueFetching( - EOEditingContext anEditingContext, - int count, - int limit, - EOObjectStore anObjectStore ) - { - return true; - } - - /** - * 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; - } - - /** - * DisplayGroups can delegate important decisions to a Delegate. - * Note that DisplayGroup doesn't require its delegates to implement - * this interface: rather, this interface defines the methods that - * DisplayGroup 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 when the specified data source fails - * to create an object for the specified display group. - */ - void displayGroupCreateObjectFailed ( - WODisplayGroup aDisplayGroup, - EODataSource aDataSource ); - - /** - * Called after the specified display group's - * data source is changed. - */ - void displayGroupDidChangeDataSource ( - WODisplayGroup aDisplayGroup ); - - /** - * Called after a change occurs in the specified - * display group's selected objects. - */ - void displayGroupDidChangeSelectedObjects ( - WODisplayGroup aDisplayGroup ); - - /** - * Called after the specified display group's - * selection has changed. - */ - void displayGroupDidChangeSelection ( - WODisplayGroup aDisplayGroup ); - - /** - * Called after the specified display group has - * deleted the specified object. - */ - void displayGroupDidDeleteObject ( - WODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called after the specified display group - * has fetched the specified object list. - */ - void displayGroupDidFetchObjects ( - WODisplayGroup aDisplayGroup, - List anObjectList ); - - /** - * Called after the specified display group - * has inserted the specified object into - * its internal object list. - */ - void displayGroupDidInsertObject ( - WODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called after the specified display group - * has set the specified value for the specified - * object and key. - */ - void displayGroupDidSetValueForObject ( - WODisplayGroup aDisplayGroup, - Object aValue, - Object anObject, - String aKey ); - - /** - * Called by the specified display group to - * determine what objects should be displayed - * for the objects in the specified list. - * @return An NSArray containing the objects - * to be displayed for the objects in the - * specified list. - */ - NSArray displayGroupDisplayArrayForObjects ( - WODisplayGroup aDisplayGroup, - List aList ); - - /** - * Called by the specified display group before - * it attempts to change the selection. - * @return True to allow the selection to change, - * false otherwise. - */ - boolean displayGroupShouldChangeSelection ( - WODisplayGroup aDisplayGroup, - List aSelectionList ); - - /** - * Called by the specified display group before - * it attempts to delete the specified object. - * @return True to allow the object to be deleted - * false to prevent the deletion. - */ - boolean displayGroupShouldDeleteObject ( - WODisplayGroup aDisplayGroup, - Object anObject ); - - /** - * Called by the specified display group before - * it attempts display the specified alert to - * the user. - * @return True to allow the message to be - * displayed, false if you want to handle the - * alert yourself and suppress the display group's - * notification. - */ - boolean displayGroupShouldDisplayAlert ( - WODisplayGroup aDisplayGroup, - String aTitle, - String aMessage ); - - /** - * Called by the specified display group before - * it attempts fetch objects. - * @return True to allow the fetch to take place, - * false to prevent the fetch. - */ - boolean displayGroupShouldFetch ( - WODisplayGroup aDisplayGroup ); - - /** - * Called by the specified display group before - * it attempts to insert the specified object. - * @return True to allow the object to be inserted - * false to prevent the insertion. - */ - boolean displayGroupShouldInsertObject ( - WODisplayGroup aDisplayGroup, - Object anObject, - int anIndex ); - - /** - * Called by the specified display group when - * it receives the specified - * ObjectsChangedInEditingContextNotification. - * @return True to allow the display group to - * update the display (recommended), false - * to prevent the update. - */ - boolean displayGroupShouldRedisplay ( - WODisplayGroup aDisplayGroup, - NSNotification aNotification ); - - /** - * Called by the specified display group when - * it receives the specified - * InvalidatedAllObjectsInStoreNotification. - * @return True to allow the display group to - * refetch (recommended), false to prevent the - * refetch. - */ - boolean displayGroupShouldRefetch ( - WODisplayGroup aDisplayGroup, - NSNotification aNotification ); - - } + int i; + NSMutableArray result = new NSMutableArray(); + Enumeration e = selectedObjects.objectEnumerator(); + while (e.hasMoreElements()) { + i = indexOf(displayedObjects, e.nextElement()); + if (i != NSArray.NotFound) { + result.addObject(new Integer(i)); + } else { + System.err.println("Should never happen: selected objects not in displayed objects"); + new RuntimeException().printStackTrace(System.err); + } + } + return result; + } + + /** + * Sets the objects managed by this display group. updateDisplayedObjects() is + * called to filter the display objects. The previous selection will be + * maintained if possible. The data source is not notified. + */ + public void setObjectArray(List anObjectList) { + if (anObjectList == null) + anObjectList = new NSArray(); + + Object result = notifyDelegate("displayGroupDisplayArrayForObjects", + new Class[] { WODisplayGroup.class, List.class }, new Object[] { this, anObjectList }); + if (result != null) { + anObjectList = (List) result; + } + + willChange(); + + NSArray oldSelectedObjects = new NSArray(selectedObjects); // copy + + // reset allObjects to new list + allObjects.removeAllObjects(); + allObjects.addObjectsFromArray(anObjectList); + + // update the displayed object list + updateDisplayedObjects(); + + // restore the selection if possible + selectObjectsIdenticalTo(oldSelectedObjects); + + batchIndex = 0; + displayBatchContainingSelectedObject(); + } + + /** + * Sets the currently selected object, or clears the selection if the object is + * not found or is null. Note: it's not clear how this differs from selectObject + * in the spec. It is recommended that you call selectObject for now. + */ + public void setSelectedObject(Object anObject) { + selectObject(anObject); + } + + /** + * Sets the current selection to the specified objects. The previous selection + * is cleared, and any objects in the display group that are in the specified + * list are then selected. If no items in the specified list are found in the + * display group, then the selection is effectively cleared. Note: it's not + * clear how this differs from selectObjectsIdenticalTo in the spec. It is + * recommended that you call that method for now. + */ + public void setSelectedObjects(List aList) { + selectObjectsIdenticalTo(aList); + } + + /** + * Sets the current selection to the objects at the specified indexes. Items in + * the list are assumed to be instances of java.lang.Number. The previous + * selection is cleared, and any objects in the display group that are in the + * specified list are then selected. If no items in the specified list are found + * in the display group, then the selection is effectively cleared. + */ + public boolean setSelectionIndexes(List aList) { + Object o; + int index; + NSMutableArray objects = new NSMutableArray(); + Iterator it = aList.iterator(); + while (it.hasNext()) { + index = ((Number) it.next()).intValue(); + if (index < displayedObjects.count()) { + o = displayedObjects.objectAtIndex(index); + if (o != null) { + objects.add(o); + } + } + } + return selectObjectsIdenticalTo(objects); + } + + /** + * Applies the qualifier to all objects and sorts the results to update the list + * of displayed objects. Observing associations are notified to reflect the + * changes. + */ + public void updateDisplayedObjects() { + updatedObjectIndex = -1; + willChange(); + + displayedObjects.removeAllObjects(); + + displayedObjects.addObjectsFromArray(allObjects); + + // apply qualifier, if any + if (qualifier != null) { + EOQualifier.filterArrayWithQualifier(displayedObjects, qualifier); + } + + // apply sort orderings, if any + NSArray orderings = sortOrderings(); + if (orderings != null) { + if (orderings.count() > 0) { + selectionChanged = true; + willChange(); + EOSortOrdering.sortArrayUsingKeyOrderArray(displayedObjects, orderings); + } + } + + // make sure the selectedObjects is a subset of displayedObjects + int i; + Object o; + Iterator it = new LinkedList(selectedObjects).iterator(); + boolean removeflag = false; + selectedIndexes.removeAllObjects(); + while (it.hasNext()) { + o = it.next(); + if ((i = displayedObjects.indexOfIdenticalObject(o)) == NSArray.NotFound) { + selectedObjects.removeIdenticalObject(o); + removeflag = true; + } else { + selectedIndexes.addObject(new Integer(i)); + } + } + + // Note: it is important to put the + // selectionChanged = true line below remove. + if (removeflag) { + selectionChanged = true; + willChange(); + + notifyDelegate("displayGroupDidChangeSelection", new Class[] { WODisplayGroup.class }, + new Object[] { this }); + notifyDelegate("displayGroupDidChangeSelectedObjects", new Class[] { WODisplayGroup.class }, + new Object[] { this }); + } + } + + /** + * Returns the index of the changed object. If more than one object has changed, + * -1 is returned. + */ + public int updatedObjectIndex() { + return updatedObjectIndex; + } + + // getting and setting values in objects + + /** + * Returns a value on the selected object for the specified key. + */ + public Object selectedObjectValueForKey(String aKey) { + Object selectedObject = selectedObject(); + if (selectedObject == null) + return null; + return valueForObject(selectedObject, aKey); + } + + /** + * Sets the specified value for the specified key on all selected objects. + */ + public boolean setSelectedObjectValue(Object aValue, String aKey) { + Object selectedObject = selectedObject(); + if (selectedObject == null) + return false; + return setValueForObject(aValue, selectedObject, aKey); + } + + /** + * Sets the specified value for the specified key on the specified object. + * Validations may be triggered, and error dialogs may appear to the user. + * + * @return True if the value was set successfully, false if the value could not + * be set and the update operation should not continue. + */ + public boolean setValueForObject(Object aValue, Object anObject, String aKey) { + // notify object's observers: + // this includes us, and will notify our observers + EOObserverCenter.notifyObserversObjectWillChange(anObject); + + // TODO: if key is null, need to remove old object + // and add new object instead of simply replacing it. + + try { + if (anObject instanceof EOKeyValueCoding) { + ((EOKeyValueCoding) anObject).takeValueForKey(aValue, aKey); + } else { + EOKeyValueCodingSupport.takeValueForKey(anObject, aValue, aKey); + } + } catch (RuntimeException exc) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { WODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", exc.getMessage() }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + throw exc; + } + return false; + } + + notifyDelegate("displayGroupDidSetValueForObject", + new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class }, + new Object[] { this, aValue, anObject, aKey }); + + return true; + } + + /** + * Calls setValueForObject() for the object at the specified index. + */ + public boolean setValueForObjectAtIndex(Object aValue, int anIndex, String aKey) { + return setValueForObject(aValue, displayedObjects.objectAtIndex(anIndex), aKey); + } + + /** + * Returns the value for the specified key on the specified object. + */ + public Object valueForObject(Object anObject, String aKey) { + // empty string is considered the identity property + if (aKey == null) + return anObject; + if (aKey.equals("")) + return anObject; + + try { + if (anObject instanceof EOKeyValueCoding) { + return ((EOKeyValueCoding) anObject).valueForKey(aKey); + } else { + return EOKeyValueCodingSupport.valueForKey(anObject, aKey); + } + } catch (RuntimeException exc) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { WODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", exc.getMessage() }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + throw exc; + } + return null; + } + } + + /** + * Calls valueForObject() for the object at the specified index. Returns null if + * out of bounds. + */ + public Object valueForObjectAtIndex(int anIndex, String aKey) { + if (displayedObjects.count() <= anIndex) + return null; + Object o = displayedObjects.objectAtIndex(anIndex); + return valueForObject(o, aKey); + } + + /** + * Prints out the list of displayed objects. + */ + public String toString() { + return displayedObjects.toString(); + } + + /** + * Handles notifications from the data source's editing context, looking for + * InvalidatedAllObjectsInStoreNotification and + * ObjectsChangedInEditingContextNotification, refetching in the former case and + * updating displayed objects in the latter. Note: This method is not in the + * public specification. + */ + public void objectsInvalidatedInEditingContext(NSNotification aNotification) { + if (EOObjectStore.InvalidatedAllObjectsInStoreNotification.equals(aNotification.name())) { + Object result = notifyDelegate("displayGroupShouldRefetch", + new Class[] { WODisplayGroup.class, NSNotification.class }, new Object[] { this, aNotification }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + fetch(); + } + } else if (EOEditingContext.ObjectsChangedInEditingContextNotification.equals(aNotification.name())) { + Object result = notifyDelegate("displayGroupShouldRedisplay", + new Class[] { WODisplayGroup.class, NSNotification.class }, new Object[] { this, aNotification }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + int index; + Enumeration e; + boolean didChange = false; + NSDictionary userInfo = aNotification.userInfo(); + + // inserts are ignored + + // mark updated objects as updated + NSArray updates = (NSArray) userInfo.objectForKey(EOObjectStore.UpdatedKey); + e = updates.objectEnumerator(); + while (e.hasMoreElements()) { + index = indexOf(displayedObjects, e.nextElement()); + if (index != NSArray.NotFound) { + // System.out.println( "WODisplayGroup: updated: " + index ); + if (!didChange) { + didChange = true; + willChange(); + updatedObjectIndex = index; + } else { + updatedObjectIndex = -1; + } + } + } + + // treat invalidated objects as updated + NSArray invalidates = (NSArray) userInfo.objectForKey(EOObjectStore.InvalidatedKey); + e = invalidates.objectEnumerator(); + while (e.hasMoreElements()) { + index = indexOf(displayedObjects, e.nextElement()); + if (index != NSArray.NotFound) { + // System.out.println( "WODisplayGroup: invalidated: " + index ); + if (!didChange) { + didChange = true; + willChange(); + updatedObjectIndex = index; + } else { + updatedObjectIndex = -1; + } + } + } + + // remove deletes from display group if they exist + NSArray deletes = (NSArray) userInfo.objectForKey(EOObjectStore.DeletedKey); + e = deletes.objectEnumerator(); + Object o; + while (e.hasMoreElements()) { + o = e.nextElement(); + index = indexOf(displayedObjects, o); + if (index != NSArray.NotFound) { + // System.out.println( "WODisplayGroup: deleted: " + o ); + deleteObjectAtIndexNoNotify(index); + } + } + + if (!usesOptimisticRefresh()) { + updateDisplayedObjects(); + } + } + } + + } + + // static methods + + /** + * Specifies the default behavior for whether changes should be validated + * immediately for all display groups. + */ + public static boolean globalDefaultForValidatesChangesImmediately() { + return globalDefaultForValidatesChangesImmediately; + } + + /** + * Specifies the default string matching format for all display groups. + */ + public static String globalDefaultStringMatchFormat() { + return globalDefaultStringMatchFormat; + } + + /** + * Specifies the default string matching operator for all display groups. + */ + public static String globalDefaultStringMatchOperator() { + return globalDefaultStringMatchOperator; + } + + /** + * Sets the default behavior for validating changes for all display groups. + */ + public static void setGlobalDefaultForValidatesChangesImmediately(boolean validatesImmediately) { + globalDefaultForValidatesChangesImmediately = validatesImmediately; + } + + /** + * Sets the default string matching format that will be used by all display + * groups. + */ + public static void setGlobalDefaultStringMatchFormat(String aFormat) { + globalDefaultStringMatchFormat = aFormat; + } + + /** + * Sets the default string matching operator that will be used by all display + * groups. + */ + public static void setGlobalDefaultStringMatchOperator(String anOperator) { + globalDefaultStringMatchOperator = anOperator; + } + + /** + * Needed because we don't inherit from NSObject. Calls + * EOObserverCenter.notifyObserversObjectWillChange. + */ + protected void willChange() { + EOObserverCenter.notifyObserversObjectWillChange(this); + } + + /** + * Returns the index of the specified object in the specified NSArray, comparing + * by value or by reference as determined by the private instance variable + * compareByReference. If not found, returns NSArray.NotFound. + */ + private int indexOf(NSArray anArray, Object anObject) { + if (compareByReference) { + return anArray.indexOfIdenticalObject(anObject); + } else { + return anArray.indexOf(anObject); + } + } + + // interface EOObserving + + /** + * Receives notifications of changes from objects that are managed by this + * display group. This implementation sets updatedObjectIndex as appropriate. + */ + public void objectWillChange(Object anObject) { + int index = indexOf(displayedObjects, anObject); + if (index != NSArray.NotFound) { + updatedObjectIndex = index; + willChange(); + } + } + + // interface EOEditingContext.Editor + + /** + * Called before the editing context begins to save changes. This implementation + * calls endEditing(). + */ + public void editingContextWillSaveChanges(EOEditingContext anEditingContext) { + endEditing(); + } + + /** + * Called to determine whether this editor has changes that have not been + * committed to the object in the context. This implementation returns false. + */ + public boolean editorHasChangesForEditingContext(EOEditingContext anEditingContext) { + return false; + } + + // interface EOEditingContext.MessageHandler + + /** + * Called to display a message for an error that occurred in the specified + * editing context. If the delegate allows, this implementation writes a message + * to the standard output. Override to customize. + */ + public void editingContextPresentErrorMessage(EOEditingContext anEditingContext, String aMessage) { + Object result = notifyDelegate("displayGroupShouldDisplayAlert", + new Class[] { WODisplayGroup.class, String.class, String.class }, + new Object[] { this, "Error", aMessage }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + System.out.println(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. This implementation returns true. + * Override to customize. + */ + public boolean editingContextShouldContinueFetching(EOEditingContext anEditingContext, int count, int limit, + EOObjectStore anObjectStore) { + return true; + } + + /** + * 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; + } + + /** + * DisplayGroups can delegate important decisions to a Delegate. Note that + * DisplayGroup doesn't require its delegates to implement this interface: + * rather, this interface defines the methods that DisplayGroup 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 when the specified data source fails to create an object for the + * specified display group. + */ + void displayGroupCreateObjectFailed(WODisplayGroup aDisplayGroup, EODataSource aDataSource); + + /** + * Called after the specified display group's data source is changed. + */ + void displayGroupDidChangeDataSource(WODisplayGroup aDisplayGroup); + + /** + * Called after a change occurs in the specified display group's selected + * objects. + */ + void displayGroupDidChangeSelectedObjects(WODisplayGroup aDisplayGroup); + + /** + * Called after the specified display group's selection has changed. + */ + void displayGroupDidChangeSelection(WODisplayGroup aDisplayGroup); + + /** + * Called after the specified display group has deleted the specified object. + */ + void displayGroupDidDeleteObject(WODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called after the specified display group has fetched the specified object + * list. + */ + void displayGroupDidFetchObjects(WODisplayGroup aDisplayGroup, List anObjectList); + + /** + * Called after the specified display group has inserted the specified object + * into its internal object list. + */ + void displayGroupDidInsertObject(WODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called after the specified display group has set the specified value for the + * specified object and key. + */ + void displayGroupDidSetValueForObject(WODisplayGroup aDisplayGroup, Object aValue, Object anObject, + String aKey); + + /** + * Called by the specified display group to determine what objects should be + * displayed for the objects in the specified list. + * + * @return An NSArray containing the objects to be displayed for the objects in + * the specified list. + */ + NSArray displayGroupDisplayArrayForObjects(WODisplayGroup aDisplayGroup, List aList); + + /** + * Called by the specified display group before it attempts to change the + * selection. + * + * @return True to allow the selection to change, false otherwise. + */ + boolean displayGroupShouldChangeSelection(WODisplayGroup aDisplayGroup, List aSelectionList); + + /** + * Called by the specified display group before it attempts to delete the + * specified object. + * + * @return True to allow the object to be deleted false to prevent the deletion. + */ + boolean displayGroupShouldDeleteObject(WODisplayGroup aDisplayGroup, Object anObject); + + /** + * Called by the specified display group before it attempts display the + * specified alert to the user. + * + * @return True to allow the message to be displayed, false if you want to + * handle the alert yourself and suppress the display group's + * notification. + */ + boolean displayGroupShouldDisplayAlert(WODisplayGroup aDisplayGroup, String aTitle, String aMessage); + + /** + * Called by the specified display group before it attempts fetch objects. + * + * @return True to allow the fetch to take place, false to prevent the fetch. + */ + boolean displayGroupShouldFetch(WODisplayGroup aDisplayGroup); + + /** + * Called by the specified display group before it attempts to insert the + * specified object. + * + * @return True to allow the object to be inserted false to prevent the + * insertion. + */ + boolean displayGroupShouldInsertObject(WODisplayGroup aDisplayGroup, Object anObject, int anIndex); + + /** + * Called by the specified display group when it receives the specified + * ObjectsChangedInEditingContextNotification. + * + * @return True to allow the display group to update the display (recommended), + * false to prevent the update. + */ + boolean displayGroupShouldRedisplay(WODisplayGroup aDisplayGroup, NSNotification aNotification); + + /** + * Called by the specified display group when it receives the specified + * InvalidatedAllObjectsInStoreNotification. + * + * @return True to allow the display group to refetch (recommended), false to + * prevent the refetch. + */ + boolean displayGroupShouldRefetch(WODisplayGroup aDisplayGroup, NSNotification aNotification); + + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/08/07 00:15:15 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.6 2003/08/07 00:15:15 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.5 2003/01/22 23:01:06 mpowers - * Better handling for index out of bounds. + * Revision 1.5 2003/01/22 23:01:06 mpowers Better handling for index out of + * bounds. * - * Revision 1.4 2003/01/21 22:27:02 mpowers - * Corrected context id usage. + * Revision 1.4 2003/01/21 22:27:02 mpowers Corrected context id usage. * Implemented backtracking. * - * Revision 1.3 2003/01/21 17:54:01 mpowers - * Batch indices are one-based, not zero-based. + * Revision 1.3 2003/01/21 17:54:01 mpowers Batch indices are one-based, not + * zero-based. * - * Revision 1.2 2003/01/21 14:38:42 mpowers - * Fixed batching. + * Revision 1.2 2003/01/21 14:38:42 mpowers Fixed batching. * - * Revision 1.1 2003/01/18 23:30:42 mpowers - * WODisplayGroup now compiles. + * Revision 1.1 2003/01/18 23:30:42 mpowers WODisplayGroup now compiles. * - * Revision 1.46 2002/10/24 21:15:36 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.46 2002/10/24 21:15:36 mpowers New implementations of NSArray and + * subclasses. * - * Revision 1.45 2002/10/24 18:20:20 mpowers - * Because NSArray is read-only, we are returning our internal representations - * to callers of allObjects(), displayedObjects(), and selectedObjects(). + * Revision 1.45 2002/10/24 18:20:20 mpowers Because NSArray is read-only, we + * are returning our internal representations to callers of allObjects(), + * displayedObjects(), and selectedObjects(). * - * Revision 1.44 2002/08/06 18:20:25 mpowers - * Now posting DisplayGroupWillFetch notifications before fetch. - * Implemented support for usesOptimisticRefresh. - * No longer supporting inserted/updated/deleted lists: not part of spec. + * Revision 1.44 2002/08/06 18:20:25 mpowers Now posting DisplayGroupWillFetch + * notifications before fetch. Implemented support for usesOptimisticRefresh. No + * longer supporting inserted/updated/deleted lists: not part of spec. * - * Revision 1.43 2002/05/17 15:01:49 mpowers - * Implemented dynamic lookup of delegate methods so delegates no longer - * need to implement the DisplayGroup.Delegate interface. + * Revision 1.43 2002/05/17 15:01:49 mpowers Implemented dynamic lookup of + * delegate methods so delegates no longer need to implement the + * DisplayGroup.Delegate interface. * - * Revision 1.42 2002/03/26 21:46:06 mpowers - * Contributing EditingContext as a java-friendly convenience. + * Revision 1.42 2002/03/26 21:46:06 mpowers Contributing EditingContext as a + * java-friendly convenience. * - * Revision 1.41 2002/03/11 03:17:56 mpowers - * Provided control point for coalesced changes. + * Revision 1.41 2002/03/11 03:17:56 mpowers Provided control point for + * coalesced changes. * - * Revision 1.40 2002/03/05 23:18:28 mpowers - * Added documentation. - * Added isSelectionPaintedImmediate and isSelectionTracking attributes - * to TableAssociation. - * Added getTableAssociation to TableColumnAssociation. + * Revision 1.40 2002/03/05 23:18:28 mpowers Added documentation. Added + * isSelectionPaintedImmediate and isSelectionTracking attributes to + * TableAssociation. Added getTableAssociation to TableColumnAssociation. * - * Revision 1.39 2002/02/19 22:26:04 mpowers - * Implemented EOEditingContext.MessageHandler support. + * Revision 1.39 2002/02/19 22:26:04 mpowers Implemented + * EOEditingContext.MessageHandler support. * - * Revision 1.38 2002/02/19 16:37:38 mpowers - * Implemented support for EOEditingContext.Editor + * Revision 1.38 2002/02/19 16:37:38 mpowers Implemented support for + * EOEditingContext.Editor * - * Revision 1.37 2001/12/11 22:17:48 mpowers - * Now properly handling exceptions in valueForObject. - * No longer trying to retain selection based only on index. + * Revision 1.37 2001/12/11 22:17:48 mpowers Now properly handling exceptions in + * valueForObject. No longer trying to retain selection based only on index. * - * Revision 1.36 2001/11/08 21:42:00 mpowers - * Now we know what to do with shouldRefetch and shouldRedisplay. + * Revision 1.36 2001/11/08 21:42:00 mpowers Now we know what to do with + * shouldRefetch and shouldRedisplay. * - * Revision 1.35 2001/11/04 18:26:58 mpowers - * Fixed bug where exceptions were not properly reported when updating - * a value and the display group did not have a delegate. + * Revision 1.35 2001/11/04 18:26:58 mpowers Fixed bug where exceptions were not + * properly reported when updating a value and the display group did not have a + * delegate. * - * Revision 1.34 2001/11/02 20:59:36 mpowers - * Now correctly ensuring selected objects are a subset of displayed objects. + * Revision 1.34 2001/11/02 20:59:36 mpowers Now correctly ensuring selected + * objects are a subset of displayed objects. * - * Revision 1.33 2001/10/30 22:56:45 mpowers - * Added support for EOQualifier. + * Revision 1.33 2001/10/30 22:56:45 mpowers Added support for EOQualifier. * - * Revision 1.32 2001/10/23 22:27:53 mpowers - * Now running at ObserverPrioritySixth. + * Revision 1.32 2001/10/23 22:27:53 mpowers Now running at + * ObserverPrioritySixth. * - * Revision 1.31 2001/10/23 18:45:05 mpowers - * Rolling back changes. + * Revision 1.31 2001/10/23 18:45:05 mpowers Rolling back changes. * - * Revision 1.28 2001/08/22 19:23:41 mpowers - * No longer asserting objects in all objects list. + * Revision 1.28 2001/08/22 19:23:41 mpowers No longer asserting objects in all + * objects list. * - * Revision 1.27 2001/07/30 16:17:01 mpowers - * Minor code cleanup. + * Revision 1.27 2001/07/30 16:17:01 mpowers Minor code cleanup. * - * Revision 1.26 2001/07/10 22:49:07 mpowers - * Fixed bug in optimization for selectObjectsIdenticalTo (found by Dongzhi). + * Revision 1.26 2001/07/10 22:49:07 mpowers Fixed bug in optimization for + * selectObjectsIdenticalTo (found by Dongzhi). * - * Revision 1.25 2001/06/19 15:40:21 mpowers - * Now only changing the selection if the new selection is different - * from the old. + * Revision 1.25 2001/06/19 15:40:21 mpowers Now only changing the selection if + * the new selection is different from the old. * - * Revision 1.24 2001/05/24 17:36:15 mpowers - * Fixed problem with selectedObjectsIdenticalTo: it was using compare - * by value instead of compare by reference. + * Revision 1.24 2001/05/24 17:36:15 mpowers Fixed problem with + * selectedObjectsIdenticalTo: it was using compare by value instead of compare + * by reference. * - * Revision 1.23 2001/05/18 21:09:19 mpowers - * Now throwing exceptions if the delegate cannot handle error from update. + * Revision 1.23 2001/05/18 21:09:19 mpowers Now throwing exceptions if the + * delegate cannot handle error from update. * - * Revision 1.22 2001/05/14 15:26:12 mpowers - * Now checking for null delegate before and after selection change. + * Revision 1.22 2001/05/14 15:26:12 mpowers Now checking for null delegate + * before and after selection change. * - * Revision 1.21 2001/05/08 18:47:34 mpowers - * Minor fixes for d3. + * Revision 1.21 2001/05/08 18:47:34 mpowers Minor fixes for d3. * - * Revision 1.20 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. + * Revision 1.20 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. * - * Revision 1.19 2001/04/13 16:38:09 mpowers - * Alpha3 release. + * Revision 1.19 2001/04/13 16:38:09 mpowers Alpha3 release. * - * Revision 1.18 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.18 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.17 2001/03/29 03:31:13 mpowers - * No longer using Introspector. + * Revision 1.17 2001/03/29 03:31:13 mpowers No longer using Introspector. * - * Revision 1.16 2001/02/27 03:32:18 mpowers - * Implemented default values for new objects. + * Revision 1.16 2001/02/27 03:32:18 mpowers Implemented default values for new + * objects. * - * Revision 1.15 2001/02/27 02:11:17 mpowers - * Now throwing exception when cloning fails. - * Removed debugging printlns. + * Revision 1.15 2001/02/27 02:11:17 mpowers Now throwing exception when cloning + * fails. Removed debugging printlns. * - * Revision 1.14 2001/02/26 22:41:51 mpowers - * Implemented null placeholder classes. - * Duplicator now uses NSNull. - * No longer catching base exception class. + * Revision 1.14 2001/02/26 22:41:51 mpowers Implemented null placeholder + * classes. Duplicator now uses NSNull. No longer catching base exception class. * - * Revision 1.13 2001/02/26 15:53:22 mpowers - * Fine-tuning notification firing. + * Revision 1.13 2001/02/26 15:53:22 mpowers Fine-tuning notification firing. * Child display groups now update properly after parent save or invalidate. * - * Revision 1.12 2001/02/22 20:55:06 mpowers - * Implemented notification handling. + * Revision 1.12 2001/02/22 20:55:06 mpowers Implemented notification handling. * - * Revision 1.11 2001/02/21 20:40:42 mpowers - * setObjectArray now falls back to index when trying to retain the - * same selection. + * Revision 1.11 2001/02/21 20:40:42 mpowers setObjectArray now falls back to + * index when trying to retain the same selection. * - * Revision 1.10 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.10 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.9 2001/02/17 17:23:49 mpowers - * More changes to support compiling with jdk1.1 collections. + * Revision 1.9 2001/02/17 17:23:49 mpowers More changes to support compiling + * with jdk1.1 collections. * - * Revision 1.8 2001/02/17 16:52:05 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.8 2001/02/17 16:52:05 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.7 2001/01/24 16:35:37 mpowers - * Improved documentation on TreeAssociation. - * SortOrderings are now inherited from parent nodes. - * Updates after sorting are still lost on TreeController. + * Revision 1.7 2001/01/24 16:35:37 mpowers Improved documentation on + * TreeAssociation. SortOrderings are now inherited from parent nodes. Updates + * after sorting are still lost on TreeController. * - * Revision 1.6 2001/01/24 14:23:05 mpowers - * Added support for OrderedDataSource. + * Revision 1.6 2001/01/24 14:23:05 mpowers Added support for OrderedDataSource. * - * Revision 1.5 2001/01/12 17:21:37 mpowers - * Implicit creation of EOSortOrderings now happens in setSortOrderings. + * Revision 1.5 2001/01/12 17:21:37 mpowers Implicit creation of EOSortOrderings + * now happens in setSortOrderings. * - * Revision 1.4 2001/01/11 20:34:26 mpowers - * Implemented EOSortOrdering and added support in framework. - * Added header-click to sort table columns. + * Revision 1.4 2001/01/11 20:34:26 mpowers Implemented EOSortOrdering and added + * support in framework. Added header-click to sort table columns. * - * Revision 1.3 2001/01/10 22:49:44 mpowers - * Implemented similarly named selection methods instead of - * throwing exceptions. + * Revision 1.3 2001/01/10 22:49:44 mpowers Implemented similarly named + * selection methods instead of throwing exceptions. * - * Revision 1.2 2001/01/09 20:12:52 mpowers - * Moved inner classes to package access. + * Revision 1.2 2001/01/09 20:12:52 mpowers Moved inner classes to package + * access. * - * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers Contributing wotonomy. * - * Revision 1.21 2000/12/20 16:25:39 michael - * Added log to all files. + * Revision 1.21 2000/12/20 16:25:39 michael Added log to all files. * - * Revision 1.20 2000/12/15 15:04:42 michael - * Added doc. + * Revision 1.20 2000/12/15 15:04:42 michael Added doc. * - * Revision 1.19 2000/12/11 13:32:48 michael - * Finish the much better TreeAssociation implementation. - * TreeAssociation now has no gui dependencies. + * Revision 1.19 2000/12/11 13:32:48 michael Finish the much better + * TreeAssociation implementation. TreeAssociation now has no gui dependencies. * - * Revision 1.18 2000/12/05 17:41:46 michael - * Broadcasts selection change after delegate refuses selection change - * so the initiating association gets refreshed. + * Revision 1.18 2000/12/05 17:41:46 michael Broadcasts selection change after + * delegate refuses selection change so the initiating association gets + * refreshed. * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java index 6e449c3..6abb7d6 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java @@ -27,182 +27,175 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; /** -* The base class for dynamic WOElements. Dynamic elements -* are expected to do something useful with user-entered data -* in the request and with any binding associations with the -* context's current WOComponent. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public abstract class WODynamicElement - extends WOElement -{ + * The base class for dynamic WOElements. Dynamic elements are expected to do + * something useful with user-entered data in the request and with any binding + * associations with the context's current WOComponent. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public abstract class WODynamicElement extends WOElement { protected String name; protected WOElement rootElement; - protected NSDictionary associations; - - /** - * The default constructor. - */ - protected WODynamicElement () - { - name = null; - associations = new NSMutableDictionary(); - rootElement = null; - } - - /** - * Required constructor specifying the class name of the component, - * a map of associations, and the root element of the tree that - * contains this element (which may be null). The map keys - * correspond to properties of this element, and the values are - * associations to be applied to the context's current component. - */ - public WODynamicElement ( - String aName, NSDictionary anAssociationMap, WOElement aRootElement) - { - this(); - name = aName; - associations = anAssociationMap; - rootElement = aRootElement; - } - - /** - * Package access only. Called to initialize the component with - * the proper context before the start of the request-response cycle. - * If the context has a current component, that component becomes - * this component's parent. - */ - void ensureAwakeInContext (WOContext aContext) - { - if ( rootElement != null ) - { - rootElement.ensureAwakeInContext( aContext ); - } - } - - /** - * Use this method to get a map with the properties that start with - * a question mark. These are supposed to go at the end of a URL, and it is - * very useful for components that generate URLs, specially with direct - * actions. - * @param c The component where the values of the properties have to be - * retrieved from. - */ - Map urlFields(WOComponent c) { - HashMap map = new HashMap(associations.count()); - Enumeration enumeration = associations.keyEnumerator(); - while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); - if (key.charAt(0) == '?') { - map.put(key.substring(1), valueForProperty(key, c)); - } - } - return map; - } - - /** Convenience method for getting the value of an association. */ - Object valueForProperty(String key, WOComponent c) { - WOAssociation a = (WOAssociation)associations.objectForKey(key); - if (a != null) - return a.valueInComponent(c); - return null; - } - - /** Convenience method for getting the string value of an association. */ - String stringForProperty(String key, WOComponent c) { - WOAssociation a = (WOAssociation)associations.objectForKey(key); - Object result = null; - if (a != null) result = a.valueInComponent(c); - if ( result == null ) return null; - return result.toString(); - } - - /** Convenience method for getting the string value of an association. */ - boolean booleanForProperty(String key, WOComponent c) { - WOAssociation a = (WOAssociation)associations.objectForKey(key); - Object result = null; - if (a != null) result = a.valueInComponent(c); - if ( result == null ) return false; - if ( result.toString().toLowerCase().equals( "true" ) ) return true; - return Boolean.TRUE.equals( result ); - } - - /** Convenience method for setting the value of an association. */ - void setValueForProperty(String key, Object value, WOComponent c) { - WOAssociation a = (WOAssociation)associations.objectForKey(key); - if ( a != null && a.isValueSettable() ) - a.setValue(value, c); - } - - /** this method composes a String suitable for inclusion inside a HTML tag. It includes - the key-value pairs of all the associations not mentioned in the standardProperties - parameter. This is very useful for including extra properties in tags without having to worry - if the HTML specification has changed or if non-standard tags are being used. - @param c The component where the associations' values should be retrieved from. - @param standardProperties An array of Strings with all the associations that should be - excluded from the resulting string. */ - String additionalHTMLProperties(WOComponent c, NSArray standardProperties) { - Enumeration enumeration = associations.keyEnumerator(); - StringBuffer buf = new StringBuffer(); - while (enumeration.hasMoreElements()) { - String key = (String)enumeration.nextElement(); - if (!(standardProperties.containsObject(key) || key.charAt(0)=='?')) { - buf.append(' '); - buf.append(key); - buf.append("=\""); - buf.append(valueForProperty(key, c)); - buf.append('\"'); - } - } - return buf.toString(); - } - - /** - * This method is called to retrieve user-entered data from - * the request. WOElements should retrieve data from the - * request based on their elementID and set values in the - * context's current WOComponent, typically those values that - * are associated with the element in the binding. This - * implementation does nothing. - */ - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - + protected NSDictionary associations; + + /** + * The default constructor. + */ + protected WODynamicElement() { + name = null; + associations = new NSMutableDictionary(); + rootElement = null; + } + + /** + * Required constructor specifying the class name of the component, a map of + * associations, and the root element of the tree that contains this element + * (which may be null). The map keys correspond to properties of this element, + * and the values are associations to be applied to the context's current + * component. + */ + public WODynamicElement(String aName, NSDictionary anAssociationMap, WOElement aRootElement) { + this(); + name = aName; + associations = anAssociationMap; + rootElement = aRootElement; + } + + /** + * Package access only. Called to initialize the component with the proper + * context before the start of the request-response cycle. If the context has a + * current component, that component becomes this component's parent. + */ + void ensureAwakeInContext(WOContext aContext) { + if (rootElement != null) { + rootElement.ensureAwakeInContext(aContext); + } + } + + /** + * Use this method to get a map with the properties that start with a question + * mark. These are supposed to go at the end of a URL, and it is very useful for + * components that generate URLs, specially with direct actions. + * + * @param c The component where the values of the properties have to be + * retrieved from. + */ + Map urlFields(WOComponent c) { + HashMap map = new HashMap(associations.count()); + Enumeration enumeration = associations.keyEnumerator(); + while (enumeration.hasMoreElements()) { + String key = (String) enumeration.nextElement(); + if (key.charAt(0) == '?') { + map.put(key.substring(1), valueForProperty(key, c)); + } + } + return map; + } + + /** Convenience method for getting the value of an association. */ + Object valueForProperty(String key, WOComponent c) { + WOAssociation a = (WOAssociation) associations.objectForKey(key); + if (a != null) + return a.valueInComponent(c); + return null; + } + + /** Convenience method for getting the string value of an association. */ + String stringForProperty(String key, WOComponent c) { + WOAssociation a = (WOAssociation) associations.objectForKey(key); + Object result = null; + if (a != null) + result = a.valueInComponent(c); + if (result == null) + return null; + return result.toString(); + } + + /** Convenience method for getting the string value of an association. */ + boolean booleanForProperty(String key, WOComponent c) { + WOAssociation a = (WOAssociation) associations.objectForKey(key); + Object result = null; + if (a != null) + result = a.valueInComponent(c); + if (result == null) + return false; + if (result.toString().toLowerCase().equals("true")) + return true; + return Boolean.TRUE.equals(result); } - /** - * This method is called on all objects and elements of the - * application until a non-null value is returned. - * WOElements should first check to see if they are the - * target of an action by checking the WOContext's senderID - * to see if it matches this element's elementID. - * If this element is the target, it should perform an - * appropriate action on the context's current WOComponent, - * usually the action specified in the binding, and return - * the result of that action. This implementation returns null. - */ - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - return null; - } - - /** - * This method is called on all elements of the content tree - * to build a response to a user request. The message should - * be forwarded to any child elements so that the entire tree - * is traversed. This implementation does nothing. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - // does nothing - } - - public WOResponse generateResponse() - { - return null; - } + /** Convenience method for setting the value of an association. */ + void setValueForProperty(String key, Object value, WOComponent c) { + WOAssociation a = (WOAssociation) associations.objectForKey(key); + if (a != null && a.isValueSettable()) + a.setValue(value, c); + } + + /** + * this method composes a String suitable for inclusion inside a HTML tag. It + * includes the key-value pairs of all the associations not mentioned in the + * standardProperties parameter. This is very useful for including extra + * properties in tags without having to worry if the HTML specification has + * changed or if non-standard tags are being used. + * + * @param c The component where the associations' values should + * be retrieved from. + * @param standardProperties An array of Strings with all the associations that + * should be excluded from the resulting string. + */ + String additionalHTMLProperties(WOComponent c, NSArray standardProperties) { + Enumeration enumeration = associations.keyEnumerator(); + StringBuffer buf = new StringBuffer(); + while (enumeration.hasMoreElements()) { + String key = (String) enumeration.nextElement(); + if (!(standardProperties.containsObject(key) || key.charAt(0) == '?')) { + buf.append(' '); + buf.append(key); + buf.append("=\""); + buf.append(valueForProperty(key, c)); + buf.append('\"'); + } + } + return buf.toString(); + } + + /** + * This method is called to retrieve user-entered data from the request. + * WOElements should retrieve data from the request based on their elementID and + * set values in the context's current WOComponent, typically those values that + * are associated with the element in the binding. This implementation does + * nothing. + */ + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + + } + + /** + * This method is called on all objects and elements of the application until a + * non-null value is returned. WOElements should first check to see if they are + * the target of an action by checking the WOContext's senderID to see if it + * matches this element's elementID. If this element is the target, it should + * perform an appropriate action on the context's current WOComponent, usually + * the action specified in the binding, and return the result of that action. + * This implementation returns null. + */ + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + return null; + } + + /** + * This method is called on all elements of the content tree to build a response + * to a user request. The message should be forwarded to any child elements so + * that the entire tree is traversed. This implementation does nothing. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + // does nothing + } + + public WOResponse generateResponse() { + return null; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java index 11944d3..184eeea 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java @@ -23,75 +23,62 @@ import java.io.Serializable; import net.wotonomy.foundation.NSDictionary; /** -* This class represents a static or dynamic portion of the -* content returned to a request. Each request walks a tree -* of WOElements to generate a response. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public abstract class WOElement implements WOActionResults, Serializable -{ + * This class represents a static or dynamic portion of the content returned to + * a request. Each request walks a tree of WOElements to generate a response. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public abstract class WOElement implements WOActionResults, Serializable { NSDictionary associations; - + + /** + * Default constructor. Performs necessary initialization. + */ + public WOElement() { + } + /** - * Default constructor. Performs necessary initialization. - */ - public WOElement() - { + * This method is called to retrieve user-entered data from the request. + * WOElements should retrieve data from the request based on their elementID and + * set values in the context's current WOComponent, typically those values that + * are associated with the element in the binding. This implementation does + * nothing. + */ + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + // does nothing } /** - * This method is called to retrieve user-entered data from - * the request. WOElements should retrieve data from the - * request based on their elementID and set values in the - * context's current WOComponent, typically those values that - * are associated with the element in the binding. This - * implementation does nothing. - */ - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - // does nothing - } + * This method is called on all objects and elements of the application until a + * non-null value is returned. WOElements should first check to see if they are + * the target of an action by checking the WOContext's senderID to see if it + * matches this element's elementID. If this element is the target, it should + * perform an appropriate action on the context's current WOComponent, usually + * the action specified in the binding, and return the result of that action. + * This implementation returns null. + */ + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + return null; + } - /** - * This method is called on all objects and elements of the - * application until a non-null value is returned. - * WOElements should first check to see if they are the - * target of an action by checking the WOContext's senderID - * to see if it matches this element's elementID. - * If this element is the target, it should perform an - * appropriate action on the context's current WOComponent, - * usually the action specified in the binding, and return - * the result of that action. This implementation returns null. - */ - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - return null; - } - - /** - * This method is called on all elements of the content tree - * to build a response to a user request. The message should - * be forwarded to any child elements so that the entire tree - * is traversed. This implementation does nothing. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - // does nothing - } - - /** - * Package access only. Called to initialize the component with - * the proper context before the start of the request-response cycle. - * If the context has a current component, that component becomes - * this component's parent. - */ - void ensureAwakeInContext (WOContext aContext) - { - // does nothing - } + /** + * This method is called on all elements of the content tree to build a response + * to a user request. The message should be forwarded to any child elements so + * that the entire tree is traversed. This implementation does nothing. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + // does nothing + } + + /** + * Package access only. Called to initialize the component with the proper + * context before the start of the request-response cycle. If the context has a + * current component, that component becomes this component's parent. + */ + void ensureAwakeInContext(WOContext aContext) { + // does nothing + } - } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java index 887d1a3..b4dca91 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java @@ -21,104 +21,101 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; -/** -* Implements a FORM element with dynamic bindings. -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ +/** + * Implements a FORM element with dynamic bindings. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOForm extends WODynamicElement { - public WOForm() { - super(); - } - - public WOForm(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } - - public String href(WOContext c) { - return (String)valueForProperty("href", c.component()); - } - - public String directActionName(WOContext c) { - return (String)valueForProperty("directAction", c.component()); - } - - public String directActionClass(WOContext c) { - return (String)valueForProperty("actionClass", c.component()); - } - - public boolean multipleSubmit(WOContext c) { - return booleanForProperty("multipleSubmit", c.component()); - } - - public void takeValuesFromRequest ( - WORequest aRequest, WOContext aContext) - { - if ( rootElement != null ) - { - rootElement.takeValuesFromRequest( aRequest, aContext ); - } - } - - public void appendToResponse(WOResponse r, WOContext c) { - //Append the opening tag - r.appendContentString(" 0) - r.appendContentString(link); - r.appendContentString(">"); - //Notify that we're inside a form now - c.setInForm(true); - //Append the inner template - rootElement.appendToResponse(r, c); - //Close the tag - r.appendContentString(""); + // Append any additional properties + link = additionalHTMLProperties(c.component(), + new NSArray(new Object[] { "href", "action", "directActionName", "actionClass", "multipleSubmit" })); + if (link.length() > 0) + r.appendContentString(link); + r.appendContentString(">"); + // Notify that we're inside a form now + c.setInForm(true); + // Append the inner template + rootElement.appendToResponse(r, c); + // Close the tag + r.appendContentString(""); // c.deleteLastElementIDComponent(); - c.setInForm(false); - } - - public WOActionResults invokeAction(WORequest r, WOContext c) { - //We only process the request if it's not a multipleSubmit (otherwise we leave it to the buttons) - if (!multipleSubmit(c) - && c.elementID().equals(c.senderID()) - && associations.objectForKey("action") != null ) { - return (WOActionResults)valueForProperty("action", c.component()); - } - WOActionResults res = rootElement.invokeAction(r, c); - return res; - } + c.setInForm(false); + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + // We only process the request if it's not a multipleSubmit (otherwise we leave + // it to the buttons) + if (!multipleSubmit(c) && c.elementID().equals(c.senderID()) && associations.objectForKey("action") != null) { + return (WOActionResults) valueForProperty("action", c.component()); + } + WOActionResults res = rootElement.invokeAction(r, c); + return res; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java index 30dd4bc..06f4b7f 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java @@ -6,55 +6,54 @@ import net.wotonomy.foundation.NSDictionary; public class WOFrame extends WODynamicElement { - public WOFrame() { - super(); - } - - public WOFrame(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } - - public String frameName(WOContext c) { - String x = (String)valueForProperty("name", c.component()); - if (x != null) - return x; - return c.elementID(); - } - - public String url(WOContext c) { - //Check if the href property is set - String href = stringForProperty("href", c.component()); - if (href != null) - return href; - href = stringForProperty("pageName", c.component()); - if (href != null || associations.objectForKey("action") != null) { //write this component's URL - return c.componentActionURL(); - } - href = stringForProperty("directActionName", c.component()); - if (href != null) { //compose the direct action URL - String fullActionName = stringForProperty("actionClass", c.component()); - if (fullActionName != null) - fullActionName = fullActionName + "/" + href; - else - fullActionName = href; - return c.directActionURLForActionNamed(fullActionName, - urlFields(c.component())); - } - //Coded needed here to support filename/framework and data/mimeType. - return null; - } - - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(" 0) - r.appendContentString(moreFields); - r.appendContentString(">"); - } + public WOFrame() { + super(); + } + + public WOFrame(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } + + public String frameName(WOContext c) { + String x = (String) valueForProperty("name", c.component()); + if (x != null) + return x; + return c.elementID(); + } + + public String url(WOContext c) { + // Check if the href property is set + String href = stringForProperty("href", c.component()); + if (href != null) + return href; + href = stringForProperty("pageName", c.component()); + if (href != null || associations.objectForKey("action") != null) { // write this component's URL + return c.componentActionURL(); + } + href = stringForProperty("directActionName", c.component()); + if (href != null) { // compose the direct action URL + String fullActionName = stringForProperty("actionClass", c.component()); + if (fullActionName != null) + fullActionName = fullActionName + "/" + href; + else + fullActionName = href; + return c.directActionURLForActionNamed(fullActionName, urlFields(c.component())); + } + // Coded needed here to support filename/framework and data/mimeType. + return null; + } + + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(" 0) + r.appendContentString(moreFields); + r.appendContentString(">"); + } } \ No newline at end of file diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java index 9af5460..b883b0f 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java @@ -21,41 +21,42 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** - * Used to dynamically generate HTML containers (elements with opening and closing tags and something in between). + * Used to dynamically generate HTML containers (elements with opening and + * closing tags and something in between). + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOGenericContainer extends WOGenericElement { - public WOGenericContainer() { - super(); - } - - public WOGenericContainer(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } - - public void appendToResponse(WOResponse r, WOContext c) { - super.appendToResponse(r, c); - rootElement.appendToResponse(r, c); - r.appendContentString(""); - } - - public void takeValuesFromRequest(WORequest r, WOContext c) { - super.takeValuesFromRequest( r, c ); - rootElement.takeValuesFromRequest(r, c); - } - - public WOActionResults invokeAction(WORequest r, WOContext c) { - WOActionResults result = super.invokeAction( r, c ); - if ( result == null ) - { - result = rootElement.invokeAction(r, c); - } - return result; - } + public WOGenericContainer() { + super(); + } + + public WOGenericContainer(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } + + public void appendToResponse(WOResponse r, WOContext c) { + super.appendToResponse(r, c); + rootElement.appendToResponse(r, c); + r.appendContentString(""); + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + super.takeValuesFromRequest(r, c); + rootElement.takeValuesFromRequest(r, c); + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + WOActionResults result = super.invokeAction(r, c); + if (result == null) { + result = rootElement.invokeAction(r, c); + } + return result; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java index 8894428..92d1b42 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java @@ -22,74 +22,70 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** - * Used to generate any HTML element dynamically. It only creates the opening tag without a closing tag. - * To generate HTML elements that have opening and closing tags, as well as some content in between, - * use WOGenericContainer instead. + * Used to generate any HTML element dynamically. It only creates the opening + * tag without a closing tag. To generate HTML elements that have opening and + * closing tags, as well as some content in between, use WOGenericContainer + * instead. + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOGenericElement extends WODynamicElement { - static NSArray bindings = new NSArray( new Object[] - { "elementName", "omitTags", "elementID", "otherTagString", - "formValue", "formValues", "invokeAction" } ); - - public WOGenericElement() { - super(); - } + static NSArray bindings = new NSArray(new Object[] { "elementName", "omitTags", "elementID", "otherTagString", + "formValue", "formValues", "invokeAction" }); - public WOGenericElement(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOGenericElement() { + super(); + } - public String elementName(WOContext c) { - String x = (String)valueForProperty("elementName", c.component()); - if (x != null) - return x; - return c.elementID(); - } + public WOGenericElement(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void takeValuesFromRequest(WORequest r, WOContext c) { - if ( c.elementID().equals( c.senderID() ) ) - { - Object value; - value = r.formValueForKey( c.elementID() ); - setValueForProperty( "formValue", value, c.component() ); - value = r.formValuesForKey( c.elementID() ); - setValueForProperty( "formValues", value, c.component() ); - } - } + public String elementName(WOContext c) { + String x = (String) valueForProperty("elementName", c.component()); + if (x != null) + return x; + return c.elementID(); + } - public WOActionResults invokeAction(WORequest r, WOContext c) - { - WOActionResults result = null; - String action = stringForProperty( "invokeAction", c.component() ); - if ( action != null && c.elementID().equals( c.senderID() ) ) - { - result = c.component().performAction( action ); - } - return result; - } - - public void appendToResponse(WOResponse r, WOContext c) { - WOComponent component = c.component(); - if ( !booleanForProperty( "omitTags", component ) ) - { - r.appendContentString("<"); - r.appendContentString(elementName(c)); - String other = stringForProperty( "otherTagString", component ); - if ( other != null ) - { - r.appendContentString( " " ); - r.appendContentString( other ); - } - String add = additionalHTMLProperties(component, bindings); - if (add.length() > 0) - r.appendContentString(add); - r.appendContentString(">"); - } - setValueForProperty( "elementID", c.elementID(), component ); - } + public void takeValuesFromRequest(WORequest r, WOContext c) { + if (c.elementID().equals(c.senderID())) { + Object value; + value = r.formValueForKey(c.elementID()); + setValueForProperty("formValue", value, c.component()); + value = r.formValuesForKey(c.elementID()); + setValueForProperty("formValues", value, c.component()); + } + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + WOActionResults result = null; + String action = stringForProperty("invokeAction", c.component()); + if (action != null && c.elementID().equals(c.senderID())) { + result = c.component().performAction(action); + } + return result; + } + + public void appendToResponse(WOResponse r, WOContext c) { + WOComponent component = c.component(); + if (!booleanForProperty("omitTags", component)) { + r.appendContentString("<"); + r.appendContentString(elementName(c)); + String other = stringForProperty("otherTagString", component); + if (other != null) { + r.appendContentString(" "); + r.appendContentString(other); + } + String add = additionalHTMLProperties(component, bindings); + if (add.length() > 0) + r.appendContentString(add); + r.appendContentString(">"); + } + setValueForProperty("elementID", c.elementID(), component); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java index c5d5711..ca4e6c2 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java @@ -21,22 +21,23 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** -* Used to dynamically generate a hidden field within a form. + * Used to dynamically generate a hidden field within a form. + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOHiddenField extends WOTextField { - public WOHiddenField() { - super(); - } + public WOHiddenField() { + super(); + } - public WOHiddenField(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOHiddenField(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void takeValuesFromRequest(WORequest r, WOContext c) { - } + public void takeValuesFromRequest(WORequest r, WOContext c) { + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java index d0f3ff7..e2041d5 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java @@ -25,26 +25,30 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** -* WOHyperlink renders a dynamically generated hyperlink in the output. - * Bindings are: + * WOHyperlink renders a dynamically generated hyperlink in the output. Bindings + * are: *
        - *
      • string: a string to be included between the hyperlink tags (optional).
      • + *
      • string: a string to be included between the hyperlink tags + * (optional).
      • *
      • escapeHTML: a property returning a value convertable to a Boolean - * indicating whether the any html characters in the output should be - * escaped so they are shown as html characters rather than interpreted - * as html.
      • + * indicating whether the any html characters in the output should be escaped so + * they are shown as html characters rather than interpreted as html. *
      • href: The URL that the hyperlink should point to.
      • - *
      • pageName: The name of the WOComponent that the hyperlink should point to.
      • - *
      • directActionName: The name of the direct action to call when the link is activated.
      • - *
      • actionClass: The name of the WODirectAction subclass where the direct action resides.
      • + *
      • pageName: The name of the WOComponent that the hyperlink should point + * to.
      • + *
      • directActionName: The name of the direct action to call when the link is + * activated.
      • + *
      • actionClass: The name of the WODirectAction subclass where the direct + * action resides.
      • *
      • anchorName: The name of the link, for anchor tags.
      • - *
      • action: A pointer to a method on the component that contains this element. If the link is activated, - * the method will be called. + *
      • action: A pointer to a method on the component that contains this + * element. If the link is activated, the method will be called. *
      • ref: The name of the anchor to go to inside the resulting page.
      • *
      * - * The href, pageName and directActionName/actionClass and name properties are mutually exclusive and you should - * only use at most one of them simultaneously. + * The href, pageName and directActionName/actionClass and name properties are + * mutually exclusive and you should only use at most one of them + * simultaneously. * * @author ezamudio@nasoft.com * @author $Author: cgruber $ @@ -52,198 +56,202 @@ import net.wotonomy.foundation.NSDictionary; */ public class WOHyperlink extends WODynamicElement { - protected String string; - protected String href; - protected String pageName; - protected String directActionName; - protected String actionClass; - protected String action; - protected boolean escapeHTML; - protected String anchorName; - protected String ref; - - protected WOHyperlink() { - super(); - } - - public WOHyperlink(String aName, NSDictionary aMap, WOElement aRootElement) { - super(aName, aMap, aRootElement); - escapeHTML = true; - } - - public void setString(String value) { - string = value; - } - public String string() { - return string; - } - - public void setHref(String value) { - href = value; - } - public String href() { - return href; - } - - public void setAnchorName(String value) { - anchorName = value; - } - public String anchorName() { - return anchorName; - } - - public void setPageName(String value) { - pageName = value; - } - public String pageName() { - return pageName; - } - - public void setDirectActionName(String value) { - directActionName = value; - } - public String directActionName() { - return directActionName; - } - - public void setActionClass(String value) { - actionClass = value; - } - public String actionClass() { - return actionClass; - } - - /** Sets the escapeHTML property. */ - public void setEscapeHTML(boolean escape) { - escapeHTML = escape; - } - - /** If true, inserts escape codes in to the string string so - * that HTML special characters (greater-than, less-than, etc.) - * appear correctly. If false, those characters will get - * interpreted by the browser. Defaults to true. - */ - public boolean escapeHTML() { - return escapeHTML; - } - - public String actionURL(WOContext c) { - //Check if the href property is set - if (href() != null) { - return href(); - } else if (pageName() != null || associations.objectForKey("action") != null) { //write this component's URL - StringBuffer retval = new StringBuffer(c.componentActionURL()); - Map addFields = urlFields(c.component()); - if (addFields.size() > 0) { - Iterator enumeration = addFields.keySet().iterator(); - retval.append('?'); - while (enumeration.hasNext()) { - String encoding = c.response() != null ? c.response().contentEncoding() : c.request().contentEncoding(); - String key = (String)enumeration.next(); - try { + protected String string; + protected String href; + protected String pageName; + protected String directActionName; + protected String actionClass; + protected String action; + protected boolean escapeHTML; + protected String anchorName; + protected String ref; + + protected WOHyperlink() { + super(); + } + + public WOHyperlink(String aName, NSDictionary aMap, WOElement aRootElement) { + super(aName, aMap, aRootElement); + escapeHTML = true; + } + + public void setString(String value) { + string = value; + } + + public String string() { + return string; + } + + public void setHref(String value) { + href = value; + } + + public String href() { + return href; + } + + public void setAnchorName(String value) { + anchorName = value; + } + + public String anchorName() { + return anchorName; + } + + public void setPageName(String value) { + pageName = value; + } + + public String pageName() { + return pageName; + } + + public void setDirectActionName(String value) { + directActionName = value; + } + + public String directActionName() { + return directActionName; + } + + public void setActionClass(String value) { + actionClass = value; + } + + public String actionClass() { + return actionClass; + } + + /** Sets the escapeHTML property. */ + public void setEscapeHTML(boolean escape) { + escapeHTML = escape; + } + + /** + * If true, inserts escape codes in to the string string so that HTML + * special characters (greater-than, less-than, etc.) appear correctly. If + * false, those characters will get interpreted by the browser. Defaults to + * true. + */ + public boolean escapeHTML() { + return escapeHTML; + } + + public String actionURL(WOContext c) { + // Check if the href property is set + if (href() != null) { + return href(); + } else if (pageName() != null || associations.objectForKey("action") != null) { // write this component's URL + StringBuffer retval = new StringBuffer(c.componentActionURL()); + Map addFields = urlFields(c.component()); + if (addFields.size() > 0) { + Iterator enumeration = addFields.keySet().iterator(); + retval.append('?'); + while (enumeration.hasNext()) { + String encoding = c.response() != null ? c.response().contentEncoding() + : c.request().contentEncoding(); + String key = (String) enumeration.next(); + try { retval.append(java.net.URLEncoder.encode(key, encoding)); - } catch (java.io.UnsupportedEncodingException ex) { - retval.append(key); - } - retval.append("="); + } catch (java.io.UnsupportedEncodingException ex) { + retval.append(key); + } + retval.append("="); try { retval.append(java.net.URLEncoder.encode(addFields.get(key).toString(), encoding)); } catch (java.io.UnsupportedEncodingException e) { retval.append(addFields.get(key).toString()); } - if (enumeration.hasNext()) - retval.append('&'); - } - } - return retval.toString(); - } else if (directActionName() != null) { //compose the direct action URL - String fullActionName = null; - if (actionClass() != null ) - fullActionName = actionClass() + "/" + directActionName(); - else - fullActionName = directActionName(); - return c.directActionURLForActionNamed(fullActionName, urlFields(c.component())); - } - return null; - } - - protected void pullValuesFromParent(WOComponent c) { - string = stringForProperty("string", c); - href = stringForProperty("href", c); - pageName = stringForProperty("pageName", c); - directActionName = stringForProperty("directActionName", c); - actionClass = stringForProperty("actionClass", c); - //action = stringForProperty("action", c); - escapeHTML = booleanForProperty("escapeHTML", c); - anchorName = stringForProperty("anchorName", c); - ref = stringForProperty("ref", c); - } - - public void appendToResponse(WOResponse r, WOContext c) { - pullValuesFromParent( c.component() ); - r.appendContentString(""); - //Append the string if present - if (string() != null) { - if (escapeHTML()) - r.appendContentHTMLString(string()); - else - r.appendContentString(string()); - } - //If there is a template, call appendToResponse on it - if (rootElement != null) { - rootElement.appendToResponse(r, c); - } - //Close the tag - r.appendContentString(""); - } - - public WOActionResults invokeAction(WORequest r, WOContext c) { - System.out.println("invoke action with elementID=" + c.elementID() + " senderID=" + c.senderID()); - //Check if this element is the target - if (c.senderID().equals(c.elementID())) { - if (pageName() != null) - { - return WOApplication.application().pageWithName(pageName(), r); - } - else - { - WOAssociation ass = (WOAssociation) associations.objectForKey("action"); - if ( ass != null && ass.path != null ) //?? - return (WOActionResults)c.component().performAction( ass.path ); - } - } - return null; - } - - public void takeValuesFromRequest(WORequest r, WOContext c) { - System.out.println("takeValuesFromRequest elementID=" + c.elementID() + " senderID=" + c.senderID()); - super.takeValuesFromRequest(r, c); - } + if (enumeration.hasNext()) + retval.append('&'); + } + } + return retval.toString(); + } else if (directActionName() != null) { // compose the direct action URL + String fullActionName = null; + if (actionClass() != null) + fullActionName = actionClass() + "/" + directActionName(); + else + fullActionName = directActionName(); + return c.directActionURLForActionNamed(fullActionName, urlFields(c.component())); + } + return null; + } + + protected void pullValuesFromParent(WOComponent c) { + string = stringForProperty("string", c); + href = stringForProperty("href", c); + pageName = stringForProperty("pageName", c); + directActionName = stringForProperty("directActionName", c); + actionClass = stringForProperty("actionClass", c); + // action = stringForProperty("action", c); + escapeHTML = booleanForProperty("escapeHTML", c); + anchorName = stringForProperty("anchorName", c); + ref = stringForProperty("ref", c); + } + + public void appendToResponse(WOResponse r, WOContext c) { + pullValuesFromParent(c.component()); + r.appendContentString(""); + // Append the string if present + if (string() != null) { + if (escapeHTML()) + r.appendContentHTMLString(string()); + else + r.appendContentString(string()); + } + // If there is a template, call appendToResponse on it + if (rootElement != null) { + rootElement.appendToResponse(r, c); + } + // Close the tag + r.appendContentString(""); + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + System.out.println("invoke action with elementID=" + c.elementID() + " senderID=" + c.senderID()); + // Check if this element is the target + if (c.senderID().equals(c.elementID())) { + if (pageName() != null) { + return WOApplication.application().pageWithName(pageName(), r); + } else { + WOAssociation ass = (WOAssociation) associations.objectForKey("action"); + if (ass != null && ass.path != null) // ?? + return (WOActionResults) c.component().performAction(ass.path); + } + } + return null; + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + System.out.println("takeValuesFromRequest elementID=" + c.elementID() + " senderID=" + c.senderID()); + super.takeValuesFromRequest(r, c); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java index 2673cd1..3f830b0 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java @@ -6,145 +6,146 @@ import net.wotonomy.foundation.NSData; import net.wotonomy.foundation.NSDictionary; /** -* WOImage renders a dynamically generated IMG tag. The URL for the image SRC can be -* static, or it can be generated dynamically to return a NSData object (with a mime-type) -* or even return the contents of a file (not implemented yet). -* -* Bindings are: -*
      • src: A static URL for the image source.
      • -*
      • data: A NSData object with the image content. Must be used with mimeType.
      • -*
      • mimeType: The MIME type for the image data. Can be used with filename or data bindings.
      • -*
      • filename: The path to a file containing an image.
      • -*
      • framework: The optional framework from whence the image should be retrieved (used in conjunction with filename).
      • -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ + * WOImage renders a dynamically generated IMG tag. The URL for the image SRC + * can be static, or it can be generated dynamically to return a NSData object + * (with a mime-type) or even return the contents of a file (not implemented + * yet). + * + * Bindings are: + *
          + *
        • src: A static URL for the image source.
        • + *
        • data: A NSData object with the image content. Must be used with + * mimeType.
        • + *
        • mimeType: The MIME type for the image data. Can be used with filename or + * data bindings.
        • + *
        • filename: The path to a file containing an image.
        • + *
        • framework: The optional framework from whence the image should be + * retrieved (used in conjunction with filename).
        • + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOImage extends WODynamicElement { - protected String src; - protected String filename; - protected String framework; - protected NSData data; - protected String mimeType; - - protected WOImage() { - super(); - } - - public WOImage(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } - - public void setSrc(String value) { - src = value; - } - public String src() { - return src; - } - - public void setFilename(String value) { - filename = value; - } - - public String filename() { - return filename; - } - - public void setFramework(String value) { - framework = value; - } - public String framework() { - return framework; - } - - public void setData(NSData value) { - data = value; - } - public NSData data() { - return data; - } - - public void setMimeType(String value) { - mimeType = value; - } - public String mimeType() { - return mimeType(); - } - - public String sourceURL(WOContext c) { - if (associations.objectForKey("src") != null) - return (String)valueForProperty("src", c.component()); - if (associations.objectForKey("data") != null) { - return c.componentActionURL(); - } - if (associations.objectForKey("filename") != null) { - WOComponent component = c.component(); - - String framework = stringForProperty("framework", component); - String filename = stringForProperty( "filename", component ); - if ( filename != null && framework == null ) - { - if ( filename.startsWith("/" ) ) - { - int i = filename.lastIndexOf( "/" ); - if ( i > 0 ) - { - framework = filename.substring( 0, i ); - if ( i < filename.length() ) - { - filename = filename.substring( i+1 ); - } - } - } - else - { - // just until we figure out how we're handling bundles/localization - framework = component.frameworkName(); - if ( framework != null ) - { - framework = framework + '/' + component.name() + ".wo"; - } - else - { - framework = '/' + component.name() + ".wo"; - } - } - } - return WOApplication.application().resourceManager().urlForResourceNamed( - filename, framework, c.request().browserLanguages(), c.request() ); - } - return "NO SOURCE"; - } - - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(""); - } - - public WOActionResults invokeAction(WORequest r, WOContext c) { - if (c.senderID().equals(c.elementID())) { - Object data = valueForProperty("data", c.component()); - if (data instanceof byte[]) data = new NSData( (byte[]) data ); - if (data instanceof NSData) { - String mt = stringForProperty("mimeType", c.component()); - if (mt == null) - throw new IllegalArgumentException("WOImage: No mimeType specified for data."); - WOResponse img = new WOResponse(); - img.setContent((NSData)data); - img.setHeader(mt, "content-type"); - return img; - } else if (filename() != null) { - //will this thing use frameworks, or regular JAR files with resources? - // mp: both/either, depending on which resource manager implementation - } - } - return null; - } + protected String src; + protected String filename; + protected String framework; + protected NSData data; + protected String mimeType; + + protected WOImage() { + super(); + } + + public WOImage(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } + + public void setSrc(String value) { + src = value; + } + + public String src() { + return src; + } + + public void setFilename(String value) { + filename = value; + } + + public String filename() { + return filename; + } + + public void setFramework(String value) { + framework = value; + } + + public String framework() { + return framework; + } + + public void setData(NSData value) { + data = value; + } + + public NSData data() { + return data; + } + + public void setMimeType(String value) { + mimeType = value; + } + + public String mimeType() { + return mimeType(); + } + + public String sourceURL(WOContext c) { + if (associations.objectForKey("src") != null) + return (String) valueForProperty("src", c.component()); + if (associations.objectForKey("data") != null) { + return c.componentActionURL(); + } + if (associations.objectForKey("filename") != null) { + WOComponent component = c.component(); + + String framework = stringForProperty("framework", component); + String filename = stringForProperty("filename", component); + if (filename != null && framework == null) { + if (filename.startsWith("/")) { + int i = filename.lastIndexOf("/"); + if (i > 0) { + framework = filename.substring(0, i); + if (i < filename.length()) { + filename = filename.substring(i + 1); + } + } + } else { + // just until we figure out how we're handling bundles/localization + framework = component.frameworkName(); + if (framework != null) { + framework = framework + '/' + component.name() + ".wo"; + } else { + framework = '/' + component.name() + ".wo"; + } + } + } + return WOApplication.application().resourceManager().urlForResourceNamed(filename, framework, + c.request().browserLanguages(), c.request()); + } + return "NO SOURCE"; + } + + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(""); + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + if (c.senderID().equals(c.elementID())) { + Object data = valueForProperty("data", c.component()); + if (data instanceof byte[]) + data = new NSData((byte[]) data); + if (data instanceof NSData) { + String mt = stringForProperty("mimeType", c.component()); + if (mt == null) + throw new IllegalArgumentException("WOImage: No mimeType specified for data."); + WOResponse img = new WOResponse(); + img.setContent((NSData) data); + img.setHeader(mt, "content-type"); + return img; + } else if (filename() != null) { + // will this thing use frameworks, or regular JAR files with resources? + // mp: both/either, depending on which resource manager implementation + } + } + return null; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java index 7c9f22e..ebb9ae1 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java @@ -5,17 +5,21 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** - * WOImageButton renders a dynamically generated IMG tag or an INPUT tag, depending on whether the - * element is inside a WOForm (in which case an INPUT of type IMAGE is generated) or not (in which - * case the equivalent of a WOActiveImage) + * WOImageButton renders a dynamically generated IMG tag or an INPUT tag, + * depending on whether the element is inside a WOForm (in which case an INPUT + * of type IMAGE is generated) or not (in which case the equivalent of a + * WOActiveImage) * * Bindings are: *
            *
          • src: A static URL for the image source.
          • - *
          • data: A NSData object with the image content. Must be used with mimeType.
          • - *
          • mimeType: The MIME type for the image data. Can be used with filename or data bindings.
          • + *
          • data: A NSData object with the image content. Must be used with + * mimeType.
          • + *
          • mimeType: The MIME type for the image data. Can be used with filename or + * data bindings.
          • *
          • filename: The path to a file containing an image.
          • - *
          • framework: The framework where the image should be retrieved from (used in conjunction with filename).
          • + *
          • framework: The framework where the image should be retrieved from (used + * in conjunction with filename).
          • * * @author ezamudio@nasoft.com * @author $Author: cgruber $ @@ -23,36 +27,36 @@ import net.wotonomy.foundation.NSDictionary; */ public class WOImageButton extends WOImage { - protected WOImageButton() { - super(); - } - - public WOImageButton(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } - - public String buttonName(WOContext c) { - String x = (String)valueForProperty("name", c.component()); - if (x != null) - return x; - return c.elementID(); - } - - public void appendToResponse(WOResponse r, WOContext c) { - if (c.isInForm()) { - //generate an INPUT - r.appendContentString(""); - } else { - //generate a WOActiveImage - new WOActiveImage(name, associations, rootElement).appendToResponse(r, c); - } - } + protected WOImageButton() { + super(); + } + + public WOImageButton(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } + + public String buttonName(WOContext c) { + String x = (String) valueForProperty("name", c.component()); + if (x != null) + return x; + return c.elementID(); + } + + public void appendToResponse(WOResponse r, WOContext c) { + if (c.isInForm()) { + // generate an INPUT + r.appendContentString(""); + } else { + // generate a WOActiveImage + new WOActiveImage(name, associations, rootElement).appendToResponse(r, c); + } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java index a8c7daa..ddcdb9e 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java @@ -10,74 +10,75 @@ import net.wotonomy.foundation.NSMutableArray; public abstract class WOInput extends WODynamicElement { - public WOInput() { - super(); - } - - public WOInput(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } - - protected abstract String inputType(); - protected abstract Object value(WOContext c); - - protected NSMutableArray additionalAttributes() { - return new NSMutableArray(new Object[]{ - "disabled", "type", "value", "name" - }); - } - - public String inputName(WOContext c) { - String x = (String)valueForProperty("name", c.component()); - if (x != null) - return x; - return c.elementID(); - } - - protected boolean disabled(WOContext c) { - return booleanForProperty("disabled", c.component()); - } - - protected void appendExtras(WOResponse r, WOContext c) { - } - - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(" 0) - r.appendContentString(moreFields); - appendExtras(r, c); - if (disabled(c)) { - r.appendContentString(" DISABLED"); - } - r.appendContentString(">"); - } - - public void takeValuesFromRequest(WORequest r, WOContext c) { - if (disabled(c)) - return; - Object val = r.formValueForKey(inputName(c)); - WOAssociation va = (WOAssociation)associations.objectForKey("value"); - if (val != null && va != null && va.isValueSettable()) - setValueForProperty("value", val, c.component()); - } - - /** Formats a value as a date or number. Checks for - * numberformat or dateformat associations; if one of them - * exists, the value is formatter using the specified pattern. + public WOInput() { + super(); + } + + public WOInput(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } + + protected abstract String inputType(); + + protected abstract Object value(WOContext c); + + protected NSMutableArray additionalAttributes() { + return new NSMutableArray(new Object[] { "disabled", "type", "value", "name" }); + } + + public String inputName(WOContext c) { + String x = (String) valueForProperty("name", c.component()); + if (x != null) + return x; + return c.elementID(); + } + + protected boolean disabled(WOContext c) { + return booleanForProperty("disabled", c.component()); + } + + protected void appendExtras(WOResponse r, WOContext c) { + } + + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(" 0) + r.appendContentString(moreFields); + appendExtras(r, c); + if (disabled(c)) { + r.appendContentString(" DISABLED"); + } + r.appendContentString(">"); + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + if (disabled(c)) + return; + Object val = r.formValueForKey(inputName(c)); + WOAssociation va = (WOAssociation) associations.objectForKey("value"); + if (val != null && va != null && va.isValueSettable()) + setValueForProperty("value", val, c.component()); + } + + /** + * Formats a value as a date or number. Checks for numberformat or dateformat + * associations; if one of them exists, the value is formatter using the + * specified pattern. + * * @param value The value to format. - * @return The original object, or a date or number if the - * receiver has a numberformat or dateformat association. + * @return The original object, or a date or number if the receiver has a + * numberformat or dateformat association. */ protected Object formattedValue(Object value, WOComponent c) { - //Format the value in case of number - String pattern = (String)valueForProperty("numberformat", c); + // Format the value in case of number + String pattern = (String) valueForProperty("numberformat", c); if (pattern != null) { DecimalFormat fmt = new DecimalFormat(pattern); try { @@ -86,8 +87,8 @@ public abstract class WOInput extends WODynamicElement { return value; } } - //Format the value in case of date - pattern = (String)valueForProperty("dateformat", c); + // Format the value in case of date + pattern = (String) valueForProperty("dateformat", c); if (pattern != null) { SimpleDateFormat fmt = new SimpleDateFormat(pattern); try { diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java index eccc489..69939f9 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java @@ -21,34 +21,30 @@ package net.wotonomy.web; import java.util.List; /** -* A pure java implementation of WOMailDelivery. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public abstract class WOMailDelivery -{ + * A pure java implementation of WOMailDelivery. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public abstract class WOMailDelivery { private static WOMailDelivery sharedInstance; - - protected WOMailDelivery () - { - } - - public static WOMailDelivery sharedInstance () - { - if ( sharedInstance == null ) - { + + protected WOMailDelivery() { + } + + public static WOMailDelivery sharedInstance() { + if (sharedInstance == null) { // sharedInstance = new WOMailDelivery(); - } - return sharedInstance; - } - - public abstract String composePlainTextEmail ( - String aSender, List aToList, List aCcList, - String aSubject, String aMessage, boolean sendImmediately ); - public abstract String composeComponentEmail ( - String aSender, List aToList, List aCcList, - String aSubject, WOComponent aComponent, boolean sendImmediately ); - public abstract void sendEmail (String aMailMessage); + } + return sharedInstance; + } + + public abstract String composePlainTextEmail(String aSender, List aToList, List aCcList, String aSubject, + String aMessage, boolean sendImmediately); + + public abstract String composeComponentEmail(String aSender, List aToList, List aCcList, String aSubject, + WOComponent aComponent, boolean sendImmediately); + + public abstract void sendEmail(String aMailMessage); } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java index be76be1..f2310c2 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java @@ -24,12 +24,12 @@ import net.wotonomy.foundation.NSMutableData; import net.wotonomy.foundation.NSMutableDictionary; /** -* A pure java implementation of WOResponse. -* -* @author ezamudio@nasoft.com -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ + * A pure java implementation of WOResponse. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOMessage { protected String _contentEncoding = "ISO8859_1"; @@ -43,291 +43,233 @@ public class WOMessage { } /** - * Sets the content encoding for the response. - */ - public void setContentEncoding (String encoding) - { + * Sets the content encoding for the response. + */ + public void setContentEncoding(String encoding) { _contentEncoding = encoding; } /** - * Gets the current content encoding for the response. - */ - public String contentEncoding () { + * Gets the current content encoding for the response. + */ + public String contentEncoding() { return _contentEncoding; - } + } /** - * Sets the specified array of values as headers under - * the specified key. - */ - public void setHeaders (NSArray headerArray, String aKey) { - _headers.setObjectForKey( headerArray, aKey ); + * Sets the specified array of values as headers under the specified key. + */ + public void setHeaders(NSArray headerArray, String aKey) { + _headers.setObjectForKey(headerArray, aKey); } /** - * Sets the specified header value for the specified key. - */ - public void setHeader (String aValue, String aKey) { - _headers.setObjectForKey( new NSArray( aValue ), aKey ); + * Sets the specified header value for the specified key. + */ + public void setHeader(String aValue, String aKey) { + _headers.setObjectForKey(new NSArray(aValue), aKey); } /** - * Returns an array of all the header keys that have been - * set in the response. - */ - public NSArray headerKeys () { + * Returns an array of all the header keys that have been set in the response. + */ + public NSArray headerKeys() { return _headers.allKeys(); } /** - * Returns an array of all the header values for the specified key, - * or null if the key does not exist. - */ - public NSArray headersForKey (String aKey) { - return (NSArray)_headers.objectForKey( aKey ); + * Returns an array of all the header values for the specified key, or null if + * the key does not exist. + */ + public NSArray headersForKey(String aKey) { + return (NSArray) _headers.objectForKey(aKey); } /** - * Returns one header value for the specified key. - * Provided as a convenience, since most header keys - * will have a single value. - */ - public String headerForKey (String aKey) { - NSArray values = (NSArray)_headers.objectForKey( aKey ); - if ( values != null && values.count() > 0 ) { - return values.objectAtIndex( 0 ).toString(); + * Returns one header value for the specified key. Provided as a convenience, + * since most header keys will have a single value. + */ + public String headerForKey(String aKey) { + NSArray values = (NSArray) _headers.objectForKey(aKey); + if (values != null && values.count() > 0) { + return values.objectAtIndex(0).toString(); } return null; } /** - * Sets the content of the response to the bytes represented - * by the specified data object. - */ + * Sets the content of the response to the bytes represented by the specified + * data object. + */ public void setContent(NSData aData) { - _contentData.setData( aData ); + _contentData.setData(aData); setHeader(Integer.toString(aData.length()), "content-length"); } /** - * Retrieves the current content of the response. - */ + * Retrieves the current content of the response. + */ public NSData content() { return _contentData; } /** - * Sets the current user info dictionary. These values - * are for application-specific uses and are available to - * other actions and components in the request-response cycle. - */ - public void setUserInfo (NSDictionary aDict) { - _userInfo = new NSMutableDictionary( aDict ); + * Sets the current user info dictionary. These values are for + * application-specific uses and are available to other actions and components + * in the request-response cycle. + */ + public void setUserInfo(NSDictionary aDict) { + _userInfo = new NSMutableDictionary(aDict); } /** - * Gets the current user info dictionary. These values - * are for application-specific uses are are available to - * other actions and components in the request-response cycle. - */ - public NSDictionary userInfo () { + * Gets the current user info dictionary. These values are for + * application-specific uses are are available to other actions and components + * in the request-response cycle. + */ + public NSDictionary userInfo() { return new NSDictionary(_userInfo); } /** - * Appends the bytes in the specified data object to the response. - */ - public void appendContentData (NSData aData) - { - _contentData.appendData( aData ); + * Appends the bytes in the specified data object to the response. + */ + public void appendContentData(NSData aData) { + _contentData.appendData(aData); setHeader(Integer.toString(_contentData.length()), "content-length"); } /** - * Appends the specified byte to the response. - */ - public void appendContentCharacter (char character) { - _contentData.appendByte((byte)character); + * Appends the specified byte to the response. + */ + public void appendContentCharacter(char character) { + _contentData.appendByte((byte) character); setHeader(Integer.toString(_contentData.length()), "content-length"); } /** - * Appends the specified string to the response. - * Any special characters will not be escaped. - * The string will be encoded in the current content encoding. - */ - public void appendContentString (String aString) - { - _contentData.appendData( new NSData( aString.getBytes() ) ); + * Appends the specified string to the response. Any special characters will not + * be escaped. The string will be encoded in the current content encoding. + */ + public void appendContentString(String aString) { + _contentData.appendData(new NSData(aString.getBytes())); setHeader(Integer.toString(_contentData.length()), "content-length"); } /** - * Appends the specified string containing HTML to the response. - * Any special characters will be escaped appropriately. - * The string will be encoded in the current content encoding. - */ - public void appendContentHTMLString (String aString) - { - _contentData.appendData( - new NSData( stringByEscapingHTMLString( - aString ).getBytes() ) ); + * Appends the specified string containing HTML to the response. Any special + * characters will be escaped appropriately. The string will be encoded in the + * current content encoding. + */ + public void appendContentHTMLString(String aString) { + _contentData.appendData(new NSData(stringByEscapingHTMLString(aString).getBytes())); setHeader(Integer.toString(_contentData.length()), "content-length"); } /** - * Appends the specified string containing HTML to the response. - * Any special characters will be escaped appropriately. - * This method escapes tabs and new-line characters as well. - * The string will be encoded in the current content encoding. - */ - public void appendContentHTMLAttributeValue (String aString) - { - _contentData.appendData ( - new NSData( stringByEscapingHTMLAttributeValue( - aString ).getBytes() ) ); + * Appends the specified string containing HTML to the response. Any special + * characters will be escaped appropriately. This method escapes tabs and + * new-line characters as well. The string will be encoded in the current + * content encoding. + */ + public void appendContentHTMLAttributeValue(String aString) { + _contentData.appendData(new NSData(stringByEscapingHTMLAttributeValue(aString).getBytes())); setHeader(Integer.toString(_contentData.length()), "content-length"); } /** - * Adds the specified cookie to the response. - */ - public void addCookie (WOCookie aCookie) - { - _cookies.setObjectForKey( aCookie, aCookie.name() ); + * Adds the specified cookie to the response. + */ + public void addCookie(WOCookie aCookie) { + _cookies.setObjectForKey(aCookie, aCookie.name()); } /** - * Removes the specified cookie from the response. - */ - public void removeCookie (WOCookie aCookie) - { - _cookies.removeObjectForKey( aCookie.name() ); + * Removes the specified cookie from the response. + */ + public void removeCookie(WOCookie aCookie) { + _cookies.removeObjectForKey(aCookie.name()); } /** - * Returns an array of cookies currently being sent with the response. - * Contains whatever cookies have previously been set in this response. - */ - public NSArray cookies () - { - return _cookies.allValues(); + * Returns an array of cookies currently being sent with the response. Contains + * whatever cookies have previously been set in this response. + */ + public NSArray cookies() { + return _cookies.allValues(); } /** - * Sets the HTTP version header in the response. - */ - public void setHTTPVersion (String aString) - { - setHeader( aString, "Protocol"); + * Sets the HTTP version header in the response. + */ + public void setHTTPVersion(String aString) { + setHeader(aString, "Protocol"); } /** - * Gets the current HTTP version header for the response. - * Because servlet responses do not allow read access - * to headers, this method returns null if setHTTPVersion - * has not been called. - */ - public String httpVersion () - { - return headerForKey( "Protocol" ); + * Gets the current HTTP version header for the response. Because servlet + * responses do not allow read access to headers, this method returns null if + * setHTTPVersion has not been called. + */ + public String httpVersion() { + return headerForKey("Protocol"); } /** - * Returns a sting containing the contents of the specified - * string after escaping all special HTML characters. - */ - public static String stringByEscapingHTMLString - (String aString) - { + * Returns a sting containing the contents of the specified string after + * escaping all special HTML characters. + */ + public static String stringByEscapingHTMLString(String aString) { int len = aString.length(); StringBuffer result = new StringBuffer(); - char[] buf = new char[ len ]; - aString.getChars( 0, len, buf, 0 ); - for ( int i = 0; i < len; i++ ) - { - if ( buf[i] == '&' ) - { - result.append( "&" ); - } - else - if ( buf[i] == '\\' ) - { - result.append( """ ); - } - else - if ( buf[i] == '<' ) - { - result.append( "<" ); + char[] buf = new char[len]; + aString.getChars(0, len, buf, 0); + for (int i = 0; i < len; i++) { + if (buf[i] == '&') { + result.append("&"); + } else if (buf[i] == '\\') { + result.append("""); + } else if (buf[i] == '<') { + result.append("<"); + } else if (buf[i] == '>') { + result.append(">"); + } else { + result.append(buf[i]); } - else - if ( buf[i] == '>' ) - { - result.append( ">" ); - } - else - { - result.append( buf[i] ); - } - } - return result.toString(); + } + return result.toString(); } - + /** - * Returns a sting containing the contents of the specified - * string after escaping all special HTML characters. - * This method escapes tabs and new-line characters as well. - */ - public static String stringByEscapingHTMLAttributeValue - (String aString) - { + * Returns a sting containing the contents of the specified string after + * escaping all special HTML characters. This method escapes tabs and new-line + * characters as well. + */ + public static String stringByEscapingHTMLAttributeValue(String aString) { int len = aString.length(); StringBuffer result = new StringBuffer(); - char[] buf = new char[ len ]; - aString.getChars( 0, len, buf, 0 ); - for ( int i = 0; i < len; i++ ) - { - if ( buf[i] == '&' ) - { - result.append( "&" ); + char[] buf = new char[len]; + aString.getChars(0, len, buf, 0); + for (int i = 0; i < len; i++) { + if (buf[i] == '&') { + result.append("&"); + } else if (buf[i] == '\\') { + result.append("""); + } else if (buf[i] == '<') { + result.append("<"); + } else if (buf[i] == '>') { + result.append(">"); + } else if (buf[i] == '\t') { + result.append(" "); + } else if (buf[i] == '\n') { + result.append(" "); + } else if (buf[i] == '\r') { + result.append(" "); + } else { + result.append(buf[i]); } - else - if ( buf[i] == '\\' ) - { - result.append( """ ); - } - else - if ( buf[i] == '<' ) - { - result.append( "<" ); - } - else - if ( buf[i] == '>' ) - { - result.append( ">" ); - } - else - if ( buf[i] == '\t' ) - { - result.append( " " ); - } - else - if ( buf[i] == '\n' ) - { - result.append( " " ); - } - else - if ( buf[i] == '\r' ) - { - result.append( " " ); - } - else - { - result.append( buf[i] ); - } - } - return result.toString(); + } + return result.toString(); } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java index 40ee618..cf84b17 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java @@ -24,155 +24,137 @@ import java.util.List; import net.wotonomy.foundation.NSMutableArray; /** -* This class represents a parent node in an element tree. -* It has no content in itself, and exists only to forward -* messages to each of its children, in turn. -* Package access only, as it is not in the specification. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -class WOParentElement extends WOElement -{ + * This class represents a parent node in an element tree. It has no content in + * itself, and exists only to forward messages to each of its children, in turn. + * Package access only, as it is not in the specification. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +class WOParentElement extends WOElement { NSMutableArray children; /** - * Default constructor. - */ - public WOParentElement() - { + * Default constructor. + */ + public WOParentElement() { children = new NSMutableArray(); } - + /** - * Returns an element with the specified children. - */ - public WOParentElement( List childElements ) - { + * Returns an element with the specified children. + */ + public WOParentElement(List childElements) { this(); - children.addAll( childElements ); + children.addAll(childElements); } - - /** - * Package access only. Called to initialize the component with - * the proper context before the start of the request-response cycle. - * If the context has a current component, that component becomes - * this component's parent. - */ - void ensureAwakeInContext (WOContext aContext) - { - WOElement element; - Iterator it = children.iterator(); - while ( it.hasNext() ) - { - element = (WOElement) it.next(); - aContext.pushElement( element ); - element.ensureAwakeInContext( aContext ); - aContext.popElement(); - } - } /** - * Forwards this message to all child elements. - */ - public void takeValuesFromRequest ( - WORequest aRequest, WOContext aContext) - { - WOElement element; - + * Package access only. Called to initialize the component with the proper + * context before the start of the request-response cycle. If the context has a + * current component, that component becomes this component's parent. + */ + void ensureAwakeInContext(WOContext aContext) { + WOElement element; + Iterator it = children.iterator(); + while (it.hasNext()) { + element = (WOElement) it.next(); + aContext.pushElement(element); + element.ensureAwakeInContext(aContext); + aContext.popElement(); + } + } + + /** + * Forwards this message to all child elements. + */ + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + WOElement element; + // aContext.incrementLastElementIDComponent(); - aContext.appendZeroElementIDComponent(); - - Iterator it = children.iterator(); - while ( it.hasNext() ) - { - element = (WOElement) it.next(); - aContext.pushElement( element ); - aContext.incrementLastElementIDComponent(); - element.takeValuesFromRequest( aRequest, aContext ); - aContext.popElement(); - } - - aContext.deleteLastElementIDComponent(); - } - - /** - * Forwards this message to all child elements, - * returning the first non-null result. - */ - public WOActionResults invokeAction ( - WORequest aRequest, WOContext aContext) - { - WOElement element; - + aContext.appendZeroElementIDComponent(); + + Iterator it = children.iterator(); + while (it.hasNext()) { + element = (WOElement) it.next(); + aContext.pushElement(element); + aContext.incrementLastElementIDComponent(); + element.takeValuesFromRequest(aRequest, aContext); + aContext.popElement(); + } + + aContext.deleteLastElementIDComponent(); + } + + /** + * Forwards this message to all child elements, returning the first non-null + * result. + */ + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + WOElement element; + // aContext.incrementLastElementIDComponent(); - aContext.appendZeroElementIDComponent(); - - WOActionResults result = null; - Iterator it = children.iterator(); - while ( it.hasNext() ) - { - element = (WOElement) it.next(); - aContext.pushElement( element ); - aContext.incrementLastElementIDComponent(); - result = element.invokeAction( aRequest, aContext ); - aContext.popElement(); - if ( result != null ) break; - } - - aContext.deleteLastElementIDComponent(); - return result; - } - - /** - * Forwards this message to all child elements. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - WOElement element; - + aContext.appendZeroElementIDComponent(); + + WOActionResults result = null; + Iterator it = children.iterator(); + while (it.hasNext()) { + element = (WOElement) it.next(); + aContext.pushElement(element); + aContext.incrementLastElementIDComponent(); + result = element.invokeAction(aRequest, aContext); + aContext.popElement(); + if (result != null) + break; + } + + aContext.deleteLastElementIDComponent(); + return result; + } + + /** + * Forwards this message to all child elements. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + WOElement element; + // aContext.incrementLastElementIDComponent(); - aContext.appendZeroElementIDComponent(); - - // for each child element - Iterator it = children.iterator(); - while ( it.hasNext() ) - { - element = (WOElement) it.next(); - aContext.pushElement( element ); - aContext.incrementLastElementIDComponent(); - - // forward the message - element.appendToResponse( - aResponse, aContext ); - - aContext.popElement(); - - } - aContext.deleteLastElementIDComponent(); - } - - public WOResponse generateResponse() - { - WOResponse r = new WOResponse(); - return r; - } - - public String toString() - { - StringBuffer result = new StringBuffer(); - result.append( "[WOParentElement: " ); - // for each child element - Iterator it = children.iterator(); - while ( it.hasNext() ) - { - result.append( "[ " ); - result.append( it.next().toString() ); - result.append( " ]" ); - } - result.append( " ]" ); - return result.toString(); - } - + aContext.appendZeroElementIDComponent(); + + // for each child element + Iterator it = children.iterator(); + while (it.hasNext()) { + element = (WOElement) it.next(); + aContext.pushElement(element); + aContext.incrementLastElementIDComponent(); + + // forward the message + element.appendToResponse(aResponse, aContext); + + aContext.popElement(); + + } + aContext.deleteLastElementIDComponent(); + } + + public WOResponse generateResponse() { + WOResponse r = new WOResponse(); + return r; + } + + public String toString() { + StringBuffer result = new StringBuffer(); + result.append("[WOParentElement: "); + // for each child element + Iterator it = children.iterator(); + while (it.hasNext()) { + result.append("[ "); + result.append(it.next().toString()); + result.append(" ]"); + } + result.append(" ]"); + return result.toString(); + } + } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java index 2d4f16e..4d81fe0 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java @@ -5,16 +5,16 @@ import net.wotonomy.foundation.NSDictionary; public class WOPasswordField extends WOTextField { - public WOPasswordField() { - super(); - } + public WOPasswordField() { + super(); + } - public WOPasswordField(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WOPasswordField(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "PASSWORD"; - } + protected String inputType() { + return "PASSWORD"; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java index a73636c..a40c828 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java @@ -7,129 +7,132 @@ import net.wotonomy.foundation.NSKeyValueCodingAdditions; public class WOPopUpButton extends WOInput { - public WOPopUpButton() { - super(); - } - - public WOPopUpButton(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } - - protected String inputType() { - return "SELECT"; - } - - protected int inputSize() { - return 1; - } - - protected NSArray list(WOContext c) { - NSArray l = (NSArray)valueForProperty("list", c.component()); - if (l == null) - l = NSArray.EmptyArray; - return l; - } - - protected void setItem(Object v, WOContext c) { - if (associations.objectForKey("item") == null) - return; - setValueForProperty("item", v, c.component()); - } - protected Object item(WOContext c) { - return valueForProperty("item", c.component()); - } - - protected void setSelection(Object v, WOContext c) { - if (associations.objectForKey("selection") == null) - return; - setValueForProperty("selection", v, c.component()); - } - protected Object selection(WOContext c) { - return valueForProperty("selection", c.component()); - } - - public Object value(WOContext c) { - return null; - } - - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(""); - } - - protected void select(Object v, WOContext c) { - if (associations.objectForKey("selection") != null) { - setSelection(v, c); - return; - } - if (associations.objectForKey("item") != null) { - setItem(v,c); - } - } - public void takeValuesFromRequest(WORequest r, WOContext c) { - Object val = r.formValueForKey(inputName(c)); - if (val == null) - return; - NSArray list = list(c); - String valueKey = stringForProperty("value", c.component()); - //If no value binding, just get the index - if (valueKey == null) { - int pos = Integer.parseInt(val.toString()); - val = list.objectAtIndex(pos); - select(val, c); - return; - } - //If value binding is present, lookup the value - java.util.Enumeration numerador = list.objectEnumerator(); - while (numerador.hasMoreElements()) { - Object o = numerador.nextElement(); - if (o instanceof NSKeyValueCodingAdditions) { - Object x = ((NSKeyValueCodingAdditions)o).valueForKeyPath(valueKey); - if (x != null && x.equals(val)) { - select(o, c); - return; - } - } - } - } + public WOPopUpButton() { + super(); + } + + public WOPopUpButton(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } + + protected String inputType() { + return "SELECT"; + } + + protected int inputSize() { + return 1; + } + + protected NSArray list(WOContext c) { + NSArray l = (NSArray) valueForProperty("list", c.component()); + if (l == null) + l = NSArray.EmptyArray; + return l; + } + + protected void setItem(Object v, WOContext c) { + if (associations.objectForKey("item") == null) + return; + setValueForProperty("item", v, c.component()); + } + + protected Object item(WOContext c) { + return valueForProperty("item", c.component()); + } + + protected void setSelection(Object v, WOContext c) { + if (associations.objectForKey("selection") == null) + return; + setValueForProperty("selection", v, c.component()); + } + + protected Object selection(WOContext c) { + return valueForProperty("selection", c.component()); + } + + public Object value(WOContext c) { + return null; + } + + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(""); + } + + protected void select(Object v, WOContext c) { + if (associations.objectForKey("selection") != null) { + setSelection(v, c); + return; + } + if (associations.objectForKey("item") != null) { + setItem(v, c); + } + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + Object val = r.formValueForKey(inputName(c)); + if (val == null) + return; + NSArray list = list(c); + String valueKey = stringForProperty("value", c.component()); + // If no value binding, just get the index + if (valueKey == null) { + int pos = Integer.parseInt(val.toString()); + val = list.objectAtIndex(pos); + select(val, c); + return; + } + // If value binding is present, lookup the value + java.util.Enumeration numerador = list.objectEnumerator(); + while (numerador.hasMoreElements()) { + Object o = numerador.nextElement(); + if (o instanceof NSKeyValueCodingAdditions) { + Object x = ((NSKeyValueCodingAdditions) o).valueForKeyPath(valueKey); + if (x != null && x.equals(val)) { + select(o, c); + return; + } + } + } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java index 386218c..b06b052 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java @@ -5,16 +5,16 @@ import net.wotonomy.foundation.NSDictionary; public class WORadioButton extends WOCheckBox { - public WORadioButton() { - super(); - } + public WORadioButton() { + super(); + } - public WORadioButton(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WORadioButton(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "RADIO"; - } + protected String inputType() { + return "RADIO"; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java index afa118a..53615a4 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java @@ -25,155 +25,150 @@ import net.wotonomy.foundation.NSDictionary; public class WORepetition extends WODynamicElement { - protected int count; - protected int index; - protected Object item; - protected Collection list; - protected Iterator iterator; - - protected WORepetition() { - super(); - } - - public WORepetition(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } - - public void setCount(int value) { - count = value; - } - - public int count() { - return count; - } - - public void setIndex(int value) { - index = value; - } - public int index() { - return index; - } - - public void setItem(Object value) { - item = value; - } - public Object item() { - return item; - } - - public void setList(Collection value) { - list = value; - } - public Collection list() { - return list; - } - - public void takeValuesFromRequest(WORequest r, WOContext c) { - c.appendZeroElementIDComponent(); - pullValuesFromParent(c.component()); - index = 0; - WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count"); - item = getNextItem(); - while (item != null) { - pushValuesToParent(c.component()); - rootElement.takeValuesFromRequest(r, c); - index++; - c.incrementLastElementIDComponent(); - if (countAsoc != null && index >= count()) - item = null; - else - item = getNextItem(); - } - iterator = null; - c.deleteLastElementIDComponent(); - } - - public WOActionResults invokeAction(WORequest r, WOContext c) { - c.appendZeroElementIDComponent(); - pullValuesFromParent(c.component()); - index = 0; - WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count"); - item = getNextItem(); - while (item != null) { - pushValuesToParent(c.component()); - WOActionResults e = rootElement.invokeAction(r, c); - if (e != null) - { - iterator = null; - return e; - } - index++; - c.incrementLastElementIDComponent(); - if (countAsoc != null && index >= count()) - item = null; - else - item = getNextItem(); - } - iterator = null; - c.deleteLastElementIDComponent(); - return null; - } - - public void appendToResponse(WOResponse r, WOContext c) { - c.appendZeroElementIDComponent(); - pullValuesFromParent(c.component()); - index = 0; - WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count"); - item = getNextItem(); - while (item != null) { - pushValuesToParent(c.component()); - rootElement.appendToResponse(r, c); - index++; - c.incrementLastElementIDComponent(); - if (countAsoc != null || index < count()) - item = null; - else - item = getNextItem(); - } - iterator = null; - c.deleteLastElementIDComponent(); - } - - protected Object getNextItem() { - if ( iterator == null ) - { - if ( list == null ) return null; - iterator = list.iterator(); - } - if ( iterator.hasNext() ) - { - return iterator.next(); - } - return null; - } - - protected void pullValuesFromParent(WOComponent c) { - Object value; - - value = valueForProperty("count", c); - if ( value instanceof Number ) - { - setCount( ((Number)value).intValue() ); - } - else - { - setCount( -1 ); - } - - value = valueForProperty("list", c); - if ( value instanceof Collection ) - { - setList( (Collection) value ); - } - else - { - setList( null ); - } - } - - protected void pushValuesToParent(WOComponent c) { - setValueForProperty("index", new Integer(index), c); - setValueForProperty("item", item, c); - } + protected int count; + protected int index; + protected Object item; + protected Collection list; + protected Iterator iterator; + + protected WORepetition() { + super(); + } + + public WORepetition(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } + + public void setCount(int value) { + count = value; + } + + public int count() { + return count; + } + + public void setIndex(int value) { + index = value; + } + + public int index() { + return index; + } + + public void setItem(Object value) { + item = value; + } + + public Object item() { + return item; + } + + public void setList(Collection value) { + list = value; + } + + public Collection list() { + return list; + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + c.appendZeroElementIDComponent(); + pullValuesFromParent(c.component()); + index = 0; + WOAssociation countAsoc = (WOAssociation) associations.objectForKey("count"); + item = getNextItem(); + while (item != null) { + pushValuesToParent(c.component()); + rootElement.takeValuesFromRequest(r, c); + index++; + c.incrementLastElementIDComponent(); + if (countAsoc != null && index >= count()) + item = null; + else + item = getNextItem(); + } + iterator = null; + c.deleteLastElementIDComponent(); + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + c.appendZeroElementIDComponent(); + pullValuesFromParent(c.component()); + index = 0; + WOAssociation countAsoc = (WOAssociation) associations.objectForKey("count"); + item = getNextItem(); + while (item != null) { + pushValuesToParent(c.component()); + WOActionResults e = rootElement.invokeAction(r, c); + if (e != null) { + iterator = null; + return e; + } + index++; + c.incrementLastElementIDComponent(); + if (countAsoc != null && index >= count()) + item = null; + else + item = getNextItem(); + } + iterator = null; + c.deleteLastElementIDComponent(); + return null; + } + + public void appendToResponse(WOResponse r, WOContext c) { + c.appendZeroElementIDComponent(); + pullValuesFromParent(c.component()); + index = 0; + WOAssociation countAsoc = (WOAssociation) associations.objectForKey("count"); + item = getNextItem(); + while (item != null) { + pushValuesToParent(c.component()); + rootElement.appendToResponse(r, c); + index++; + c.incrementLastElementIDComponent(); + if (countAsoc != null || index < count()) + item = null; + else + item = getNextItem(); + } + iterator = null; + c.deleteLastElementIDComponent(); + } + + protected Object getNextItem() { + if (iterator == null) { + if (list == null) + return null; + iterator = list.iterator(); + } + if (iterator.hasNext()) { + return iterator.next(); + } + return null; + } + + protected void pullValuesFromParent(WOComponent c) { + Object value; + + value = valueForProperty("count", c); + if (value instanceof Number) { + setCount(((Number) value).intValue()); + } else { + setCount(-1); + } + + value = valueForProperty("list", c); + if (value instanceof Collection) { + setList((Collection) value); + } else { + setList(null); + } + } + + protected void pushValuesToParent(WOComponent c) { + setValueForProperty("index", new Integer(index), c); + setValueForProperty("item", item, c); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java index 7d71223..34f7414 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java @@ -33,554 +33,479 @@ import net.wotonomy.foundation.NSMutableData; import net.wotonomy.foundation.NSMutableDictionary; /** -* A pure java implementation of WORequest. -* This implementation is backed by an HttpServletRequest, -* and thus does not support application-specific subclassing. -* Future implementations may remove this limitation. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WORequest extends WOResponse -{ - HttpServletRequest request; - private WOApplication application; - private NSArray languages; - private String requestHandlerKey; - private String requestHandlerPath; - private String pageName; - private String contextID; - private String senderID; - private String defaultFormValueEncoding; - - /** - * Parameterless constructor which should not be called. - */ - public WORequest () - { - throw new RuntimeException( - "This constructor is not yet supported." ); - } - - /** - * Standard constructor. Method and URL are required. - * HeaderMap is a map of header names to arrays containing - * one or more values. Data is the content of the request. - * UserInfo contains application-defined paramters that get - * passed to all objects involved in dispatching the request. - */ - public WORequest - (String aMethod, String aURL, String aProtocolName, - NSDictionary headerMap, NSData aData, NSDictionary userInfo) - { - throw new RuntimeException( - "This constructor is not yet supported." ); - } - - /** - * The only supported constructor for this implementation. - * This WORequest will wrap the specified servlet request. - */ - public WORequest( HttpServletRequest aRequest, WOApplication anApplication ) - { + * A pure java implementation of WORequest. This implementation is backed by an + * HttpServletRequest, and thus does not support application-specific + * subclassing. Future implementations may remove this limitation. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WORequest extends WOResponse { + HttpServletRequest request; + private WOApplication application; + private NSArray languages; + private String requestHandlerKey; + private String requestHandlerPath; + private String pageName; + private String contextID; + private String senderID; + private String defaultFormValueEncoding; + + /** + * Parameterless constructor which should not be called. + */ + public WORequest() { + throw new RuntimeException("This constructor is not yet supported."); + } + + /** + * Standard constructor. Method and URL are required. HeaderMap is a map of + * header names to arrays containing one or more values. Data is the content of + * the request. UserInfo contains application-defined paramters that get passed + * to all objects involved in dispatching the request. + */ + public WORequest(String aMethod, String aURL, String aProtocolName, NSDictionary headerMap, NSData aData, + NSDictionary userInfo) { + throw new RuntimeException("This constructor is not yet supported."); + } + + /** + * The only supported constructor for this implementation. This WORequest will + * wrap the specified servlet request. + */ + public WORequest(HttpServletRequest aRequest, WOApplication anApplication) { request = aRequest; - application = anApplication; - - languages = null; - senderID = null; - pageName = null; - contextID = null; - defaultFormValueEncoding = "ISO8859_1"; - requestHandlerKey = WOApplication.directActionRequestHandlerKey(); - requestHandlerPath = request.getServletPath(); // request.getPathInfo(); - String remainder = requestHandlerPath; - if ( requestHandlerPath == null ) requestHandlerPath = ""; - if ( requestHandlerPath.length() > 0 ) - { - int i; - - // get request handler key - i = requestHandlerPath.indexOf( "/", 1 ); - if ( i != -1 ) - { - requestHandlerKey = requestHandlerPath.substring( 1, i ); - requestHandlerPath = requestHandlerPath.substring( i ); - } - - Enumeration e = requestHandlerPathArray().objectEnumerator(); - // skipping session ID for now - if ( e.hasMoreElements() ) - { - pageName = e.nextElement().toString(); - } - if ( e.hasMoreElements() ) - { - contextID = e.nextElement().toString(); - } - if ( e.hasMoreElements() ) - { - senderID = e.nextElement().toString(); - } - } - } - - /** - * Returns the HTTP method of the request: "GET" or "POST", - * or possibly "PUT". - */ - public String method () - { - return request.getMethod(); - } - - /** - * Returns the Uniform Resource Identifier, which is the part - * of the request URL after the protocol name (after the port - * number) and before the query parameters (before the question mark). - */ - public String uri () - { - return request.getRequestURI(); - } - - /** - * Returns the name of the protocol (presumably HTTP) and the - * version used by the client as sent in the request headers. - */ - public String httpVersion () - { - return request.getProtocol(); - } - - /** - * Returns an array of the header names in this request - * in no particular order. - */ - public NSArray headerKeys () - { - return arrayFromEnumeration( request.getHeaderNames() ); - } - - /** - * Returns an array of the header values for the specified key - * in no particular order. - */ - public NSArray headersForKey (String aString) - { - return arrayFromEnumeration( request.getHeaders( aString ) ); - } - - /** - * Returns one value for the specified header key. - * Provided as a convenience since most headers have - * only one value. Returns null if not found. - */ - public String headerForKey (String aKey) - { - return request.getHeader( aKey ); - } - - /** - * Returns the content of the request, or null if no content. - * The type of the content is determined by the "content-type" header. - * On error, null is returned. - */ - public NSData content () - { - //TODO: This is broken! - NSMutableData data = new NSMutableData(); - try - { - byte[] buf = new byte[ request.getContentLength() ]; - InputStream is = request.getInputStream(); - + application = anApplication; + + languages = null; + senderID = null; + pageName = null; + contextID = null; + defaultFormValueEncoding = "ISO8859_1"; + requestHandlerKey = WOApplication.directActionRequestHandlerKey(); + requestHandlerPath = request.getServletPath(); // request.getPathInfo(); + String remainder = requestHandlerPath; + if (requestHandlerPath == null) + requestHandlerPath = ""; + if (requestHandlerPath.length() > 0) { + int i; + + // get request handler key + i = requestHandlerPath.indexOf("/", 1); + if (i != -1) { + requestHandlerKey = requestHandlerPath.substring(1, i); + requestHandlerPath = requestHandlerPath.substring(i); + } + + Enumeration e = requestHandlerPathArray().objectEnumerator(); + // skipping session ID for now + if (e.hasMoreElements()) { + pageName = e.nextElement().toString(); + } + if (e.hasMoreElements()) { + contextID = e.nextElement().toString(); + } + if (e.hasMoreElements()) { + senderID = e.nextElement().toString(); + } + } + } + + /** + * Returns the HTTP method of the request: "GET" or "POST", or possibly "PUT". + */ + public String method() { + return request.getMethod(); + } + + /** + * Returns the Uniform Resource Identifier, which is the part of the request URL + * after the protocol name (after the port number) and before the query + * parameters (before the question mark). + */ + public String uri() { + return request.getRequestURI(); + } + + /** + * Returns the name of the protocol (presumably HTTP) and the version used by + * the client as sent in the request headers. + */ + public String httpVersion() { + return request.getProtocol(); + } + + /** + * Returns an array of the header names in this request in no particular order. + */ + public NSArray headerKeys() { + return arrayFromEnumeration(request.getHeaderNames()); + } + + /** + * Returns an array of the header values for the specified key in no particular + * order. + */ + public NSArray headersForKey(String aString) { + return arrayFromEnumeration(request.getHeaders(aString)); + } + + /** + * Returns one value for the specified header key. Provided as a convenience + * since most headers have only one value. Returns null if not found. + */ + public String headerForKey(String aKey) { + return request.getHeader(aKey); + } + + /** + * Returns the content of the request, or null if no content. The type of the + * content is determined by the "content-type" header. On error, null is + * returned. + */ + public NSData content() { + // TODO: This is broken! + NSMutableData data = new NSMutableData(); + try { + byte[] buf = new byte[request.getContentLength()]; + InputStream is = request.getInputStream(); + // this is so very not efficient int len; - while ( ( len = is.read( buf ) ) > -1 ) - { - // copies data twice... - data.appendData( new NSData( buf, 0, len ) ); - } - } - catch ( Exception exc ) - { + while ((len = is.read(buf)) > -1) { + // copies data twice... + data.appendData(new NSData(buf, 0, len)); + } + } catch (Exception exc) { return null; } return data; - } - - /** - * Returns the application-specific userInfo dictionary. - * This implementation returns the servlet attribute map. - */ - public NSDictionary userInfo () - { - //TODO: Test this logic. - Object key, value; - NSMutableDictionary info = new NSMutableDictionary(); - java.util.Enumeration e = request.getAttributeNames(); - while ( e.hasMoreElements() ) - { - key = e.nextElement(); - value = request.getAttribute( e.nextElement().toString() ); - if ( value != null ) - { - info.setObjectForKey( value, key ); - } - } - return info; - } - - /** - * Returns the items in the request handler path parsed - * by the "/" delimiter and put into an array. - */ - public NSArray requestHandlerPathArray () - { - return NSArray.componentsSeparatedByString( requestHandlerPath(), "/" ); - } - - /** - * Returns the client's preferred languages in decreasing - * order of preference. The strings are returned in java - * Locale format, meaning dashes (-) are converted to - * underbars (_): see java.util.Locale.toString(). - */ - public NSArray browserLanguages () - { - if ( languages == null ) - { - languages = arrayFromEnumeration( request.getLocales() ); - } - return languages; - } - - /** - * Returns the portion of the URI specifying the engine within which - * this web application is running. This is important for generating - * URLs to be used in the content of the response. - */ - public String adaptorPrefix () - { - //TODO: Test this logic. - String name = request.getServletPath(); - return name; - + } + + /** + * Returns the application-specific userInfo dictionary. This implementation + * returns the servlet attribute map. + */ + public NSDictionary userInfo() { + // TODO: Test this logic. + Object key, value; + NSMutableDictionary info = new NSMutableDictionary(); + java.util.Enumeration e = request.getAttributeNames(); + while (e.hasMoreElements()) { + key = e.nextElement(); + value = request.getAttribute(e.nextElement().toString()); + if (value != null) { + info.setObjectForKey(value, key); + } + } + return info; + } + + /** + * Returns the items in the request handler path parsed by the "/" delimiter and + * put into an array. + */ + public NSArray requestHandlerPathArray() { + return NSArray.componentsSeparatedByString(requestHandlerPath(), "/"); + } + + /** + * Returns the client's preferred languages in decreasing order of preference. + * The strings are returned in java Locale format, meaning dashes (-) are + * converted to underbars (_): see java.util.Locale.toString(). + */ + public NSArray browserLanguages() { + if (languages == null) { + languages = arrayFromEnumeration(request.getLocales()); + } + return languages; + } + + /** + * Returns the portion of the URI specifying the engine within which this web + * application is running. This is important for generating URLs to be used in + * the content of the response. + */ + public String adaptorPrefix() { + // TODO: Test this logic. + String name = request.getServletPath(); + return name; + // String uri = request.getRequestURI(); // int end = uri.indexOf( request.getPathInfo() ); // return uri.substring( request.getContextPath().length(), end ); - } - - /** - * Returns the application name as specified in the request URI. - * Note that wotonomy web applications do not typically have a .woa - * extension, but the extension will be included for those that do. - */ - public String applicationName () - { - return request.getContextPath(); - } - - /** - * Returns the id of the application instance that is needed to - * service this request. -1 indicates that the request is not - * specific to a particular instance of the application. - * This implementation currently returns -1. - */ - public int applicationNumber () - { + } + + /** + * Returns the application name as specified in the request URI. Note that + * wotonomy web applications do not typically have a .woa extension, but the + * extension will be included for those that do. + */ + public String applicationName() { + return request.getContextPath(); + } + + /** + * Returns the id of the application instance that is needed to service this + * request. -1 indicates that the request is not specific to a particular + * instance of the application. This implementation currently returns -1. + */ + public int applicationNumber() { return -1; - } - - /** - * Returns the portion of the URI that specifies which request handler - * should handle the request. - */ - public String requestHandlerKey () - { - return requestHandlerKey; - } - - /** - * Returns the portion of the URI that specifies path information - * for the request, not including the query string. - */ - public String requestHandlerPath () - { - return requestHandlerPath; - } - - /** - * Returns the unique identifier for the sessions associated with - * this request, or null if no session is found. - */ - public String sessionID () - { - HttpSession session = request.getSession( false ); - if ( session == null ) return null; - return session.getId(); - } - - /** - * Returns an array containing all the form keys in the request. - */ - public NSArray formValueKeys () - { - return arrayFromEnumeration( request.getParameterNames() ); - } - - /** - * Returns an array of the values for the specified key in - * no particular order. - */ - public NSArray formValuesForKey (String aKey) - { - NSMutableArray result = new NSMutableArray(); - String[] values = request.getParameterValues( aKey ); - if ( values != null ) - { - for ( int i = 0; i < values.length; i++ ) - { - result.addObject( values[i] ); - } - } - return result; - } - - /** - * Returns one value for the specified key. - * Provided as a convenience since most keys have - * only one value. Returns null if not found. - */ - public Object formValueForKey (String aKey) - { - return request.getParameter( aKey ); - } - - /** - * Returns the value for the specified key, as a String. - */ - public String stringFormValueForKey(String key) { - Object x = formValueForKey(key); - if (x != null) - return x.toString(); - return null; - } - - /** - * Returns a dictionary containing all the key-value - * mappings in the request. - */ - public NSDictionary formValues () - { - NSMutableDictionary result = new NSMutableDictionary (); - java.util.Enumeration e = request.getParameterNames(); - String key; - while ( e.hasMoreElements() ) - { - key = e.nextElement().toString(); - result.setObjectForKey( formValuesForKey( key ), key ); - } - return result; - } - - /** - * Returns whether the request is from a java-based client component. - * This implementation returns false. - */ - public boolean isFromClientComponent () - { - return false; - } - - /** - * Returns an array of cookie values for the specified key in - * no particular order. - */ - public NSArray cookieValuesForKey (String aKey) - { - // TODO: Test this logic. - NSMutableArray result = new NSMutableArray(); - Cookie[] cookies = request.getCookies(); - if ( cookies == null ) return result; - - for ( int i = 0; i < cookies.length; i++ ) - { - if ( cookies[i].getName().equals( aKey ) ) - { - result.addObject( cookies[i].getValue() ); - } - } - - return result; - } - - /** - * Returns one value for the specified cookie key. - * Provided as a convenience since most cookies have - * only one value. Returns null if not found. - */ - public String cookieValueForKey (String aKey) - { - // TODO: Test this logic. - Cookie[] cookies = request.getCookies(); - if ( cookies == null ) return null; - - for ( int i = 0; i < cookies.length; i++ ) - { - if ( cookies[i].getName().equals( aKey ) ) - { - return cookies[i].getValue(); - } - } - - return null; - } - - /** - * Returns a dictionary of cookie key-value mappings. - */ - public NSDictionary cookieValues () - { - // TODO: Test this logic. - NSMutableDictionary result = new NSMutableDictionary(); - Cookie[] cookies = request.getCookies(); - if ( cookies == null ) return result; - - NSMutableArray value; - for ( int i = 0; i < cookies.length; i++ ) - { - value = (NSMutableArray) result.objectForKey( - cookies[i].getName() ); - if ( value == null ) - { - value = new NSMutableArray(); - result.setObjectForKey( - cookies[i].getValue(), cookies[i].getName() ); + } + + /** + * Returns the portion of the URI that specifies which request handler should + * handle the request. + */ + public String requestHandlerKey() { + return requestHandlerKey; + } + + /** + * Returns the portion of the URI that specifies path information for the + * request, not including the query string. + */ + public String requestHandlerPath() { + return requestHandlerPath; + } + + /** + * Returns the unique identifier for the sessions associated with this request, + * or null if no session is found. + */ + public String sessionID() { + HttpSession session = request.getSession(false); + if (session == null) + return null; + return session.getId(); + } + + /** + * Returns an array containing all the form keys in the request. + */ + public NSArray formValueKeys() { + return arrayFromEnumeration(request.getParameterNames()); + } + + /** + * Returns an array of the values for the specified key in no particular order. + */ + public NSArray formValuesForKey(String aKey) { + NSMutableArray result = new NSMutableArray(); + String[] values = request.getParameterValues(aKey); + if (values != null) { + for (int i = 0; i < values.length; i++) { + result.addObject(values[i]); + } + } + return result; + } + + /** + * Returns one value for the specified key. Provided as a convenience since most + * keys have only one value. Returns null if not found. + */ + public Object formValueForKey(String aKey) { + return request.getParameter(aKey); + } + + /** + * Returns the value for the specified key, as a String. + */ + public String stringFormValueForKey(String key) { + Object x = formValueForKey(key); + if (x != null) + return x.toString(); + return null; + } + + /** + * Returns a dictionary containing all the key-value mappings in the request. + */ + public NSDictionary formValues() { + NSMutableDictionary result = new NSMutableDictionary(); + java.util.Enumeration e = request.getParameterNames(); + String key; + while (e.hasMoreElements()) { + key = e.nextElement().toString(); + result.setObjectForKey(formValuesForKey(key), key); + } + return result; + } + + /** + * Returns whether the request is from a java-based client component. This + * implementation returns false. + */ + public boolean isFromClientComponent() { + return false; + } + + /** + * Returns an array of cookie values for the specified key in no particular + * order. + */ + public NSArray cookieValuesForKey(String aKey) { + // TODO: Test this logic. + NSMutableArray result = new NSMutableArray(); + Cookie[] cookies = request.getCookies(); + if (cookies == null) + return result; + + for (int i = 0; i < cookies.length; i++) { + if (cookies[i].getName().equals(aKey)) { + result.addObject(cookies[i].getValue()); } - value.addObject( cookies[i].getValue() ); - } - - return result; - } - - /** - * Sets the default character encoding. - */ - public void setDefaultFormValueEncoding (String encoding) - { - defaultFormValueEncoding = encoding; - } - - /** - * Returns the default form value encoding ("ISO8859_1"). - */ - public String defaultFormValueEncoding () - { - return defaultFormValueEncoding; - } - - /** - * Sets whether the appropriate encoding scheme for decoding - * the form values will be automatically determined. - */ - public void setFormValueEncodingDetectionEnabled (boolean enabled) - { - throw new RuntimeException( "Not yet implemented." ); - } - - /** - * Gets whether the appropriate encoding scheme for decoding - * the form values is currently automatically determined. - */ - public boolean isFormValueEncodingDetectionEnabled () - { - throw new RuntimeException( "Not yet implemented." ); - } - - /** - * Gets the current method used for decoding form values. - */ - public int formValueEncoding () - { - throw new RuntimeException( "Not yet implemented." ); - } - - /** - * Returns the host name of the server that is the target of this request. - * NOTE: This method is not published in the WORequest specification. - */ - String applicationHost () - { - // FIXME: this should call WOApplication.hostname(); - return request.getServerName(); - } - - /** - * Returns the port of the server that is the target of this request. - * NOTE: This method is not published in the WORequest specification. - */ - int port() - { - // FIXME: this should call WOApplication.port(); - return request.getServerPort(); - } - - /** - * Returns the backing HttpServletRequest. - */ - HttpServletRequest servletRequest () - { - return request; - } - - /** - * Returns the application that was the target of this request. - * This method is not published in the WORequest specification. - */ - WOApplication application () - { - return application; - } - - /** - * This method is not published in the WORequest specification. - * This sender id from the URI, which is the id of the element - * that generated this request. - */ - String senderID () - { - return senderID; - } - - /** - * This method is not published in the WORequest specification. - * This returns the context id from the URI. - */ - String contextID () - { - return contextID; - } - - /** - * This method is not published in the WORequest specification. - */ - String pageName () - { - return pageName; - } - - /** - * Convenience method to populate an NSArray from an Enumeration. - */ - private static NSArray arrayFromEnumeration( java.util.Enumeration e ) - { - NSMutableArray result = new NSMutableArray(); - while ( e.hasMoreElements() ) - { - result.addObject( e.nextElement() ); - } - return result; - } + } + + return result; + } + + /** + * Returns one value for the specified cookie key. Provided as a convenience + * since most cookies have only one value. Returns null if not found. + */ + public String cookieValueForKey(String aKey) { + // TODO: Test this logic. + Cookie[] cookies = request.getCookies(); + if (cookies == null) + return null; + + for (int i = 0; i < cookies.length; i++) { + if (cookies[i].getName().equals(aKey)) { + return cookies[i].getValue(); + } + } + + return null; + } + + /** + * Returns a dictionary of cookie key-value mappings. + */ + public NSDictionary cookieValues() { + // TODO: Test this logic. + NSMutableDictionary result = new NSMutableDictionary(); + Cookie[] cookies = request.getCookies(); + if (cookies == null) + return result; + + NSMutableArray value; + for (int i = 0; i < cookies.length; i++) { + value = (NSMutableArray) result.objectForKey(cookies[i].getName()); + if (value == null) { + value = new NSMutableArray(); + result.setObjectForKey(cookies[i].getValue(), cookies[i].getName()); + } + value.addObject(cookies[i].getValue()); + } + + return result; + } + + /** + * Sets the default character encoding. + */ + public void setDefaultFormValueEncoding(String encoding) { + defaultFormValueEncoding = encoding; + } + + /** + * Returns the default form value encoding ("ISO8859_1"). + */ + public String defaultFormValueEncoding() { + return defaultFormValueEncoding; + } + + /** + * Sets whether the appropriate encoding scheme for decoding the form values + * will be automatically determined. + */ + public void setFormValueEncodingDetectionEnabled(boolean enabled) { + throw new RuntimeException("Not yet implemented."); + } + + /** + * Gets whether the appropriate encoding scheme for decoding the form values is + * currently automatically determined. + */ + public boolean isFormValueEncodingDetectionEnabled() { + throw new RuntimeException("Not yet implemented."); + } + + /** + * Gets the current method used for decoding form values. + */ + public int formValueEncoding() { + throw new RuntimeException("Not yet implemented."); + } + + /** + * Returns the host name of the server that is the target of this request. NOTE: + * This method is not published in the WORequest specification. + */ + String applicationHost() { + // FIXME: this should call WOApplication.hostname(); + return request.getServerName(); + } + + /** + * Returns the port of the server that is the target of this request. NOTE: This + * method is not published in the WORequest specification. + */ + int port() { + // FIXME: this should call WOApplication.port(); + return request.getServerPort(); + } + + /** + * Returns the backing HttpServletRequest. + */ + HttpServletRequest servletRequest() { + return request; + } + + /** + * Returns the application that was the target of this request. This method is + * not published in the WORequest specification. + */ + WOApplication application() { + return application; + } + + /** + * This method is not published in the WORequest specification. This sender id + * from the URI, which is the id of the element that generated this request. + */ + String senderID() { + return senderID; + } + + /** + * This method is not published in the WORequest specification. This returns the + * context id from the URI. + */ + String contextID() { + return contextID; + } + + /** + * This method is not published in the WORequest specification. + */ + String pageName() { + return pageName; + } + + /** + * Convenience method to populate an NSArray from an Enumeration. + */ + private static NSArray arrayFromEnumeration(java.util.Enumeration e) { + NSMutableArray result = new NSMutableArray(); + while (e.hasMoreElements()) { + result.addObject(e.nextElement()); + } + return result; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java index 957cf25..7e3c624 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java @@ -21,44 +21,37 @@ package net.wotonomy.web; import net.wotonomy.foundation.internal.NetworkClassLoader; /** -* A pure java implementation that corresponds -* to the abstract class WORequestHandler. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public abstract class WORequestHandler -{ + * A pure java implementation that corresponds to the abstract class + * WORequestHandler. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public abstract class WORequestHandler { private NetworkClassLoader loader; - + /** - * Default constructor. - */ - public WORequestHandler() - { - loader = new NetworkClassLoader( WOApplication.application().getClass().getClassLoader() ); + * Default constructor. + */ + public WORequestHandler() { + loader = new NetworkClassLoader(WOApplication.application().getClass().getClassLoader()); } - + /** - * Dispatches the request and returns the response. - */ - abstract public WOResponse handleRequest (WORequest aRequest); - + * Dispatches the request and returns the response. + */ + abstract public WOResponse handleRequest(WORequest aRequest); + /** - * Gets the specified class, loading it if it has not already been - * loaded or if it has been modified. Returns null if not found. - * Because this method is not in the specification, it has only - * package access. - */ - Class loadClass( String aName ) - { - try - { - return loader.loadClass( aName, true ); - } - catch ( ClassNotFoundException cnfe ) - { + * Gets the specified class, loading it if it has not already been loaded or if + * it has been modified. Returns null if not found. Because this method is not + * in the specification, it has only package access. + */ + Class loadClass(String aName) { + try { + return loader.loadClass(aName, true); + } catch (ClassNotFoundException cnfe) { return null; } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java index 8428681..f4fb49f 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java @@ -21,30 +21,31 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** -* Implements a reset button with dynamic bindings. + * Implements a reset button with dynamic bindings. + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOResetButton extends WOInput { - public WOResetButton() { - super(); - } + public WOResetButton() { + super(); + } - public WOResetButton(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOResetButton(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - protected String inputType() { - return "RESET"; - } + protected String inputType() { + return "RESET"; + } - protected Object value(WOContext c) { - Object v = valueForProperty("value", c.component()); - if (v == null) - return "Reset"; - return v; - } + protected Object value(WOContext c) { + Object v = valueForProperty("value", c.component()); + if (v == null) + return "Reset"; + return v; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java index c69c9db..5941ad2 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java @@ -37,453 +37,353 @@ import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.internal.PropertyListParser; /** -* Manages all resources vended by the application. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOResourceManager -{ - private NSMutableDictionary resourceCache; - private NSMutableDictionary dynamicDataCache; - private NSMutableDictionary stringTableCache; - private Map localeCache; // used for wo-style i18n - private Locale californiaLocale; // used for wo-style i18n - - /** - * Constructor is only accessible to subclasses. - */ - protected WOResourceManager() - { - resourceCache = new NSMutableDictionary(); - dynamicDataCache = new NSMutableDictionary(); - stringTableCache = new NSMutableDictionary(); - localeCache = new HashMap(); - californiaLocale = new Locale( "en", "US" ); - localeCache.put( "en", californiaLocale ); - } - - /** - * Returns the raw data corresponding to the specified resource. - * Any data retrieved by this method will be placed in the - * resource manager's global cache. - */ - public byte[] bytesForResourceNamed(String aFileName, - String aFrameworkName, - NSArray aLanguagesList) - { - String mash = aFileName + aFrameworkName; - if ( aLanguagesList != null ) - { - mash = mash + aLanguagesList.componentsJoinedByString(":"); - } - - byte[] result = (byte[]) resourceCache.objectForKey( mash ); - if ( result == null ) - { - InputStream input = inputStreamForResourceNamed( - aFileName, aFrameworkName, aLanguagesList ); - if ( input != null ) - { - try - { - int c; - ByteArrayOutputStream output = new ByteArrayOutputStream(); - while ( ( c = input.read() ) != -1 ) - { - output.write( c ); - } - output.flush(); - input.close(); - output.close(); - result = output.toByteArray(); - synchronized ( resourceCache ) - { - resourceCache.setObjectForKey( result, mash ); - } - } - catch ( Throwable t ) - { - System.err.println( "WOResourceManager: Error reading bytes: " + aFileName ); - t.printStackTrace(); - } - } - } - return result; - } - - /** - * Returns the content type corresponding to the specified resource. - * This implementation recognizes gif, jpg, png, html, and xml extensions. - * Otherwise, "text/plain" is returned. - */ - public String contentTypeForResourceNamed(String aResourcePath) - { - if ( aResourcePath.endsWith( ".gif" ) ) return "image/gif"; - if ( aResourcePath.endsWith( ".jpg" ) ) return "image/jpeg"; - if ( aResourcePath.endsWith( ".png" ) ) return "image/png"; - if ( aResourcePath.endsWith( ".html" ) ) return "text/html"; - if ( aResourcePath.endsWith( ".xml" ) ) return "text/xml"; - return "text/plain"; - } - - /** - * Returns a url to be used when errors occur while retrieving a resource. - */ - public String errorMessageUrlForResourceNamed(String aResourceName, - String aFrameworkName) - { - if ( aResourceName == null ) aResourceName = "null"; - if ( aFrameworkName == null ) - { - return "/ERROR/NOT_FOUND/app=" + - WOApplication.application().name() + - "/filename=" + aResourceName; - } - else - { - return "/ERROR/NOT_FOUND/framework=" + - aFrameworkName + "/filename=" + aResourceName; - } - - } - - /** - * Clears all cached system-wide resource data. - */ - public void flushDataCache() - { - synchronized ( resourceCache ) - { - resourceCache.removeAllObjects(); - } - synchronized ( dynamicDataCache ) - { - dynamicDataCache.removeAllObjects(); - } - synchronized ( stringTableCache ) - { - stringTableCache.removeAllObjects(); - } - } - - /** - * Returns the file-system path for the specified resource. - * Deprecated and not implemented. - * @deprecated Use inputStreamForResourceNamed instead. - */ - public String pathForResourceNamed(String aResourceName, - String aFrameworkName, - NSArray aLanguagesList) - { - throw new RuntimeException( "ResourceManager.pathForResourceNamed: deprecated" ); - } - - /** - * Removes the data from the dynamic data cache for the specified session. - * If aSession is null, the data is removed from the application-wide - * data cache. - */ - public void removeDataForKey(String aKey, - WOSession aSession) - { - if ( aSession != null ) - { - if ( aSession.dynamicDataCache != null ) - { - aSession.dynamicDataCache.removeObjectForKey( aKey ); - } - } - else - { - synchronized ( dynamicDataCache ) - { - dynamicDataCache.removeObjectForKey( aKey ); - } - } - } - - /** - * Sets the data in the dynamic data cache for the specified session. - * If aSession is null, the data is placed in the application-wide - * data cache. If the key is a system-generated key, the data will - * be removed by calling removeData() the next time it is requested. - */ - public void setData(NSData someData, - String key, - String type, - WOSession aSession) - { - if ( aSession != null ) - { - if ( aSession.dynamicDataCache != null ) - { - aSession.dynamicDataCache.setObjectForKey( - new TypedData( type, someData ), key ); - } - } - else - { - synchronized ( dynamicDataCache ) - { - dynamicDataCache.setObjectForKey( - new TypedData( type, someData ), key ); - } - } - } - - /** - * Returns a localized string from a property list for - * a given key. If the key doesn't exist, aDefaultValue - * is returned. - */ - public String stringForKey(String aKey, - String aFileName, - String aDefaultValue, - String aFrameworkName, - NSArray aLanguagesList) - { - - String mash = aFileName + aFrameworkName; - if ( aLanguagesList != null ) - { - mash = mash + aLanguagesList.componentsJoinedByString(":"); - } - - Object table = stringTableCache.objectForKey( mash ); - if ( table == null ) - { - try - { - InputStream input = (InputStream) inputStreamForResourceNamed( - aFileName, aFrameworkName, aLanguagesList); - if ( input != null ) - { - Reader reader = - new BufferedReader(new InputStreamReader(input)); - table = PropertyListParser.propertyListFromReader( reader ); - synchronized ( stringTableCache ) - { - stringTableCache.setObjectForKey( table, mash ); - } - } - } - catch ( IOException ioe ) - { - System.err.println( "WOResourceManager: error reading: " + aFileName ); - ioe.printStackTrace(); - } - catch ( Throwable t ) - { - // could not parse - System.err.println( "WOResourceManager: could not parse: " + aFileName ); - System.err.println( t ); - } - } - - Object result = null; - if ( table != null ) - { - result = NSKeyValueCoding.DefaultImplementation.valueForKey( table, aKey ); - } - if ( result == null ) - { - result = aDefaultValue; - } - else - { - result = result.toString(); - } - - return (String) result; - } - - /** - * Returns a url that invokes the resource manager for the - * specified resource. - */ - public String urlForResourceNamed(String aResourceName, - String aFrameworkName, - NSArray aLanguagesList, - WORequest aRequest) - { - StringBuffer buffer = new StringBuffer(); - if ( aFrameworkName == null ) - { - aFrameworkName = "application"; - } - buffer.append( aRequest.applicationName() ); - buffer.append( '/' ); - buffer.append( WOApplication.resourceRequestHandlerKey() ); - if ( !aFrameworkName.startsWith("/") ) - { - buffer.append( '/' ); - } - buffer.append( aFrameworkName ); - buffer.append( '/' ); - buffer.append( aResourceName ); - return buffer.toString(); - } - - /** - * Returns an input for the raw resource. Data returned by - * this method will not be put in the resource manager's global cache. - */ - public InputStream inputStreamForResourceNamed(String aResourceName, - String aFrameworkName, - NSArray aLanguagesList) - { - if ( aResourceName == null ) return null; - InputStream result = null; - - StringBuffer path = new StringBuffer(); - path.append( '/' ); - if ( aFrameworkName != null ) - { - path.append( aFrameworkName ).append( '/' ); - } - - int i = aResourceName.lastIndexOf( "." ); - if ( i != -1 ) - path.append( aResourceName.substring( 0, i ) ); - else - path.append( aResourceName ); - - String location = path.toString(); - if ( aLanguagesList != null ) - { - String language; - Locale locale; - HashSet tried = new HashSet(5); - Enumeration e = aLanguagesList.objectEnumerator(); - while ( e.hasMoreElements() && result == null ) - { - language = e.nextElement().toString(); - - // look for java-style localization - if ( i != -1 ) - { - result = getStream( location + '_' - + language + aResourceName.substring( i ) ); - } - else // no dot extension - { - result = getStream( location + '_' + language ); - } - - // look for wo-style localization - if ( result == null ) - { - locale = (Locale) localeCache.get( language ); - if ( locale == null ) - { - if ( language.length() == 5 ) - { - locale = new Locale( - language.substring( 0, 2 ), - language.substring( 3, 5 ) ); - } - else - { - locale = new Locale( language, "" ); - } - synchronized ( localeCache ) - { - localeCache.put( language, locale ); - } - } - - language = '/'+locale.getDisplayLanguage( californiaLocale )+".lproj"; - if ( !tried.contains( language ) ) - { - if ( aFrameworkName != null ) - { - int j = aFrameworkName.length()+1; - path.insert( j, language ); - result = getStream( path.toString() + aResourceName.substring( i ) ); - path.delete( j, j+language.length() ); - } - else - { - result = getStream( language + path.toString() + aResourceName.substring( i ) ); - } - tried.add( language ); - } - } - } - } - - // look for file in package - if ( result == null ) - { - if ( i != -1 ) - { - result = getStream( path.append( - aResourceName.substring( i ) ).toString() ); - } - else // no dot extension - { - result = getStream( location ); - } - } - - return result; - } - - private static final InputStream getStream( String path ) - { //System.out.println( "getStream: " + path ); - InputStream input = - WOApplication.application().getClass().getResourceAsStream( path ); - if ( input == null ) - { - // in case the local class loader doesn't delegate to its parent - input = ClassLoader.getSystemResourceAsStream( path ); - } - return input; - } - - private static final class TypedData - { - String type; - NSData data; - - public TypedData( String aType, NSData aData ) - { - type = aType; - data = aData; - } - } + * Manages all resources vended by the application. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOResourceManager { + private NSMutableDictionary resourceCache; + private NSMutableDictionary dynamicDataCache; + private NSMutableDictionary stringTableCache; + private Map localeCache; // used for wo-style i18n + private Locale californiaLocale; // used for wo-style i18n + + /** + * Constructor is only accessible to subclasses. + */ + protected WOResourceManager() { + resourceCache = new NSMutableDictionary(); + dynamicDataCache = new NSMutableDictionary(); + stringTableCache = new NSMutableDictionary(); + localeCache = new HashMap(); + californiaLocale = new Locale("en", "US"); + localeCache.put("en", californiaLocale); + } + + /** + * Returns the raw data corresponding to the specified resource. Any data + * retrieved by this method will be placed in the resource manager's global + * cache. + */ + public byte[] bytesForResourceNamed(String aFileName, String aFrameworkName, NSArray aLanguagesList) { + String mash = aFileName + aFrameworkName; + if (aLanguagesList != null) { + mash = mash + aLanguagesList.componentsJoinedByString(":"); + } + + byte[] result = (byte[]) resourceCache.objectForKey(mash); + if (result == null) { + InputStream input = inputStreamForResourceNamed(aFileName, aFrameworkName, aLanguagesList); + if (input != null) { + try { + int c; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + while ((c = input.read()) != -1) { + output.write(c); + } + output.flush(); + input.close(); + output.close(); + result = output.toByteArray(); + synchronized (resourceCache) { + resourceCache.setObjectForKey(result, mash); + } + } catch (Throwable t) { + System.err.println("WOResourceManager: Error reading bytes: " + aFileName); + t.printStackTrace(); + } + } + } + return result; + } + + /** + * Returns the content type corresponding to the specified resource. This + * implementation recognizes gif, jpg, png, html, and xml extensions. Otherwise, + * "text/plain" is returned. + */ + public String contentTypeForResourceNamed(String aResourcePath) { + if (aResourcePath.endsWith(".gif")) + return "image/gif"; + if (aResourcePath.endsWith(".jpg")) + return "image/jpeg"; + if (aResourcePath.endsWith(".png")) + return "image/png"; + if (aResourcePath.endsWith(".html")) + return "text/html"; + if (aResourcePath.endsWith(".xml")) + return "text/xml"; + return "text/plain"; + } + + /** + * Returns a url to be used when errors occur while retrieving a resource. + */ + public String errorMessageUrlForResourceNamed(String aResourceName, String aFrameworkName) { + if (aResourceName == null) + aResourceName = "null"; + if (aFrameworkName == null) { + return "/ERROR/NOT_FOUND/app=" + WOApplication.application().name() + "/filename=" + aResourceName; + } else { + return "/ERROR/NOT_FOUND/framework=" + aFrameworkName + "/filename=" + aResourceName; + } + + } + + /** + * Clears all cached system-wide resource data. + */ + public void flushDataCache() { + synchronized (resourceCache) { + resourceCache.removeAllObjects(); + } + synchronized (dynamicDataCache) { + dynamicDataCache.removeAllObjects(); + } + synchronized (stringTableCache) { + stringTableCache.removeAllObjects(); + } + } + + /** + * Returns the file-system path for the specified resource. Deprecated and not + * implemented. + * + * @deprecated Use inputStreamForResourceNamed instead. + */ + public String pathForResourceNamed(String aResourceName, String aFrameworkName, NSArray aLanguagesList) { + throw new RuntimeException("ResourceManager.pathForResourceNamed: deprecated"); + } + + /** + * Removes the data from the dynamic data cache for the specified session. If + * aSession is null, the data is removed from the application-wide data cache. + */ + public void removeDataForKey(String aKey, WOSession aSession) { + if (aSession != null) { + if (aSession.dynamicDataCache != null) { + aSession.dynamicDataCache.removeObjectForKey(aKey); + } + } else { + synchronized (dynamicDataCache) { + dynamicDataCache.removeObjectForKey(aKey); + } + } + } + + /** + * Sets the data in the dynamic data cache for the specified session. If + * aSession is null, the data is placed in the application-wide data cache. If + * the key is a system-generated key, the data will be removed by calling + * removeData() the next time it is requested. + */ + public void setData(NSData someData, String key, String type, WOSession aSession) { + if (aSession != null) { + if (aSession.dynamicDataCache != null) { + aSession.dynamicDataCache.setObjectForKey(new TypedData(type, someData), key); + } + } else { + synchronized (dynamicDataCache) { + dynamicDataCache.setObjectForKey(new TypedData(type, someData), key); + } + } + } + + /** + * Returns a localized string from a property list for a given key. If the key + * doesn't exist, aDefaultValue is returned. + */ + public String stringForKey(String aKey, String aFileName, String aDefaultValue, String aFrameworkName, + NSArray aLanguagesList) { + + String mash = aFileName + aFrameworkName; + if (aLanguagesList != null) { + mash = mash + aLanguagesList.componentsJoinedByString(":"); + } + + Object table = stringTableCache.objectForKey(mash); + if (table == null) { + try { + InputStream input = (InputStream) inputStreamForResourceNamed(aFileName, aFrameworkName, + aLanguagesList); + if (input != null) { + Reader reader = new BufferedReader(new InputStreamReader(input)); + table = PropertyListParser.propertyListFromReader(reader); + synchronized (stringTableCache) { + stringTableCache.setObjectForKey(table, mash); + } + } + } catch (IOException ioe) { + System.err.println("WOResourceManager: error reading: " + aFileName); + ioe.printStackTrace(); + } catch (Throwable t) { + // could not parse + System.err.println("WOResourceManager: could not parse: " + aFileName); + System.err.println(t); + } + } + + Object result = null; + if (table != null) { + result = NSKeyValueCoding.DefaultImplementation.valueForKey(table, aKey); + } + if (result == null) { + result = aDefaultValue; + } else { + result = result.toString(); + } + + return (String) result; + } + + /** + * Returns a url that invokes the resource manager for the specified resource. + */ + public String urlForResourceNamed(String aResourceName, String aFrameworkName, NSArray aLanguagesList, + WORequest aRequest) { + StringBuffer buffer = new StringBuffer(); + if (aFrameworkName == null) { + aFrameworkName = "application"; + } + buffer.append(aRequest.applicationName()); + buffer.append('/'); + buffer.append(WOApplication.resourceRequestHandlerKey()); + if (!aFrameworkName.startsWith("/")) { + buffer.append('/'); + } + buffer.append(aFrameworkName); + buffer.append('/'); + buffer.append(aResourceName); + return buffer.toString(); + } + + /** + * Returns an input for the raw resource. Data returned by this method will not + * be put in the resource manager's global cache. + */ + public InputStream inputStreamForResourceNamed(String aResourceName, String aFrameworkName, + NSArray aLanguagesList) { + if (aResourceName == null) + return null; + InputStream result = null; + + StringBuffer path = new StringBuffer(); + path.append('/'); + if (aFrameworkName != null) { + path.append(aFrameworkName).append('/'); + } + + int i = aResourceName.lastIndexOf("."); + if (i != -1) + path.append(aResourceName.substring(0, i)); + else + path.append(aResourceName); + + String location = path.toString(); + if (aLanguagesList != null) { + String language; + Locale locale; + HashSet tried = new HashSet(5); + Enumeration e = aLanguagesList.objectEnumerator(); + while (e.hasMoreElements() && result == null) { + language = e.nextElement().toString(); + + // look for java-style localization + if (i != -1) { + result = getStream(location + '_' + language + aResourceName.substring(i)); + } else // no dot extension + { + result = getStream(location + '_' + language); + } + + // look for wo-style localization + if (result == null) { + locale = (Locale) localeCache.get(language); + if (locale == null) { + if (language.length() == 5) { + locale = new Locale(language.substring(0, 2), language.substring(3, 5)); + } else { + locale = new Locale(language, ""); + } + synchronized (localeCache) { + localeCache.put(language, locale); + } + } + + language = '/' + locale.getDisplayLanguage(californiaLocale) + ".lproj"; + if (!tried.contains(language)) { + if (aFrameworkName != null) { + int j = aFrameworkName.length() + 1; + path.insert(j, language); + result = getStream(path.toString() + aResourceName.substring(i)); + path.delete(j, j + language.length()); + } else { + result = getStream(language + path.toString() + aResourceName.substring(i)); + } + tried.add(language); + } + } + } + } + + // look for file in package + if (result == null) { + if (i != -1) { + result = getStream(path.append(aResourceName.substring(i)).toString()); + } else // no dot extension + { + result = getStream(location); + } + } + + return result; + } + + private static final InputStream getStream(String path) { // System.out.println( "getStream: " + path ); + InputStream input = WOApplication.application().getClass().getResourceAsStream(path); + if (input == null) { + // in case the local class loader doesn't delegate to its parent + input = ClassLoader.getSystemResourceAsStream(path); + } + return input; + } + + private static final class TypedData { + String type; + NSData data; + + public TypedData(String aType, NSData aData) { + type = aType; + data = aData; + } + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.5 2003/08/07 00:15:15 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.5 2003/08/07 00:15:15 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.4 2003/02/28 22:58:57 mpowers - * Added support for wo-style localization (*.lproj). + * Revision 1.4 2003/02/28 22:58:57 mpowers Added support for wo-style + * localization (*.lproj). * - * Revision 1.3 2003/02/21 16:40:24 mpowers - * Now reading port and smtp host from system properties. - * Implemented WOApplication.main. + * Revision 1.3 2003/02/21 16:40:24 mpowers Now reading port and smtp host from + * system properties. Implemented WOApplication.main. * - * Revision 1.2 2003/01/28 19:33:52 mpowers - * Implemented the rest of WOResourceManager. - * Implemented support for java-style i18n. - * Components now use the resource manager to load templates. + * Revision 1.2 2003/01/28 19:33:52 mpowers Implemented the rest of + * WOResourceManager. Implemented support for java-style i18n. Components now + * use the resource manager to load templates. * - * Revision 1.1 2003/01/27 15:08:00 mpowers - * Implemented WOResourceManager, using java resources for now. + * Revision 1.1 2003/01/27 15:08:00 mpowers Implemented WOResourceManager, using + * java resources for now. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java index 4d25128..f611149 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java @@ -24,99 +24,81 @@ import net.wotonomy.foundation.NSData; import net.wotonomy.foundation.NSMutableDictionary; /** -* An implementation of WORequestHandler that -* retrieves resources from the deployed application. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOResourceRequestHandler - extends WORequestHandler -{ - private NSMutableDictionary resourceCache; - private WOResourceManager resourceManager; - - public WOResourceRequestHandler() - { - //TODO: should probably have some kind of limit on the cache - resourceCache = new NSMutableDictionary(); - resourceManager = WOApplication.application().resourceManager(); - } - - public WOResponse handleRequest( WORequest aRequest ) - { - WOResponse response = new WOResponse(); - - StringBuffer buf = new StringBuffer(); - - //TODO: this is just to get things working... - String framework = null; - Enumeration e = aRequest.requestHandlerPathArray().objectEnumerator(); - if ( e.hasMoreElements() ) - { - framework = e.nextElement().toString(); - if ( framework.equals( "application" ) ) - { - buf.append('/').append( framework ); - framework = null; - } - } - if ( e.hasMoreElements() ) - { - buf.append( e.nextElement() ); - } - while ( e.hasMoreElements() ) - { - buf.append('/').append( e.nextElement() ); - } - - String resource; - if ( buf.length() > 0 ) - { - resource = buf.toString(); - byte[] data = resourceManager.bytesForResourceNamed( - resource, framework, aRequest.browserLanguages() ); - if ( data != null ) - { - response.setHeader( - resourceManager.contentTypeForResourceNamed( resource ), - "Content-Type" ); - response.setContent( new NSData( data ) ); - return response; - } - } - response.setStatus( 404 ); // not found - return response; - } + * An implementation of WORequestHandler that retrieves resources from the + * deployed application. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOResourceRequestHandler extends WORequestHandler { + private NSMutableDictionary resourceCache; + private WOResourceManager resourceManager; + + public WOResourceRequestHandler() { + // TODO: should probably have some kind of limit on the cache + resourceCache = new NSMutableDictionary(); + resourceManager = WOApplication.application().resourceManager(); + } + + public WOResponse handleRequest(WORequest aRequest) { + WOResponse response = new WOResponse(); + + StringBuffer buf = new StringBuffer(); + + // TODO: this is just to get things working... + String framework = null; + Enumeration e = aRequest.requestHandlerPathArray().objectEnumerator(); + if (e.hasMoreElements()) { + framework = e.nextElement().toString(); + if (framework.equals("application")) { + buf.append('/').append(framework); + framework = null; + } + } + if (e.hasMoreElements()) { + buf.append(e.nextElement()); + } + while (e.hasMoreElements()) { + buf.append('/').append(e.nextElement()); + } + + String resource; + if (buf.length() > 0) { + resource = buf.toString(); + byte[] data = resourceManager.bytesForResourceNamed(resource, framework, aRequest.browserLanguages()); + if (data != null) { + response.setHeader(resourceManager.contentTypeForResourceNamed(resource), "Content-Type"); + response.setContent(new NSData(data)); + return response; + } + } + response.setStatus(404); // not found + return response; + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.6 2003/08/07 00:15:15 chochos - * general cleanup (mostly removing unused imports) + * Revision 1.6 2003/08/07 00:15:15 chochos general cleanup (mostly removing + * unused imports) * - * Revision 1.5 2003/01/27 15:08:00 mpowers - * Implemented WOResourceManager, using java resources for now. + * Revision 1.5 2003/01/27 15:08:00 mpowers Implemented WOResourceManager, using + * java resources for now. * - * Revision 1.4 2003/01/22 23:01:36 mpowers - * Better handling for request handler path. - * Better support for resources with absolute path. + * Revision 1.4 2003/01/22 23:01:36 mpowers Better handling for request handler + * path. Better support for resources with absolute path. * - * Revision 1.3 2003/01/20 16:18:22 mpowers - * Fixed class loading issues with resource retrieval. + * Revision 1.3 2003/01/20 16:18:22 mpowers Fixed class loading issues with + * resource retrieval. * - * Revision 1.2 2003/01/17 20:58:20 mpowers - * Fixed up WOHyperlink. + * Revision 1.2 2003/01/17 20:58:20 mpowers Fixed up WOHyperlink. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java index 862c6dc..14c1c87 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java @@ -6,26 +6,26 @@ import net.wotonomy.foundation.NSDictionary; public class WOResourceURL extends WODynamicElement { - public WOResourceURL() { - super(); - } + public WOResourceURL() { + super(); + } - public WOResourceURL(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WOResourceURL(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - public void appendToResponse(WOResponse r, WOContext c) { - String fname = stringForProperty("filename", c.component()); - if (fname != null) { - String fwk = stringForProperty("framework", c.component()); - return; - } - NSData data = (NSData)valueForProperty("data", c.component()); - if (data != null) { - String mime = stringForProperty("mimeType", c.component()); - String key = stringForProperty("key", c.component()); - return; - } - } + public void appendToResponse(WOResponse r, WOContext c) { + String fname = stringForProperty("filename", c.component()); + if (fname != null) { + String fwk = stringForProperty("framework", c.component()); + return; + } + NSData data = (NSData) valueForProperty("data", c.component()); + if (data != null) { + String mime = stringForProperty("mimeType", c.component()); + String key = stringForProperty("key", c.component()); + return; + } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java index d9fbade..6362a03 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java @@ -25,160 +25,133 @@ import java.util.TimeZone; import net.wotonomy.foundation.NSArray; /** -* A pure java implementation of WOResponse. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOResponse extends WOMessage - implements WOActionResults -{ + * A pure java implementation of WOResponse. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOResponse extends WOMessage implements WOActionResults { protected static String defaultEncoding; - private static SimpleDateFormat htmlDateFormat; - static - { - defaultEncoding = "ISO8859_1"; - htmlDateFormat = new SimpleDateFormat( - "EEE, dd MMM yyyy HH:mm:ss z" ); - htmlDateFormat.setTimeZone( - TimeZone.getTimeZone( "GMT" ) ); - } + private static SimpleDateFormat htmlDateFormat; + static { + defaultEncoding = "ISO8859_1"; + htmlDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); + htmlDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + } private int status; /** - * Parameterless constructor which should not be called. - */ - public WOResponse () - { + * Parameterless constructor which should not be called. + */ + public WOResponse() { status = -1; // -1 indicates not yet set - } + } + + /** + * Sets the status code of the response. You should use the constants defined in + * HttpServletResponse. + */ + public void setStatus(int code) { + status = code; + } /** - * Sets the status code of the response. - * You should use the constants defined in HttpServletResponse. - */ - public void setStatus (int code) - { - status = code; - } + * Gets the current status code for the response. + */ + public int status() { + return status; + } /** - * Gets the current status code for the response. - */ - public int status () - { - return status; - } + * Sets a header in the response to disable client caching. (Whether this works + * depends on the client implementation.) + */ + public void disableClientCaching() { + String dateString = htmlDateFormat.format(new Date()); + setHeader(dateString, "Date"); + setHeader(dateString, "Expires"); + setHeader("no-cache", "Pragma"); + setHeaders(new NSArray(new Object[] { "private", "no-cache", "max-age = 0" }), "Cache-Control"); + // System.out.println( "disableClientCaching: " + dateString ); + } /** - * Sets a header in the response to disable client caching. - * (Whether this works depends on the client implementation.) - */ - public void disableClientCaching () - { - String dateString = htmlDateFormat.format( new Date() ); - setHeader( dateString, "Date" ); - setHeader( dateString, "Expires" ); - setHeader( "no-cache", "Pragma" ); - setHeaders( new NSArray( new Object[] { - "private", "no-cache", "max-age = 0" } ), "Cache-Control" ); - //System.out.println( "disableClientCaching: " + dateString ); - } - - /** - * Returns this object. (Implements the WOActionResults interface.) - */ - public WOResponse generateResponse() - { - return this; - } + * Returns this object. (Implements the WOActionResults interface.) + */ + public WOResponse generateResponse() { + return this; + } /** - * Generates the response using the specified servlet response, - * but does not flush the buffer. The caller is responsible - * for sending the response to the client.

            - * Note that this method is currently responsible for setting - * the content type to "text/html". As far as I can tell, WORequests - * have no way to set the content type and therefore must always - * be of type "text/html". - */ - void generateServletResponse - (javax.servlet.http.HttpServletResponse response) - { - if ( WOApplication.application().isPageRefreshOnBacktrackEnabled() ) - { - disableClientCaching(); - } - - String key; - java.util.Enumeration e, f; - - // set content type: might be overwritten by headers below - response.setContentType( "text/html" ); - - // set status - if ( status != -1 ) - { - response.setStatus( status ); + * Generates the response using the specified servlet response, but does not + * flush the buffer. The caller is responsible for sending the response to the + * client.
            + *
            + * Note that this method is currently responsible for setting the content type + * to "text/html". As far as I can tell, WORequests have no way to set the + * content type and therefore must always be of type "text/html". + */ + void generateServletResponse(javax.servlet.http.HttpServletResponse response) { + if (WOApplication.application().isPageRefreshOnBacktrackEnabled()) { + disableClientCaching(); + } + + String key; + java.util.Enumeration e, f; + + // set content type: might be overwritten by headers below + response.setContentType("text/html"); + + // set status + if (status != -1) { + response.setStatus(status); } - // set headers - f = _headers.allKeys().objectEnumerator(); - while ( f.hasMoreElements() ) - { - key = f.nextElement().toString(); - e = ((NSArray)_headers.objectForKey( key )).objectEnumerator(); - if ( e.hasMoreElements() ) - { - // overwrite existing header - response.setHeader( key, e.nextElement().toString() ); - } - while ( e.hasMoreElements() ) - { - response.addHeader( key, e.nextElement().toString() ); - } + // set headers + f = _headers.allKeys().objectEnumerator(); + while (f.hasMoreElements()) { + key = f.nextElement().toString(); + e = ((NSArray) _headers.objectForKey(key)).objectEnumerator(); + if (e.hasMoreElements()) { + // overwrite existing header + response.setHeader(key, e.nextElement().toString()); + } + while (e.hasMoreElements()) { + response.addHeader(key, e.nextElement().toString()); + } } - // set cookies - e = _cookies.allValues().objectEnumerator(); - while ( e.hasMoreElements() ) - { - response.addCookie( (WOCookie) e.nextElement() ); - } - - try - { - // write content - response.getOutputStream().write(_contentData.bytes() ); - if ( status > 299 ) - { - response.sendError( status ); - } - } - catch ( Exception exc ) - { - throw new RuntimeException( - "Error writing response: " + exc ); + // set cookies + e = _cookies.allValues().objectEnumerator(); + while (e.hasMoreElements()) { + response.addCookie((WOCookie) e.nextElement()); } - } + try { + // write content + response.getOutputStream().write(_contentData.bytes()); + if (status > 299) { + response.sendError(status); + } + } catch (Exception exc) { + throw new RuntimeException("Error writing response: " + exc); + } + } /** - * Returns the current default encoding seting. - */ - public static String defaultEncoding () - { - return defaultEncoding; - } + * Returns the current default encoding seting. + */ + public static String defaultEncoding() { + return defaultEncoding; + } /** - * Sets the default encoding setting. - */ - public static void setDefaultEncoding (String encoding) - { - defaultEncoding = encoding; - } + * Sets the default encoding setting. + */ + public static void setDefaultEncoding(String encoding) { + defaultEncoding = encoding; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java index 0f2898c..647346c 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java @@ -28,161 +28,135 @@ import java.io.ObjectStreamClass; import javax.servlet.http.HttpSession; - /** -* An implementation of WOSessionStore that stores WOSessions -* inside of HttpSessions, to take advantage of the servlet -* containers built-in distribution capabilities. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOServletSessionStore extends WOSessionStore -{ - private static final String ServletSessionKey = "WOServletSessionStoreKey"; - - /** - * This implementation currently does nothing and returns null, as we rely on the - * servlet container to dispose of the HttpSession which contains our WOSession. - */ - public WOSession removeSessionWithID(String sessionID) - { - return null; - } - - /** - * Returns the WOSession for the specified ID from the store. - * The sessionID parameter is ignored, and the session is removed from - * the store until saveSessionForContext is called. - */ - public WOSession restoreSessionWithID(String sessionID, WORequest aRequest) - { - WOSession result = null; - HttpSession servletSession = aRequest.servletRequest().getSession(); - if ( servletSession != null ) - { - try - { - result = (WOSession) servletSession.getAttribute( ServletSessionKey ); - if ( result != null ) - { - // if the servlet's class loader has changed, we need to reload - if ( result.getClass().getClassLoader() != - WOApplication.application().getClass().getClassLoader() ) - { - throw new ClassCastException( result.getClass().toString() ); - } - } - } - catch ( ClassCastException exc ) - { - // we're having an issue with the container's class loader: - // try serializing and deserializing to see if it is reloaded correctly - try - { - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ObjectOutputStream output = new ObjectOutputStream( bytes ); - Object o = servletSession.getAttribute( ServletSessionKey ); - output.writeObject( o ); - output.flush(); - output.close(); - - System.out.println( - "WOServletSessionStore: reloaded session with size: " + - bytes.toByteArray().length ); - - ObjectInputStream input = new CustomObjectInputStream( - new ByteArrayInputStream( bytes.toByteArray() ), - WOApplication.application().getClass().getClassLoader() ); - - o = input.readObject(); - input.close(); - - // try it again - result = (WOSession) o; - } - catch ( Exception e ) - { - e.printStackTrace(); - // give up: remove the attribute and allow a new session - servletSession.removeAttribute( ServletSessionKey ); - } - } - - if ( result != null ) - { - servletSession.removeAttribute( ServletSessionKey ); - } - } - //System.out.println( "restoreSessionWithID: " + sessionID + " : " + result ); - return result; - } - - /** - * Places the context's session into the store. - */ - public void saveSessionForContext(WOContext context) - { - //System.out.println( "saveSessionForContext: " + context + " : " + context.session() ); - context.request().servletRequest().getSession( true ).setAttribute( - ServletSessionKey, context.session() ); - } - - /** - * Needed to specify a class loader which may be different that the - * one used to load the ObjectInputStream class. - */ - private static class CustomObjectInputStream extends ObjectInputStream - { - ClassLoader loader; - - public CustomObjectInputStream( - InputStream anInputStream, ClassLoader aClassLoader ) - throws java.io.IOException, java.io.StreamCorruptedException - { - super( anInputStream ); - loader = aClassLoader; - } - - protected Class resolveClass(ObjectStreamClass v) - throws IOException, ClassNotFoundException - { - return loader.loadClass( v.getName() ); - } - } + * An implementation of WOSessionStore that stores WOSessions inside of + * HttpSessions, to take advantage of the servlet containers built-in + * distribution capabilities. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOServletSessionStore extends WOSessionStore { + private static final String ServletSessionKey = "WOServletSessionStoreKey"; + + /** + * This implementation currently does nothing and returns null, as we rely on + * the servlet container to dispose of the HttpSession which contains our + * WOSession. + */ + public WOSession removeSessionWithID(String sessionID) { + return null; + } + + /** + * Returns the WOSession for the specified ID from the store. The sessionID + * parameter is ignored, and the session is removed from the store until + * saveSessionForContext is called. + */ + public WOSession restoreSessionWithID(String sessionID, WORequest aRequest) { + WOSession result = null; + HttpSession servletSession = aRequest.servletRequest().getSession(); + if (servletSession != null) { + try { + result = (WOSession) servletSession.getAttribute(ServletSessionKey); + if (result != null) { + // if the servlet's class loader has changed, we need to reload + if (result.getClass().getClassLoader() != WOApplication.application().getClass().getClassLoader()) { + throw new ClassCastException(result.getClass().toString()); + } + } + } catch (ClassCastException exc) { + // we're having an issue with the container's class loader: + // try serializing and deserializing to see if it is reloaded correctly + try { + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream output = new ObjectOutputStream(bytes); + Object o = servletSession.getAttribute(ServletSessionKey); + output.writeObject(o); + output.flush(); + output.close(); + + System.out.println( + "WOServletSessionStore: reloaded session with size: " + bytes.toByteArray().length); + + ObjectInputStream input = new CustomObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()), + WOApplication.application().getClass().getClassLoader()); + + o = input.readObject(); + input.close(); + + // try it again + result = (WOSession) o; + } catch (Exception e) { + e.printStackTrace(); + // give up: remove the attribute and allow a new session + servletSession.removeAttribute(ServletSessionKey); + } + } + + if (result != null) { + servletSession.removeAttribute(ServletSessionKey); + } + } + // System.out.println( "restoreSessionWithID: " + sessionID + " : " + result ); + return result; + } + + /** + * Places the context's session into the store. + */ + public void saveSessionForContext(WOContext context) { + // System.out.println( "saveSessionForContext: " + context + " : " + + // context.session() ); + context.request().servletRequest().getSession(true).setAttribute(ServletSessionKey, context.session()); + } + + /** + * Needed to specify a class loader which may be different that the one used to + * load the ObjectInputStream class. + */ + private static class CustomObjectInputStream extends ObjectInputStream { + ClassLoader loader; + + public CustomObjectInputStream(InputStream anInputStream, ClassLoader aClassLoader) + throws java.io.IOException, java.io.StreamCorruptedException { + super(anInputStream); + loader = aClassLoader; + } + + protected Class resolveClass(ObjectStreamClass v) throws IOException, ClassNotFoundException { + return loader.loadClass(v.getName()); + } + } } /* - * $Log$ - * Revision 1.2 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2003/01/22 23:00:32 mpowers - * Now keeping the most recent page around. + * Revision 1.7 2003/01/22 23:00:32 mpowers Now keeping the most recent page + * around. * - * Revision 1.6 2003/01/21 17:53:16 mpowers - * Now handling reloading when wotonomy is on the system class path. + * Revision 1.6 2003/01/21 17:53:16 mpowers Now handling reloading when wotonomy + * is on the system class path. * - * Revision 1.5 2003/01/15 19:50:49 mpowers - * Fixed issues with WOSession and Serializable. - * Can now persist sessions between classloaders (hot swap of class impls). + * Revision 1.5 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and + * Serializable. Can now persist sessions between classloaders (hot swap of + * class impls). * - * Revision 1.3 2003/01/14 16:05:19 mpowers - * Removed extraneous printlns. + * Revision 1.3 2003/01/14 16:05:19 mpowers Removed extraneous printlns. * - * Revision 1.2 2003/01/13 22:25:07 mpowers - * Request-response cycle is working with session and page persistence. + * Revision 1.2 2003/01/13 22:25:07 mpowers Request-response cycle is working + * with session and page persistence. * - * Revision 1.1 2003/01/07 20:48:24 mpowers - * Implemented WOSessionStore and WOServletSessionStore. + * Revision 1.1 2003/01/07 20:48:24 mpowers Implemented WOSessionStore and + * WOServletSessionStore. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java index e187567..82df58b 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java @@ -36,493 +36,433 @@ import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; /** -* A pure java implementation of WOSession. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOSession implements Serializable, NSKeyValueCodingAdditions -{ - //NOTE: need to set this when deserialized and on creation - transient private HttpSession session; - - // the current context - transient private WOContext context; - - // the last requested page: an optimization - transient private WOComponent currentPage; - // the last requested page's context id - transient private String currentContextID; - - //FIXME: transient until ec's implement serializable - private transient EOEditingContext defaultEditingContext; - - private NSMutableDictionary state; - private NSMutableDictionary pages; - private NSMutableDictionary permanentPages; - private NSMutableArray stateStack; - private NSMutableArray pageStack; - private NSMutableArray permanentPageStack; - private boolean terminating; - - // used by WOResourceManager to cache dynamic resources - transient NSMutableDictionary dynamicDataCache; - - public static final String WOSessionDidTimeOutNotification - = "WOSessionDidTimeOutNotification"; - public static final String WOSessionDidRestoreNotification - = "WOSessionDidRestoreNotification"; - public static final String WOSessionDidCreateNotification - = "WOSessionDidCreateNotification"; - - /** - * Default constructor. This is called implicitly by - * subclasses in all cases. - */ - public WOSession () - { - session = null; - state = new NSMutableDictionary(); - pages = new NSMutableDictionary(); - permanentPages = new NSMutableDictionary(); - stateStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); - pageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); - permanentPageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() ); - defaultEditingContext = null; - terminating = false; - } - - /** - * Package method to initialize the backing session. - */ - void setServletSession( HttpSession aSession ) - { - session = aSession; - } - - /** - * Package method to set the current context. - */ - void setContext( WOContext aContext ) - { - context = aContext; - } - - /** - * Returns the id of the current session. If no session - * currently exists, return null. - */ - public String sessionID () - { - if ( session != null ) - { - return session.getId(); - } - return null; - } - - /** - * Sets whether distribution is currently enabled. - * This method is not implemented by this implementation - * as the servlet container manages distribution. - */ - public void setDistributionEnabled (boolean enabled) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether the session is part of a distributed application. - * This implementation always returns false. - */ - public boolean isDistributionEnabled () - { - return false; - } - - /** - * Sets whether session ids should be stored in cookies. - * This method is not implemented in this implementation - * as the servlet container manages sessions with cookies. - */ - public void setStoresIDsInCookies (boolean cookies) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether session ids are currently stored in cookies. - * This implementation always returns true. - */ - public boolean storesIDsInCookies () - { - return true; - } - - /** - * Returns the current expiration date for cookies that store session ids. - */ - public NSDate expirationDateForIDCookies () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Sets whether session ids should be stored in urls. - * This method is not implemented in this implementation - * as the servlet container manages sessions with cookies. - */ - public void setStoresIDsInURLs (boolean urls) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns whether session ids are currently stored in urls. - * This implementation always returns false. - */ - public boolean storesIDsInURLs () - { - return false; - } - - /** - * Returns the current domain for cookies containing session ids. - */ - public String domainForIDCookies () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Terminates this session after the completion of the current response. - */ - public void terminate () - { - terminating = true; - session.invalidate(); - } - - /** - * Returns whether the current session will terminate at the completion - * of the current response. - */ - public boolean isTerminating () - { - return terminating; - } - - /** - * Sets the number of seconds after the last request before - * the session should be terminated. - */ - public void setTimeOut (double timeout) - { - session.setMaxInactiveInterval( (int) timeout ); - } - - /** - * Returns the number of seconds after the last request before - * the session should be terminated. - */ - public double timeOut () - { - return session.getMaxInactiveInterval(); - } - - /** - * Sets the languages for which this session has been localized, - * in order of preference. The application will be responsible for - * localizing the content based on the languages found in this array. - */ - public void setLanguages (NSArray anArray) - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Returns the languages for which this session has been localized, - * in order of preference. The application will be responsible for - * localizing the content based on the languages found in this array. - */ - public NSArray languages () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Stores the specified key-value pair in the session. - */ - public void setObjectForKey (Object anObject, String aKey) - { - state.setObjectForKey( anObject, aKey ); - } - - /** - * Returns the session value associated with the specified key. - */ - public Object objectForKey (String aKey) - { - return state.objectForKey( aKey ); - } - - /** - * Removes the session value associated with the specified key. - */ - public void removeObjectForKey (String aKey) - { - state.removeObjectForKey( aKey ); - } - - /** - * Returns the context for the current request. - */ - public WOContext context () - { - return context; - } - - /** - * Invoked at the beginning of the request-response cycle. - * Override to perform any kind of initialization at the - * start of a request. This implementation does nothing. - */ - public void awake () - { - - } - - /** - * Invoked by the Application to extract user-assigned balues - * and assign them to attributes. This implementation calls - * takeValuesFromRequest on the top-level component. - */ - public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) - { - context().component().takeValuesFromRequest( aRequest, aContext ); - } - - /** - * Invoked by the Application to determine which component is the - * intended recipient of the user's action. This implementation calls - * invokeAction on the top-level component. - */ - public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) - { - return context().component().invokeAction( aRequest, aContext ); - } - - /** - * Invoked by the Application to generate the content of the response. - * This implementation calls appendToResponse on the top-level component. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - context().component().appendToResponse( aResponse, aContext ); - } - - /** - * Invoked at the end of the request-response cycle. - * Override to perform any kind of clean-up at the - * end of a request. This implementation does nothing. - */ - public void sleep () - { - - } - - /** - * Returns a list of pages accessed by this session in order - * of their access and named by calling WOComponent.descriptionForResponse. - */ - public NSArray statistics () - { - throw new RuntimeException( "Not implemented yet." ); - } - - /** - * Puts this page in the session's page cache using the current - * context id as the key. - */ - public void savePage (WOComponent aComponent) - { - currentPage = aComponent; - currentContextID = context.contextID(); - - if ( pages.objectForKey( currentContextID ) == null ) - { - byte[] data = KeyValueCodingUtilities.freeze( - aComponent, defaultEditingContext(), aComponent, true ); - System.out.println( "WOSession.savePage: " + currentContextID + " : " + data.length ); - - pages.setObjectForKey( data, currentContextID ); - pageStack.addObject( currentContextID ); - if ( pageStack.count() > context().application().pageCacheSize() ) - { - String id = pageStack.remove( 0 ).toString(); // removeObjectAtIndex - System.out.println( "WOSession.savePage: removing from cache: " + id ); - pages.removeObjectForKey( id ); - } - } - //System.out.println( "savePage: " + this + " : " + id + " : " + pages ); - } - - /** - * Returns the page in the session's page cache corresponding to - * the specified context id. Any special permanent caches are - * searched before the standard page cache. - */ - public WOComponent restorePageForContextID (String anID) - { - if ( anID == null ) return null; - if ( anID.equals( currentContextID ) ) return currentPage; - - WOComponent result = null; - byte[] data = (byte[]) permanentPages.objectForKey( anID ); - if ( data == null ) data = (byte[]) pages.objectForKey( anID ); - if ( data != null ) - { - System.out.println( "WOSession.restorePageForContextID: " + anID + " : " + data.length ); - result = (WOComponent) KeyValueCodingUtilities.thaw( - data, defaultEditingContext(), - WOApplication.application().getClass().getClassLoader(), true ); - } - //System.out.println( "restorePageForContextID: " + this + " : " + anID + " : " + result + " : " + pages ); - return result; - } - - /** - * Puts this page in the special cache is will not get automatically - * flushed like the session page cache. Use this if the page - * is likely to be around for a while, specifically pages within - * frames. - */ - public void savePageInPermanentCache (WOComponent aComponent) - { - currentPage = aComponent; - currentContextID = context.contextID(); - - if ( permanentPages.objectForKey( currentContextID ) == null ) - { - byte[] data = KeyValueCodingUtilities.freeze( - aComponent, defaultEditingContext(), aComponent, true ); - //System.out.println( "WOSession.savePageInPermanentCache: " - // + currentContextID + " : " + data.length ); - - permanentPages.setObjectForKey( data, currentContextID ); - permanentPageStack.addObject( currentContextID ); - if ( permanentPageStack.count() > context().application().pageCacheSize() ) - { - String id = permanentPageStack.remove( 0 ).toString(); // removeObjectAtIndex - permanentPages.removeObjectForKey( id ); - } - } - } - - /** - * Writes a message to the standard error stream. - */ - public static void logString (String aString) - { - System.err.println( aString ); - } - - /** - * Writes a message to the standard error stream - * if debugging is activated. - */ - public static void debugString (String aString) - { - // TODO: Check to see if debugging is enabled. - System.err.println( aString ); - } - - /** - * Returns the default editing context used by this session. - * Defaults to null. - */ - public EOEditingContext defaultEditingContext () - { - return defaultEditingContext; - } - - /** - * Sets the default editing context used by this session. - */ - public void setDefaultEditingContext (EOEditingContext aContext) - { - defaultEditingContext = aContext; - } - - // interface NSKeyValueCodingAdditions - - public Object valueForKeyPath (String aPath) - { - // currently key value coding support also handles keypaths - return valueForKey( aPath ); - } - - public void takeValueForKeyPath (Object aValue, String aPath) - { - // currently key value coding support also handles keypaths - takeValueForKey( aValue, aPath ); - } - - public NSDictionary valuesForKeys (List aKeyList) - { - throw new RuntimeException( "Not implemented yet." ); - } - - public void takeValuesFromDictionary (Map aValueMap) - { - throw new RuntimeException( "Not implemented yet." ); - } - - public Object valueForKey (String aKey) - { // System.out.println( "valueForKey: " + aKey + "->" + this ); - Object result = objectForKey( aKey ); - if ( result == null ) - result = NSKeyValueCodingSupport.valueForKey( this, aKey ); - return result; - } - - public void takeValueForKey (Object aValue, String aKey) - { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); - setObjectForKey( aValue, aKey ); - } - - public Object storedValueForKey (String aKey) - { - Object result = objectForKey( aKey ); - if ( result == null ) - NSKeyValueCodingSupport.storedValueForKey( this, aKey ); - return result; - } - - public void takeStoredValueForKey (Object aValue, String aKey) - { - setObjectForKey( aValue, aKey ); - } - - public Object handleQueryWithUnboundKey (String aKey) - { - return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); - } - - public void handleTakeValueForUnboundKey (Object aValue, String aKey) - { - NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); - } - - public void unableToSetNullForKey (String aKey) - { - NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); - } - - public Object validateTakeValueForKeyPath (Object aValue, String aKey) - { - throw new RuntimeException( "Not implemented yet." ); - } - + * A pure java implementation of WOSession. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOSession implements Serializable, NSKeyValueCodingAdditions { + // NOTE: need to set this when deserialized and on creation + transient private HttpSession session; + + // the current context + transient private WOContext context; + + // the last requested page: an optimization + transient private WOComponent currentPage; + // the last requested page's context id + transient private String currentContextID; + + // FIXME: transient until ec's implement serializable + private transient EOEditingContext defaultEditingContext; + + private NSMutableDictionary state; + private NSMutableDictionary pages; + private NSMutableDictionary permanentPages; + private NSMutableArray stateStack; + private NSMutableArray pageStack; + private NSMutableArray permanentPageStack; + private boolean terminating; + + // used by WOResourceManager to cache dynamic resources + transient NSMutableDictionary dynamicDataCache; + + public static final String WOSessionDidTimeOutNotification = "WOSessionDidTimeOutNotification"; + public static final String WOSessionDidRestoreNotification = "WOSessionDidRestoreNotification"; + public static final String WOSessionDidCreateNotification = "WOSessionDidCreateNotification"; + + /** + * Default constructor. This is called implicitly by subclasses in all cases. + */ + public WOSession() { + session = null; + state = new NSMutableDictionary(); + pages = new NSMutableDictionary(); + permanentPages = new NSMutableDictionary(); + stateStack = NSMutableArray.mutableArrayBackedByList(new LinkedList()); + pageStack = NSMutableArray.mutableArrayBackedByList(new LinkedList()); + permanentPageStack = NSMutableArray.mutableArrayBackedByList(new LinkedList()); + defaultEditingContext = null; + terminating = false; + } + + /** + * Package method to initialize the backing session. + */ + void setServletSession(HttpSession aSession) { + session = aSession; + } + + /** + * Package method to set the current context. + */ + void setContext(WOContext aContext) { + context = aContext; + } + + /** + * Returns the id of the current session. If no session currently exists, return + * null. + */ + public String sessionID() { + if (session != null) { + return session.getId(); + } + return null; + } + + /** + * Sets whether distribution is currently enabled. This method is not + * implemented by this implementation as the servlet container manages + * distribution. + */ + public void setDistributionEnabled(boolean enabled) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether the session is part of a distributed application. This + * implementation always returns false. + */ + public boolean isDistributionEnabled() { + return false; + } + + /** + * Sets whether session ids should be stored in cookies. This method is not + * implemented in this implementation as the servlet container manages sessions + * with cookies. + */ + public void setStoresIDsInCookies(boolean cookies) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether session ids are currently stored in cookies. This + * implementation always returns true. + */ + public boolean storesIDsInCookies() { + return true; + } + + /** + * Returns the current expiration date for cookies that store session ids. + */ + public NSDate expirationDateForIDCookies() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Sets whether session ids should be stored in urls. This method is not + * implemented in this implementation as the servlet container manages sessions + * with cookies. + */ + public void setStoresIDsInURLs(boolean urls) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns whether session ids are currently stored in urls. This implementation + * always returns false. + */ + public boolean storesIDsInURLs() { + return false; + } + + /** + * Returns the current domain for cookies containing session ids. + */ + public String domainForIDCookies() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Terminates this session after the completion of the current response. + */ + public void terminate() { + terminating = true; + session.invalidate(); + } + + /** + * Returns whether the current session will terminate at the completion of the + * current response. + */ + public boolean isTerminating() { + return terminating; + } + + /** + * Sets the number of seconds after the last request before the session should + * be terminated. + */ + public void setTimeOut(double timeout) { + session.setMaxInactiveInterval((int) timeout); + } + + /** + * Returns the number of seconds after the last request before the session + * should be terminated. + */ + public double timeOut() { + return session.getMaxInactiveInterval(); + } + + /** + * Sets the languages for which this session has been localized, in order of + * preference. The application will be responsible for localizing the content + * based on the languages found in this array. + */ + public void setLanguages(NSArray anArray) { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Returns the languages for which this session has been localized, in order of + * preference. The application will be responsible for localizing the content + * based on the languages found in this array. + */ + public NSArray languages() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Stores the specified key-value pair in the session. + */ + public void setObjectForKey(Object anObject, String aKey) { + state.setObjectForKey(anObject, aKey); + } + + /** + * Returns the session value associated with the specified key. + */ + public Object objectForKey(String aKey) { + return state.objectForKey(aKey); + } + + /** + * Removes the session value associated with the specified key. + */ + public void removeObjectForKey(String aKey) { + state.removeObjectForKey(aKey); + } + + /** + * Returns the context for the current request. + */ + public WOContext context() { + return context; + } + + /** + * Invoked at the beginning of the request-response cycle. Override to perform + * any kind of initialization at the start of a request. This implementation + * does nothing. + */ + public void awake() { + + } + + /** + * Invoked by the Application to extract user-assigned balues and assign them to + * attributes. This implementation calls takeValuesFromRequest on the top-level + * component. + */ + public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { + context().component().takeValuesFromRequest(aRequest, aContext); + } + + /** + * Invoked by the Application to determine which component is the intended + * recipient of the user's action. This implementation calls invokeAction on the + * top-level component. + */ + public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { + return context().component().invokeAction(aRequest, aContext); + } + + /** + * Invoked by the Application to generate the content of the response. This + * implementation calls appendToResponse on the top-level component. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + context().component().appendToResponse(aResponse, aContext); + } + + /** + * Invoked at the end of the request-response cycle. Override to perform any + * kind of clean-up at the end of a request. This implementation does nothing. + */ + public void sleep() { + + } + + /** + * Returns a list of pages accessed by this session in order of their access and + * named by calling WOComponent.descriptionForResponse. + */ + public NSArray statistics() { + throw new RuntimeException("Not implemented yet."); + } + + /** + * Puts this page in the session's page cache using the current context id as + * the key. + */ + public void savePage(WOComponent aComponent) { + currentPage = aComponent; + currentContextID = context.contextID(); + + if (pages.objectForKey(currentContextID) == null) { + byte[] data = KeyValueCodingUtilities.freeze(aComponent, defaultEditingContext(), aComponent, true); + System.out.println("WOSession.savePage: " + currentContextID + " : " + data.length); + + pages.setObjectForKey(data, currentContextID); + pageStack.addObject(currentContextID); + if (pageStack.count() > context().application().pageCacheSize()) { + String id = pageStack.remove(0).toString(); // removeObjectAtIndex + System.out.println("WOSession.savePage: removing from cache: " + id); + pages.removeObjectForKey(id); + } + } + // System.out.println( "savePage: " + this + " : " + id + " : " + pages ); + } + + /** + * Returns the page in the session's page cache corresponding to the specified + * context id. Any special permanent caches are searched before the standard + * page cache. + */ + public WOComponent restorePageForContextID(String anID) { + if (anID == null) + return null; + if (anID.equals(currentContextID)) + return currentPage; + + WOComponent result = null; + byte[] data = (byte[]) permanentPages.objectForKey(anID); + if (data == null) + data = (byte[]) pages.objectForKey(anID); + if (data != null) { + System.out.println("WOSession.restorePageForContextID: " + anID + " : " + data.length); + result = (WOComponent) KeyValueCodingUtilities.thaw(data, defaultEditingContext(), + WOApplication.application().getClass().getClassLoader(), true); + } + // System.out.println( "restorePageForContextID: " + this + " : " + anID + " : " + // + result + " : " + pages ); + return result; + } + + /** + * Puts this page in the special cache is will not get automatically flushed + * like the session page cache. Use this if the page is likely to be around for + * a while, specifically pages within frames. + */ + public void savePageInPermanentCache(WOComponent aComponent) { + currentPage = aComponent; + currentContextID = context.contextID(); + + if (permanentPages.objectForKey(currentContextID) == null) { + byte[] data = KeyValueCodingUtilities.freeze(aComponent, defaultEditingContext(), aComponent, true); + // System.out.println( "WOSession.savePageInPermanentCache: " + // + currentContextID + " : " + data.length ); + + permanentPages.setObjectForKey(data, currentContextID); + permanentPageStack.addObject(currentContextID); + if (permanentPageStack.count() > context().application().pageCacheSize()) { + String id = permanentPageStack.remove(0).toString(); // removeObjectAtIndex + permanentPages.removeObjectForKey(id); + } + } + } + + /** + * Writes a message to the standard error stream. + */ + public static void logString(String aString) { + System.err.println(aString); + } + + /** + * Writes a message to the standard error stream if debugging is activated. + */ + public static void debugString(String aString) { + // TODO: Check to see if debugging is enabled. + System.err.println(aString); + } + + /** + * Returns the default editing context used by this session. Defaults to null. + */ + public EOEditingContext defaultEditingContext() { + return defaultEditingContext; + } + + /** + * Sets the default editing context used by this session. + */ + public void setDefaultEditingContext(EOEditingContext aContext) { + defaultEditingContext = aContext; + } + + // interface NSKeyValueCodingAdditions + + public Object valueForKeyPath(String aPath) { + // currently key value coding support also handles keypaths + return valueForKey(aPath); + } + + public void takeValueForKeyPath(Object aValue, String aPath) { + // currently key value coding support also handles keypaths + takeValueForKey(aValue, aPath); + } + + public NSDictionary valuesForKeys(List aKeyList) { + throw new RuntimeException("Not implemented yet."); + } + + public void takeValuesFromDictionary(Map aValueMap) { + throw new RuntimeException("Not implemented yet."); + } + + public Object valueForKey(String aKey) { // System.out.println( "valueForKey: " + aKey + "->" + this ); + Object result = objectForKey(aKey); + if (result == null) + result = NSKeyValueCodingSupport.valueForKey(this, aKey); + return result; + } + + public void takeValueForKey(Object aValue, String aKey) { // System.out.println( "takeValueForKey: " + aKey + " : " + // + aValue + "->" + this ); + setObjectForKey(aValue, aKey); + } + + public Object storedValueForKey(String aKey) { + Object result = objectForKey(aKey); + if (result == null) + NSKeyValueCodingSupport.storedValueForKey(this, aKey); + return result; + } + + public void takeStoredValueForKey(Object aValue, String aKey) { + setObjectForKey(aValue, aKey); + } + + public Object handleQueryWithUnboundKey(String aKey) { + return NSKeyValueCodingSupport.handleQueryWithUnboundKey(this, aKey); + } + + public void handleTakeValueForUnboundKey(Object aValue, String aKey) { + NSKeyValueCodingSupport.handleTakeValueForUnboundKey(this, aValue, aKey); + } + + public void unableToSetNullForKey(String aKey) { + NSKeyValueCodingSupport.unableToSetNullForKey(this, aKey); + } + + public Object validateTakeValueForKeyPath(Object aValue, String aKey) { + throw new RuntimeException("Not implemented yet."); + } + } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java index f91a433..3008f21 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java @@ -19,75 +19,67 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web; /** -* An abstract class defining the requirements for persisting -* session state across user transactions. Used by WOApplication -* to persist sessions between requests. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -public abstract class WOSessionStore -{ - private static WOSessionStore serverSessionStore = null; - - /** - * Returns the default session store used by WOApplication. - */ - public static WOSessionStore serverSessionStore() - { - if ( serverSessionStore == null ) - { - serverSessionStore = new WOServletSessionStore(); - } - return serverSessionStore; - } + * An abstract class defining the requirements for persisting session state + * across user transactions. Used by WOApplication to persist sessions between + * requests. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +public abstract class WOSessionStore { + private static WOSessionStore serverSessionStore = null; + + /** + * Returns the default session store used by WOApplication. + */ + public static WOSessionStore serverSessionStore() { + if (serverSessionStore == null) { + serverSessionStore = new WOServletSessionStore(); + } + return serverSessionStore; + } - /** - * Called by WOApplication after the request-response cycle has ended. - * The context's session will again be available for subsequent requests. - */ - public final void checkInSessionForContext(WOContext aContext) - { - saveSessionForContext( aContext ); - } - - /** - * Returns the session with the specified id for the specified request, - * or null if none exist. Subsequent calls for the same id will return - * null until the session is checked in again. - * Called by WOApplication before the request-response cycle starts. - */ - public final WOSession checkOutSessionWithID(String sessionID, WORequest aRequest) - { - return restoreSessionWithID( sessionID, aRequest ); - } + /** + * Called by WOApplication after the request-response cycle has ended. The + * context's session will again be available for subsequent requests. + */ + public final void checkInSessionForContext(WOContext aContext) { + saveSessionForContext(aContext); + } - /** - * Removes the WOSession for the specified ID from the store and returns it. - */ - public abstract WOSession removeSessionWithID(String sessionID); - - /** - * Returns the WOSession for the specified ID from the store. - */ - public abstract WOSession restoreSessionWithID(String sessionID, - WORequest aRequest); - - /** - * Places the context's session into the store. - */ - public abstract void saveSessionForContext(WOContext context); + /** + * Returns the session with the specified id for the specified request, or null + * if none exist. Subsequent calls for the same id will return null until the + * session is checked in again. Called by WOApplication before the + * request-response cycle starts. + */ + public final WOSession checkOutSessionWithID(String sessionID, WORequest aRequest) { + return restoreSessionWithID(sessionID, aRequest); + } + + /** + * Removes the WOSession for the specified ID from the store and returns it. + */ + public abstract WOSession removeSessionWithID(String sessionID); + + /** + * Returns the WOSession for the specified ID from the store. + */ + public abstract WOSession restoreSessionWithID(String sessionID, WORequest aRequest); + + /** + * Places the context's session into the store. + */ + public abstract void saveSessionForContext(WOContext context); } /* - * $Log$ - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * $Log$ Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2003/01/07 20:48:29 mpowers - * Implemented WOSessionStore and WOServletSessionStore. + * Revision 1.1 2003/01/07 20:48:29 mpowers Implemented WOSessionStore and + * WOServletSessionStore. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java index 308cd20..29a06c4 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java @@ -19,52 +19,44 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web; /** -* This class represents a static portion of a web page. -* Package access only, as it is not in the specification. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 893 $ -*/ -class WOStaticElement extends WOElement -{ + * This class represents a static portion of a web page. Package access only, as + * it is not in the specification. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 893 $ + */ +class WOStaticElement extends WOElement { String content; /** - * Default constructor. - */ - public WOStaticElement() - { + * Default constructor. + */ + public WOStaticElement() { content = null; } - + /** - * Returns a static element representing the specified content. - */ - public WOStaticElement( String aContentString ) - { + * Returns a static element representing the specified content. + */ + public WOStaticElement(String aContentString) { this(); content = aContentString; } - /** - * Overridden to append the content string.. - */ - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - aResponse.appendContentString( content ); - } - - - public WOResponse generateResponse() - { - WOResponse r = new WOResponse(); - if (content != null) - { - r.appendContentString(content); - } - return r; - } - - + /** + * Overridden to append the content string.. + */ + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + aResponse.appendContentString(content); + } + + public WOResponse generateResponse() { + WOResponse r = new WOResponse(); + if (content != null) { + r.appendContentString(content); + } + return r; + } + } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java index ef5b771..0c471d7 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java @@ -25,112 +25,87 @@ import net.wotonomy.foundation.NSNumberFormatter; import net.wotonomy.foundation.NSTimestampFormatter; /** -* WOString renders a dynamically generated string in the output. -* Bindings are: -*
              -*
            • value: a property returning a value which will be rendered as the -* output. If a formatter is not used, then the value must be convertable -* to a String with toString().
            • -*
            • escapeHTML: a property returning a value convertable to a Boolean -* indicating whether the any html characters in the output should be -* escaped so they are shown as html characters rather than interpreted -* as html.
            • -*
            • formatter: a property returning a Format object that will be -* used to format the value into a String.
            • -*
            • dateformat: a property returning a DateFormat object that will be -* used to format the value into a String.
            • -*
            • numberformat: a property returning a NumberFormat object that will be -* used to format the value into a String. Not yet implemented.
            • -*
            • valueWhenEmpty: a property returning a String that will be used -* in place of an empty or null value. Not yet implemented.
            • -*
            -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class WOString extends WODynamicElement -{ + * WOString renders a dynamically generated string in the output. Bindings are: + *
              + *
            • value: a property returning a value which will be rendered as the output. + * If a formatter is not used, then the value must be convertable to a String + * with toString().
            • + *
            • escapeHTML: a property returning a value convertable to a Boolean + * indicating whether the any html characters in the output should be escaped so + * they are shown as html characters rather than interpreted as html.
            • + *
            • formatter: a property returning a Format object that will be used to + * format the value into a String.
            • + *
            • dateformat: a property returning a DateFormat object that will be used to + * format the value into a String.
            • + *
            • numberformat: a property returning a NumberFormat object that will be + * used to format the value into a String. Not yet implemented.
            • + *
            • valueWhenEmpty: a property returning a String that will be used in place + * of an empty or null value. Not yet implemented.
            • + *
            + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class WOString extends WODynamicElement { protected Object value; protected boolean escapeHTML; protected Format formatter; protected String dateformat; protected String numberformat; - protected Object valueWhenEmpty; + protected Object valueWhenEmpty; /** - * The default constructor. - */ - protected WOString () - { - } - - public WOString ( - String aName, NSDictionary anAssociationMap, WOElement aRootElement) - { - super( aName, anAssociationMap, aRootElement ); - escapeHTML = true; - } + * The default constructor. + */ + protected WOString() { + } + + public WOString(String aName, NSDictionary anAssociationMap, WOElement aRootElement) { + super(aName, anAssociationMap, aRootElement); + escapeHTML = true; + } + + public void appendToResponse(WOResponse aResponse, WOContext aContext) { + WOComponent c = aContext.component(); + numberformat = stringForProperty("numberformat", c); + dateformat = stringForProperty("dateformat", c); + formatter = (Format) valueForProperty("formatter", c); + escapeHTML = booleanForProperty("escapeHTML", c); + value = valueForProperty("value", c); + valueWhenEmpty = valueForProperty("valueWhenEmpty", c); - public void appendToResponse (WOResponse aResponse, WOContext aContext) - { - WOComponent c = aContext.component(); - numberformat = stringForProperty("numberformat", c ); - dateformat = stringForProperty("dateformat", c ); - formatter = (Format) valueForProperty("formatter", c ); - escapeHTML = booleanForProperty("escapeHTML", c ); - value = valueForProperty("value", c ); - valueWhenEmpty = valueForProperty("valueWhenEmpty", c ); - - Object result = value; - if ( result != null ) - { - if ( formatter != null ) - { - try - { - result = formatter.format( result ); - } - catch ( IllegalArgumentException exc ) - { - } - } - if ( dateformat != null ) - { - try - { - result = new NSTimestampFormatter( dateformat ).format( result ); - } - catch ( IllegalArgumentException exc ) - { - } - } - if ( numberformat != null ) - { - try - { - result = new NSNumberFormatter( numberformat ).format( result ); - } - catch ( IllegalArgumentException exc ) - { - } - } - } - if ( result == null ) - { - result = valueWhenEmpty; - if ( result == null ) - { - result = "nil"; - } - } - if ( escapeHTML ) - { - aResponse.appendContentHTMLString( result.toString() ); - } - else - { - aResponse.appendContentString( result.toString() ); + Object result = value; + if (result != null) { + if (formatter != null) { + try { + result = formatter.format(result); + } catch (IllegalArgumentException exc) { + } + } + if (dateformat != null) { + try { + result = new NSTimestampFormatter(dateformat).format(result); + } catch (IllegalArgumentException exc) { + } + } + if (numberformat != null) { + try { + result = new NSNumberFormatter(numberformat).format(result); + } catch (IllegalArgumentException exc) { + } + } + } + if (result == null) { + result = valueWhenEmpty; + if (result == null) { + result = "nil"; + } + } + if (escapeHTML) { + aResponse.appendContentHTMLString(result.toString()); + } else { + aResponse.appendContentString(result.toString()); } - } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java index 05824f3..a1ef303 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java @@ -22,49 +22,52 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableArray; /** -* Implements a submit button with dynamic bindings. -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ + * Implements a submit button with dynamic bindings. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOSubmitButton extends WOInput { - public WOSubmitButton() { - super(); - } + public WOSubmitButton() { + super(); + } - public WOSubmitButton(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOSubmitButton(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - protected String inputType() { - return "SUBMIT"; - } + protected String inputType() { + return "SUBMIT"; + } - protected Object value(WOContext c) { - Object v = valueForProperty("value", c.component()); - if (v == null) { - return "Submit"; - } - return v; - } + protected Object value(WOContext c) { + Object v = valueForProperty("value", c.component()); + if (v == null) { + return "Submit"; + } + return v; + } - protected NSMutableArray additionalAttributes() { - NSMutableArray a = super.additionalAttributes(); - a.add("action"); - return a; - } + protected NSMutableArray additionalAttributes() { + NSMutableArray a = super.additionalAttributes(); + a.add("action"); + return a; + } - public WOActionResults invokeAction(WORequest r, WOContext c) { - if (disabled(c)) - return null; - //It's useless to check the senderID here because it's going to be for the form always. - //So we check if the formValues contain the button's name (which means it was pressed) - if (r.formValueForKey(inputName(c)) != null) { - if (associations.objectForKey("action") != null) - return (WOActionResults)valueForProperty("action", c.component()); - } - return null; - } + public WOActionResults invokeAction(WORequest r, WOContext c) { + if (disabled(c)) + return null; + // It's useless to check the senderID here because it's going to be for the form + // always. + // So we check if the formValues contain the button's name (which means it was + // pressed) + if (r.formValueForKey(inputName(c)) != null) { + if (associations.objectForKey("action") != null) + return (WOActionResults) valueForProperty("action", c.component()); + } + return null; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java index d2e373a..6f42199 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java @@ -23,90 +23,78 @@ import net.wotonomy.foundation.NSMutableDictionary; public class WOSwitchComponent extends WODynamicElement { - private NSMutableDictionary elements; - private String currentName; - private WOElement currentElement; - - protected WOSwitchComponent() - { - super(); - elements = new NSMutableDictionary(); - } - - public WOSwitchComponent(String aName, NSDictionary aMap, WOElement template) - { - super(aName, aMap, template); - elements = new NSMutableDictionary(); - } - - private WOElement getCurrentElement( WOContext c ) - { - String name = stringForProperty( "WOComponentName", c.component() ); - if ( name == null ) return null; - - if ( currentElement != null && name.equals( currentName ) ) return currentElement; - - currentName = name; - currentElement = (WOElement) elements.objectForKey( name ); - if ( currentElement == null ) - { - currentElement = WOApplication.application().pageWithName( name, c ); - if ( currentElement != null ) - { - currentElement.associations = associations; - elements.setObjectForKey( currentElement, name ); - } - } - - return currentElement; - } - - void ensureAwakeInContext (WOContext aContext) - { - if ( aContext != null ) - { - WOElement element = getCurrentElement( aContext ); - if ( element != null ) - { - aContext.pushElement( element ); - element.ensureAwakeInContext( aContext ); - aContext.popElement(); - } - } - } - - public void takeValuesFromRequest(WORequest r, WOContext c) - { - WOElement element = getCurrentElement( c ); - if ( element != null ) - { - c.pushElement( element ); - element.takeValuesFromRequest( r, c ); - c.popElement(); - } - } - - public WOActionResults invokeAction(WORequest r, WOContext c) - { - WOActionResults result = null; - WOElement element = getCurrentElement( c ); - if ( element != null ) - { - c.pushElement( element ); - result = element.invokeAction( r, c ); - c.popElement(); - } - return result; - } - - public void appendToResponse(WOResponse r, WOContext c) - { - WOElement element = getCurrentElement( c ); - if ( element != null ) - { - c.pushElement( element ); - element.appendToResponse( r, c ); - c.popElement(); - } - } + private NSMutableDictionary elements; + private String currentName; + private WOElement currentElement; + + protected WOSwitchComponent() { + super(); + elements = new NSMutableDictionary(); + } + + public WOSwitchComponent(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + elements = new NSMutableDictionary(); + } + + private WOElement getCurrentElement(WOContext c) { + String name = stringForProperty("WOComponentName", c.component()); + if (name == null) + return null; + + if (currentElement != null && name.equals(currentName)) + return currentElement; + + currentName = name; + currentElement = (WOElement) elements.objectForKey(name); + if (currentElement == null) { + currentElement = WOApplication.application().pageWithName(name, c); + if (currentElement != null) { + currentElement.associations = associations; + elements.setObjectForKey(currentElement, name); + } + } + + return currentElement; + } + + void ensureAwakeInContext(WOContext aContext) { + if (aContext != null) { + WOElement element = getCurrentElement(aContext); + if (element != null) { + aContext.pushElement(element); + element.ensureAwakeInContext(aContext); + aContext.popElement(); + } + } + } + + public void takeValuesFromRequest(WORequest r, WOContext c) { + WOElement element = getCurrentElement(c); + if (element != null) { + c.pushElement(element); + element.takeValuesFromRequest(r, c); + c.popElement(); + } + } + + public WOActionResults invokeAction(WORequest r, WOContext c) { + WOActionResults result = null; + WOElement element = getCurrentElement(c); + if (element != null) { + c.pushElement(element); + result = element.invokeAction(r, c); + c.popElement(); + } + return result; + } + + public void appendToResponse(WOResponse r, WOContext c) { + WOElement element = getCurrentElement(c); + if (element != null) { + c.pushElement(element); + element.appendToResponse(r, c); + c.popElement(); + } + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java index 008fda8..3b5dd1d 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java @@ -22,20 +22,21 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** -* Implements a TEXTAREA element, with dynamic bindings. + * Implements a TEXTAREA element, with dynamic bindings. + * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOText extends WOInput { - public WOText() { - super(); - } + public WOText() { + super(); + } - public WOText(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOText(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } protected String inputType() { return "TEXTAREA"; @@ -49,24 +50,23 @@ public class WOText extends WOInput { return formattedValue(fieldValue, c.component()); } - public void takeValuesFromRequest(WORequest r, WOContext c) { - Object val = r.formValueForKey(inputName(c)); - if ( val != null ) - setValueForProperty("value", formattedValue(val, c.component()), c.component()); - } + public void takeValuesFromRequest(WORequest r, WOContext c) { + Object val = r.formValueForKey(inputName(c)); + if (val != null) + setValueForProperty("value", formattedValue(val, c.component()), c.component()); + } - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(""); - } + public void appendToResponse(WOResponse r, WOContext c) { + r.appendContentString(""); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java index 4233ad4..82f591a 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java @@ -22,38 +22,39 @@ import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableArray; /** -* Implements an INPUT tag of type TEXT, with dynamic bindings. -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ + * Implements an INPUT tag of type TEXT, with dynamic bindings. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOTextField extends WOInput { - public WOTextField() { - super(); - } - - public WOTextField(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } - - protected String inputType() { - return "TEXT"; - } - - protected Object value(WOContext c) { - Object fieldValue = valueForProperty("value", c.component()); - if (fieldValue == null) { - fieldValue = ""; - } - return formattedValue(fieldValue, c.component()); - } - - protected NSMutableArray additionalAttributes() { - NSMutableArray a = super.additionalAttributes(); - a.addObject("dateformat"); - a.addObject("numberformat"); - return a; - } + public WOTextField() { + super(); + } + + public WOTextField(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } + + protected String inputType() { + return "TEXT"; + } + + protected Object value(WOContext c) { + Object fieldValue = valueForProperty("value", c.component()); + if (fieldValue == null) { + fieldValue = ""; + } + return formattedValue(fieldValue, c.component()); + } + + protected NSMutableArray additionalAttributes() { + NSMutableArray a = super.additionalAttributes(); + a.addObject("dateformat"); + a.addObject("numberformat"); + return a; + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java index 777d4a1..7a6a946 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java @@ -10,656 +10,644 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -/**

            BrowserLauncher is a class that provides one static method, - * openURL, which opens the default web browser for the current user - * of the system to the given URL. It may support other protocols - * depending on the system -- mailto, ftp, etc. -- but that has not - * been rigorously tested and is not guaranteed to work.

            +/** + *

            + * BrowserLauncher is a class that provides one static method, openURL, which + * opens the default web browser for the current user of the system to the given + * URL. It may support other protocols depending on the system -- mailto, ftp, + * etc. -- but that has not been rigorously tested and is not guaranteed to + * work. + *

            * - *

            Yes, this is platform-specific code, and yes, it may rely on - * classes on certain platforms that are not part of the standard JDK. - * What we're trying to do, though, is to take something that's - * frequently desirable but inherently platform-specific -- opening a - * default browser -- and allow programmers (you, for example) to do - * so without worrying about dropping into native code or doing - * anything else similarly evil.

            + *

            + * Yes, this is platform-specific code, and yes, it may rely on classes on + * certain platforms that are not part of the standard JDK. What we're trying to + * do, though, is to take something that's frequently desirable but inherently + * platform-specific -- opening a default browser -- and allow programmers (you, + * for example) to do so without worrying about dropping into native code or + * doing anything else similarly evil. + *

            * - *

            Anyway, this code is completely in Java and will run on all JDK - * 1.1-compliant systems without modification or a need for additional - * libraries. All classes that are required on certain platforms to - * allow this to run are dynamically loaded at runtime via reflection - * and, if not found, will not cause this to do anything other than - * returning an error when opening the browser.

            + *

            + * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant + * systems without modification or a need for additional libraries. All classes + * that are required on certain platforms to allow this to run are dynamically + * loaded at runtime via reflection and, if not found, will not cause this to do + * anything other than returning an error when opening the browser. + *

            * - *

            There are certain system requirements for this class, as it's - * running through Runtime.exec(), which is Java's way of making a - * native system call. Currently, this requires that a Macintosh have - * a Finder which supports the GURL event, which is true for Mac OS - * 8.0 and 8.1 systems that have the Internet Scripting AppleScript - * dictionary installed in the Scripting Additions folder in the - * Extensions folder (which is installed by default as far as I know - * under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later - * systems. On Windows, it only runs under Win32 systems (Windows 95, - * 98, and NT 4.0, as well as later versions of all). On other - * systems, this drops back from the inherently platform-sensitive - * concept of a default browser and simply attempts to launch Netscape - * via a shell command.

            + *

            + * There are certain system requirements for this class, as it's running through + * Runtime.exec(), which is Java's way of making a native system call. + * Currently, this requires that a Macintosh have a Finder which supports the + * GURL event, which is true for Mac OS 8.0 and 8.1 systems that have the + * Internet Scripting AppleScript dictionary installed in the Scripting + * Additions folder in the Extensions folder (which is installed by default as + * far as I know under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later + * systems. On Windows, it only runs under Win32 systems (Windows 95, 98, and NT + * 4.0, as well as later versions of all). On other systems, this drops back + * from the inherently platform-sensitive concept of a default browser and + * simply attempts to launch Netscape via a shell command. + *

            * - *

            This code is Copyright 1999-2001 by Eric Albert - * (ejalbert@cs.stanford.edu) and may be redistributed or modified in - * any form without restrictions as long as the portion of this - * comment from this paragraph through the end of the comment is not - * removed. The author requests that he be notified of any - * application, applet, or other binary that makes use of this code, - * but that's more out of curiosity than anything and is not required. - * This software includes no warranty. The author is not repsonsible - * for any loss of data or functionality or any adverse or unexpected - * effects of using this software.

            + *

            + * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) + * and may be redistributed or modified in any form without restrictions as long + * as the portion of this comment from this paragraph through the end of the + * comment is not removed. The author requests that he be notified of any + * application, applet, or other binary that makes use of this code, but that's + * more out of curiosity than anything and is not required. This software + * includes no warranty. The author is not repsonsible for any loss of data or + * functionality or any adverse or unexpected effects of using this software. + *

            * - * Credits:
            Steven Spencer, JavaWorld magazine (Java - * Tip 66)
            Thanks also to Ron B. Yeh, Eric Shapiro, Ben - * Engber, Paul Teitlebaum, Andrea Cantatore, Larry Barowski, Trevor - * Bedzek, Frank Miedrich, and Ron Rabakukk + * Credits:
            + * Steven Spencer, JavaWorld magazine + * (Java + * Tip 66)
            + * Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea + * Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk * * - * original author Eric Albert (ejalbert@cs.stanford.edu) + * original author Eric Albert + * (ejalbert@cs.stanford.edu) * author's version 1.4b1 (Released June 20, 2001) + * * @author Copyright 2001, Intersect Software Corporation - * @version $Revision: 905 $ $Date: 2006-02-18 20:44:03 -0500 (Sat, 18 Feb 2006) $ + * @version $Revision: 905 $ $Date: 2006-02-18 20:44:03 -0500 (Sat, 18 Feb 2006) + * $ */ public class BrowserLauncher { - /** The Java virtual machine that we are running on. Actually, in - * most cases we only care about the operating system, but some - * operating systems require us to switch on the VM. */ - private static int jvm; + /** + * The Java virtual machine that we are running on. Actually, in most cases we + * only care about the operating system, but some operating systems require us + * to switch on the VM. + */ + private static int jvm; - /** The browser for the system */ - private static Object browser; + /** The browser for the system */ + private static Object browser; - /** Caches whether any classes, methods, and fields that are not - * part of the JDK and need to be dynamically loaded at runtime - * loaded successfully.

            Note that if this is - * false, openURL() will always return - * an IOException. */ - private static boolean loadedWithoutErrors; + /** + * Caches whether any classes, methods, and fields that are not part of the JDK + * and need to be dynamically loaded at runtime loaded successfully. + *

            + * Note that if this is false, openURL() will always + * return an IOException. + */ + private static boolean loadedWithoutErrors; - /** The com.apple.mrj.MRJFileUtils class */ - private static Class mrjFileUtilsClass; + /** The com.apple.mrj.MRJFileUtils class */ + private static Class mrjFileUtilsClass; - /** The com.apple.mrj.MRJOSType class */ - private static Class mrjOSTypeClass; + /** The com.apple.mrj.MRJOSType class */ + private static Class mrjOSTypeClass; - /** The com.apple.MacOS.AEDesc class */ - private static Class aeDescClass; + /** The com.apple.MacOS.AEDesc class */ + private static Class aeDescClass; - /** The (int) method of com.apple.MacOS.AETarget */ - private static Constructor aeTargetConstructor; + /** The (int) method of com.apple.MacOS.AETarget */ + private static Constructor aeTargetConstructor; - /** The (int, int, int) method of com.apple.MacOS.AppleEvent */ - private static Constructor appleEventConstructor; + /** The (int, int, int) method of com.apple.MacOS.AppleEvent */ + private static Constructor appleEventConstructor; - /** The (String) method of com.apple.MacOS.AEDesc */ - private static Constructor aeDescConstructor; + /** The (String) method of com.apple.MacOS.AEDesc */ + private static Constructor aeDescConstructor; - /** The findFolder method of com.apple.mrj.MRJFileUtils */ - private static Method findFolder; + /** The findFolder method of com.apple.mrj.MRJFileUtils */ + private static Method findFolder; - /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ - private static Method getFileCreator; + /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ + private static Method getFileCreator; - /** The getFileType method of com.apple.mrj.MRJFileUtils */ - private static Method getFileType; + /** The getFileType method of com.apple.mrj.MRJFileUtils */ + private static Method getFileType; - /** The openURL method of com.apple.mrj.MRJFileUtils */ - private static Method openURL; + /** The openURL method of com.apple.mrj.MRJFileUtils */ + private static Method openURL; - /** The makeOSType method of com.apple.MacOS.OSUtils */ - private static Method makeOSType; - - /** The putParameter method of com.apple.MacOS.AppleEvent */ - private static Method putParameter; - - /** The sendNoReply method of com.apple.MacOS.AppleEvent */ - private static Method sendNoReply; - - /** Actually an MRJOSType pointing to the System Folder on a Macintosh */ - private static Object kSystemFolderType; - - /** The keyDirectObject AppleEvent parameter type */ - private static Integer keyDirectObject; - - /** The kAutoGenerateReturnID AppleEvent code */ - private static Integer kAutoGenerateReturnID; - - /** The kAnyTransactionID AppleEvent code */ - private static Integer kAnyTransactionID; - - /** The linkage object required for JDirect 3 on Mac OS X. */ - private static Object linkage; - - /** The framework to reference on Mac OS X */ - private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; - - /** JVM constant for MRJ 2.0 */ - private static final int MRJ_2_0 = 0; - - /** JVM constant for MRJ 2.1 or later */ - private static final int MRJ_2_1 = 1; - - /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */ - private static final int MRJ_3_0 = 3; - - /** JVM constant for MRJ 3.1 */ - private static final int MRJ_3_1 = 4; - - /** JVM constant for any Windows NT JVM */ - private static final int WINDOWS_NT = 5; - - /** JVM constant for any Windows 9x JVM */ - private static final int WINDOWS_9x = 6; - - /** JVM constant for any other platform */ - private static final int OTHER = -1; - - /** The file type of the Finder on a Macintosh. Hardcoding - * "Finder" would keep non-U.S. English systems from working - * properly. */ - private static final String FINDER_TYPE = "FNDR"; - - /** The creator code of the Finder on a Macintosh, which is needed - * to send AppleEvents to the application. */ - private static final String FINDER_CREATOR = "MACS"; - - /** The name for the AppleEvent type corresponding to a GetURL event. */ - private static final String GURL_EVENT = "GURL"; - - /** The first parameter that needs to be passed into - * Runtime.exec() to open the default web browser on Windows. */ - private static final String FIRST_WINDOWS_PARAMETER = "/c"; - - /** The second parameter for Runtime.exec() on Windows. */ - private static final String SECOND_WINDOWS_PARAMETER = "start"; - - /** The third parameter for Runtime.exec() on Windows. This is a - * "title" parameter that the command line expects. Setting this - * parameter allows URLs containing spaces to work. */ - private static final String THIRD_WINDOWS_PARAMETER = "\"\""; - - /** The shell parameters for Netscape that opens a given URL in an - * already-open copy of Netscape on many command-line systems. */ - private static final String NETSCAPE_REMOTE_PARAMETER = "-remote"; - //private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL("; - //private static final String NETSCAPE_OPEN_PARAMETER_END = ")'"; - private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL("; - private static final String NETSCAPE_OPEN_PARAMETER_END = ")"; - - /** The message from any exception thrown throughout the - * initialization process. */ - private static String errorMessage; - - /** An initialization block that determines the operating system - * and loads the necessary runtime data. */ - static { - loadedWithoutErrors = true; - String osName = System.getProperty("os.name"); - if (osName.startsWith("Mac OS")) { - String mrjVersion = System.getProperty("mrj.version"); - String majorMRJVersion = mrjVersion.substring(0, 3); - try { - double version = Double.valueOf(majorMRJVersion).doubleValue(); - if (version == 2) { - jvm = MRJ_2_0; - } else if (version >= 2.1 && version < 3) { - // Assume that all 2.x versions of MRJ work the - // same. MRJ 2.1 actually works via - // Runtime.exec() and 2.2 supports that but has an - // openURL() method as well that we currently - // ignore. - jvm = MRJ_2_1; - } else if (version == 3.0) { - jvm = MRJ_3_0; - } else if (version >= 3.1) { - // Assume that all 3.1 and later versions of MRJ - // work the same. - jvm = MRJ_3_1; - } else { - loadedWithoutErrors = false; - errorMessage = "Unsupported MRJ version: " + version; - } - } catch (NumberFormatException nfe) { - loadedWithoutErrors = false; - errorMessage = "Invalid MRJ version: " + mrjVersion; - } - } else if (osName.startsWith("Windows")) { - if (osName.indexOf("9") != -1) { - jvm = WINDOWS_9x; - } else { - jvm = WINDOWS_NT; - } - } else { - jvm = OTHER; - } - - if (loadedWithoutErrors) { // if we haven't hit any errors yet - loadedWithoutErrors = loadClasses(); - } - } - - /** This class should be never be instantiated; this just ensures so. */ - private BrowserLauncher() { } - - /** Called by a static initializer to load any classes, fields, - * and methods required at runtime to locate the user's web - * browser. - * @return true if all intialization succeeded - * false if any portion of the initialization failed */ - private static boolean loadClasses() { - switch (jvm) { - case MRJ_2_0: - try { - Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); - Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); - Class appleEventClass = Class.forName - ("com.apple.MacOS.AppleEvent"); - Class aeClass = Class.forName("com.apple.MacOS.ae"); - aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); - - aeTargetConstructor = aeTargetClass.getDeclaredConstructor - (new Class [] { int.class }); - appleEventConstructor = appleEventClass.getDeclaredConstructor - (new Class[] { int.class, int.class, aeTargetClass, - int.class, int.class }); - aeDescConstructor = aeDescClass.getDeclaredConstructor - (new Class[] { String.class }); - - makeOSType = osUtilsClass.getDeclaredMethod - ("makeOSType", new Class [] { String.class }); - putParameter = appleEventClass.getDeclaredMethod - ("putParameter", new Class[] { int.class, aeDescClass }); - sendNoReply = appleEventClass.getDeclaredMethod - ("sendNoReply", new Class[] { }); - - Field keyDirectObjectField = aeClass.getDeclaredField - ("keyDirectObject"); - keyDirectObject = (Integer) keyDirectObjectField.get(null); - Field autoGenerateReturnIDField = appleEventClass - .getDeclaredField("kAutoGenerateReturnID"); - kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField - .get(null); - Field anyTransactionIDField = appleEventClass.getDeclaredField - ("kAnyTransactionID"); - kAnyTransactionID = (Integer) anyTransactionIDField.get(null); - } catch (ClassNotFoundException cnfe) { - errorMessage = cnfe.getMessage(); - return false; - } catch (NoSuchMethodException nsme) { - errorMessage = nsme.getMessage(); - return false; - } catch (NoSuchFieldException nsfe) { - errorMessage = nsfe.getMessage(); - return false; - } catch (IllegalAccessException iae) { - errorMessage = iae.getMessage(); - return false; - } - break; - case MRJ_2_1: - try { - mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); - mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); - Field systemFolderField = mrjFileUtilsClass.getDeclaredField - ("kSystemFolderType"); - kSystemFolderType = systemFolderField.get(null); - findFolder = mrjFileUtilsClass.getDeclaredMethod - ("findFolder", new Class[] { mrjOSTypeClass }); - getFileCreator = mrjFileUtilsClass.getDeclaredMethod - ("getFileCreator", new Class[] { File.class }); - getFileType = mrjFileUtilsClass.getDeclaredMethod - ("getFileType", new Class[] { File.class }); - } catch (ClassNotFoundException cnfe) { - errorMessage = cnfe.getMessage(); - return false; - } catch (NoSuchFieldException nsfe) { - errorMessage = nsfe.getMessage(); - return false; - } catch (NoSuchMethodException nsme) { - errorMessage = nsme.getMessage(); - return false; - } catch (SecurityException se) { - errorMessage = se.getMessage(); - return false; - } catch (IllegalAccessException iae) { - errorMessage = iae.getMessage(); - return false; - } - break; - case MRJ_3_0: - try { - Class linker = Class.forName("com.apple.mrj.jdirect.Linker"); - Constructor constructor = linker.getConstructor - (new Class[]{ Class.class }); - linkage = constructor.newInstance(new Object[] - { BrowserLauncher.class }); - } catch (ClassNotFoundException cnfe) { - errorMessage = cnfe.getMessage(); - return false; - } catch (NoSuchMethodException nsme) { - errorMessage = nsme.getMessage(); - return false; - } catch (InvocationTargetException ite) { - errorMessage = ite.getMessage(); - return false; - } catch (InstantiationException ie) { - errorMessage = ie.getMessage(); - return false; - } catch (IllegalAccessException iae) { - errorMessage = iae.getMessage(); - return false; - } - break; - case MRJ_3_1: - try { - mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); - openURL = mrjFileUtilsClass.getDeclaredMethod - ("openURL", new Class[] { String.class }); - } catch (ClassNotFoundException cnfe) { - errorMessage = cnfe.getMessage(); - return false; - } catch (NoSuchMethodException nsme) { - errorMessage = nsme.getMessage(); - return false; - } - break; - default: - break; - } - return true; - } - - /** Attempts to locate the default web browser on the local - * system. Caches results so it only locates the browser once * - * for each use of this class per JVM instance. - * @return The browser for the system. Note that this may not be - * what you would consider to be a standard web browser; instead, - * it's the application that gets called to open the default web - * browser. In some cases, this will be a non-String object that - * provides the means of calling the default browser. */ - private static Object locateBrowser() { - if (browser != null) { - return browser; - } - switch (jvm) { - case MRJ_2_0: - try { - Integer finderCreatorCode = (Integer) makeOSType.invoke - (null, new Object[] { FINDER_CREATOR }); - Object aeTarget = aeTargetConstructor.newInstance - (new Object[] { finderCreatorCode }); - Integer gurlType = (Integer) makeOSType.invoke - (null, new Object[] { GURL_EVENT }); - Object appleEvent = appleEventConstructor.newInstance - (new Object[] { gurlType, gurlType, aeTarget, - kAutoGenerateReturnID, kAnyTransactionID }); - // Don't set browser = appleEvent because then the - // next time we call locateBrowser(), we'll get the - // same AppleEvent, to which we'll already have added - // the relevant parameter. Instead, regenerate the - // AppleEvent every time. There's probably a way to - // do this better; if any has any ideas, please let me - // know. - return appleEvent; - } catch (IllegalAccessException iae) { - browser = null; - errorMessage = iae.getMessage(); - return browser; - } catch (InstantiationException ie) { - browser = null; - errorMessage = ie.getMessage(); - return browser; - } catch (InvocationTargetException ite) { - browser = null; - errorMessage = ite.getMessage(); - return browser; - } - case MRJ_2_1: - File systemFolder; - try { - systemFolder = (File) findFolder.invoke(null, new Object[] - { kSystemFolderType }); - } catch (IllegalArgumentException iare) { - browser = null; - errorMessage = iare.getMessage(); - return browser; - } catch (IllegalAccessException iae) { - browser = null; - errorMessage = iae.getMessage(); - return browser; - } catch (InvocationTargetException ite) { - browser = null; - errorMessage = ite.getTargetException().getClass() + ": " + - ite.getTargetException().getMessage(); - return browser; - } - String[] systemFolderFiles = systemFolder.list(); - // Avoid a FilenameFilter because that can't be stopped mid-list - for(int i = 0; i < systemFolderFiles.length; i++) { - try { - File file = new File(systemFolder, systemFolderFiles[i]); - if (!file.isFile()) { - continue; - } - // We're looking for a file with a creator code of - // 'MACS' and a type of 'FNDR'. Only requiring - // the type results in non-Finder applications - // being picked up on certain Mac OS 9 systems, - // especially German ones, and sending a GURL - // event to those applications results in a logout - // under Multiple Users. - Object fileType = getFileType.invoke - (null, new Object[] { file }); - if (FINDER_TYPE.equals(fileType.toString())) { - Object fileCreator = getFileCreator.invoke - (null, new Object[] { file }); - if (FINDER_CREATOR.equals(fileCreator.toString())) { - browser = file.toString(); // Actually the - // Finder, but that's OK - return browser; - } - } - } catch (IllegalArgumentException iare) { - //WTF? browser = browser; - errorMessage = iare.getMessage(); - return null; - } catch (IllegalAccessException iae) { - browser = null; - errorMessage = iae.getMessage(); - return browser; - } catch (InvocationTargetException ite) { - browser = null; - errorMessage = ite.getTargetException().getClass() + ": " - + ite.getTargetException().getMessage(); - return browser; - } - } - browser = null; - break; - case MRJ_3_0: - case MRJ_3_1: - browser = ""; // Return something non-null - break; - case WINDOWS_NT: - browser = "cmd.exe"; - break; - case WINDOWS_9x: - browser = "command.com"; - break; - case OTHER: - default: - browser = "netscape"; - break; - } - return browser; - } - - /** Attempts to open the default web browser to the given URL. - * @param url The URL to open - * @throws IOException If the web browser could not be located or - * does not run */ - public static void openURL(String url) throws IOException { - if (!loadedWithoutErrors) { - throw new IOException("Exception in finding browser: " - + errorMessage); - } - Object browser = locateBrowser(); - if (browser == null) { - throw new IOException("Unable to locate browser: " + errorMessage); - } - - switch (jvm) { - case MRJ_2_0: - Object aeDesc = null; - try { - aeDesc = aeDescConstructor.newInstance(new Object[] { url }); - putParameter.invoke(browser, new Object[] - { keyDirectObject, aeDesc }); - sendNoReply.invoke(browser, new Object[] { }); - } catch (InvocationTargetException ite) { - throw new IOException("InvocationTargetException while creating" - +" AEDesc: " + ite.getMessage()); - } catch (IllegalAccessException iae) { - throw new IOException("IllegalAccessException while building " - + "AppleEvent: " + iae.getMessage()); - } catch (InstantiationException ie) { - throw new IOException("InstantiationException while creating " - + "AEDesc: " + ie.getMessage()); - } finally { - aeDesc = null; // Encourage it to get disposed if it - // was created - browser = null; // Ditto - } - break; - case MRJ_2_1: - Runtime.getRuntime().exec(new String[] { (String) browser, url } ); - break; - case MRJ_3_0: - int[] instance = new int[1]; - int result = ICStart(instance, 0); - if (result == 0) { - int[] selectionStart = new int[] { 0 }; - byte[] urlBytes = url.getBytes(); - int[] selectionEnd = new int[] { urlBytes.length }; - result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, - urlBytes.length, selectionStart, - selectionEnd); - if (result == 0) { - // Ignore the return value; the URL was launched - // successfully regardless of what happens here. - ICStop(instance); - } else { - throw new IOException("Unable to launch URL: " + result); - } - } else { - throw new IOException("Unable to create an Internet Config " - + "instance: " + result); - } - break; - case MRJ_3_1: - try { - openURL.invoke(null, new Object[] { url }); - } catch (InvocationTargetException ite) { - throw new IOException("InvocationTargetException while calling " - + "openURL: " + ite.getMessage()); - } catch (IllegalAccessException iae) { - throw new IOException("IllegalAccessException while calling " - + "openURL: " + iae.getMessage()); - } - break; - case WINDOWS_NT: - // Add quotes around the URL to allow ampersands and other special - // characters to work. - Process process = Runtime.getRuntime().exec(new String[] - { (String) browser, FIRST_WINDOWS_PARAMETER, - SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER, - '"' + url + '"' }); - // This avoids a memory leak on some versions of Java on - // Windows. That's hinted at in - // . - try { - process.waitFor(); - process.exitValue(); - } catch (InterruptedException ie) { - throw new IOException("InterruptedException while launching " - + "browser: " + ie.getMessage()); - } - break; - case WINDOWS_9x: - // Add quotes around the URL to allow ampersands and other special - // characters to work. - // Note: windows 98 doesn't expect the THIRD_WINDOWS_PARAMETER for - // its title. - process = Runtime.getRuntime().exec(new String[] - { (String) browser, FIRST_WINDOWS_PARAMETER, - SECOND_WINDOWS_PARAMETER, '"' + url + '"' }); - // This avoids a memory leak on some versions of Java on - // Windows. That's hinted at in - // . - try { - process.waitFor(); - process.exitValue(); - } catch (InterruptedException ie) { - throw new IOException("InterruptedException while launching " - + "browser: " + ie.getMessage()); - } - break; - case OTHER: - // Assume that we're on Unix and that Netscape is installed - - // First, attempt to open the URL in a currently running - // session of Netscape - process = Runtime.getRuntime().exec(new String[] - {(String)browser, NETSCAPE_REMOTE_PARAMETER, - NETSCAPE_OPEN_PARAMETER_START + url + - NETSCAPE_OPEN_PARAMETER_END }); - try { - int exitCode = process.waitFor(); - if (exitCode != 0) { // if the command had an error - Runtime.getRuntime().exec(new String[] - { (String) browser, url } ); - } else if(process.getErrorStream() != null) { - // Netscape may not be open, so the command may not have an - // error, it just wouldn't have a process to attach to... - BufferedReader reader = new BufferedReader - (new InputStreamReader(process.getErrorStream())); - String errorStr = reader.readLine(); - - if ( errorStr != null ) { - // Command failed, start up the browser - process = Runtime.getRuntime().exec(new String[] { - (String) browser, url }); - } - } - } catch (InterruptedException ie) { - throw new IOException("InterruptedException while launching " - + "browser: " + ie.getMessage()); - } - break; - default: - // This should never occur, but if it does, we'll try the - // simplest thing possible - Runtime.getRuntime().exec(new String[] { (String) browser, url }); - break; - } - } - - /** Methods required for Mac OS X. The presence of native methods - * does not cause any problems on other platforms. */ - private native static int ICStart(int[] instance, int signature); - private native static int ICStop(int[] instance); - private native static int ICLaunchURL - (int instance, byte[] hint, byte[] data, int len, int[] selectionStart, - int[] selectionEnd); + /** The makeOSType method of com.apple.MacOS.OSUtils */ + private static Method makeOSType; + + /** The putParameter method of com.apple.MacOS.AppleEvent */ + private static Method putParameter; + + /** The sendNoReply method of com.apple.MacOS.AppleEvent */ + private static Method sendNoReply; + + /** Actually an MRJOSType pointing to the System Folder on a Macintosh */ + private static Object kSystemFolderType; + + /** The keyDirectObject AppleEvent parameter type */ + private static Integer keyDirectObject; + + /** The kAutoGenerateReturnID AppleEvent code */ + private static Integer kAutoGenerateReturnID; + + /** The kAnyTransactionID AppleEvent code */ + private static Integer kAnyTransactionID; + + /** The linkage object required for JDirect 3 on Mac OS X. */ + private static Object linkage; + + /** The framework to reference on Mac OS X */ + private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; + + /** JVM constant for MRJ 2.0 */ + private static final int MRJ_2_0 = 0; + + /** JVM constant for MRJ 2.1 or later */ + private static final int MRJ_2_1 = 1; + + /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */ + private static final int MRJ_3_0 = 3; + + /** JVM constant for MRJ 3.1 */ + private static final int MRJ_3_1 = 4; + + /** JVM constant for any Windows NT JVM */ + private static final int WINDOWS_NT = 5; + + /** JVM constant for any Windows 9x JVM */ + private static final int WINDOWS_9x = 6; + + /** JVM constant for any other platform */ + private static final int OTHER = -1; + + /** + * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep + * non-U.S. English systems from working properly. + */ + private static final String FINDER_TYPE = "FNDR"; + + /** + * The creator code of the Finder on a Macintosh, which is needed to send + * AppleEvents to the application. + */ + private static final String FINDER_CREATOR = "MACS"; + + /** The name for the AppleEvent type corresponding to a GetURL event. */ + private static final String GURL_EVENT = "GURL"; + + /** + * The first parameter that needs to be passed into Runtime.exec() to open the + * default web browser on Windows. + */ + private static final String FIRST_WINDOWS_PARAMETER = "/c"; + + /** The second parameter for Runtime.exec() on Windows. */ + private static final String SECOND_WINDOWS_PARAMETER = "start"; + + /** + * The third parameter for Runtime.exec() on Windows. This is a "title" + * parameter that the command line expects. Setting this parameter allows URLs + * containing spaces to work. + */ + private static final String THIRD_WINDOWS_PARAMETER = "\"\""; + + /** + * The shell parameters for Netscape that opens a given URL in an already-open + * copy of Netscape on many command-line systems. + */ + private static final String NETSCAPE_REMOTE_PARAMETER = "-remote"; + // private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL("; + // private static final String NETSCAPE_OPEN_PARAMETER_END = ")'"; + private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL("; + private static final String NETSCAPE_OPEN_PARAMETER_END = ")"; + + /** + * The message from any exception thrown throughout the initialization process. + */ + private static String errorMessage; + + /** + * An initialization block that determines the operating system and loads the + * necessary runtime data. + */ + static { + loadedWithoutErrors = true; + String osName = System.getProperty("os.name"); + if (osName.startsWith("Mac OS")) { + String mrjVersion = System.getProperty("mrj.version"); + String majorMRJVersion = mrjVersion.substring(0, 3); + try { + double version = Double.valueOf(majorMRJVersion).doubleValue(); + if (version == 2) { + jvm = MRJ_2_0; + } else if (version >= 2.1 && version < 3) { + // Assume that all 2.x versions of MRJ work the + // same. MRJ 2.1 actually works via + // Runtime.exec() and 2.2 supports that but has an + // openURL() method as well that we currently + // ignore. + jvm = MRJ_2_1; + } else if (version == 3.0) { + jvm = MRJ_3_0; + } else if (version >= 3.1) { + // Assume that all 3.1 and later versions of MRJ + // work the same. + jvm = MRJ_3_1; + } else { + loadedWithoutErrors = false; + errorMessage = "Unsupported MRJ version: " + version; + } + } catch (NumberFormatException nfe) { + loadedWithoutErrors = false; + errorMessage = "Invalid MRJ version: " + mrjVersion; + } + } else if (osName.startsWith("Windows")) { + if (osName.indexOf("9") != -1) { + jvm = WINDOWS_9x; + } else { + jvm = WINDOWS_NT; + } + } else { + jvm = OTHER; + } + + if (loadedWithoutErrors) { // if we haven't hit any errors yet + loadedWithoutErrors = loadClasses(); + } + } + + /** This class should be never be instantiated; this just ensures so. */ + private BrowserLauncher() { + } + + /** + * Called by a static initializer to load any classes, fields, and methods + * required at runtime to locate the user's web browser. + * + * @return true if all intialization succeeded false + * if any portion of the initialization failed + */ + private static boolean loadClasses() { + switch (jvm) { + case MRJ_2_0: + try { + Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); + Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); + Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent"); + Class aeClass = Class.forName("com.apple.MacOS.ae"); + aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); + + aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class[] { int.class }); + appleEventConstructor = appleEventClass.getDeclaredConstructor( + new Class[] { int.class, int.class, aeTargetClass, int.class, int.class }); + aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class }); + + makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class[] { String.class }); + putParameter = appleEventClass.getDeclaredMethod("putParameter", + new Class[] { int.class, aeDescClass }); + sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] {}); + + Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject"); + keyDirectObject = (Integer) keyDirectObjectField.get(null); + Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID"); + kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null); + Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID"); + kAnyTransactionID = (Integer) anyTransactionIDField.get(null); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_2_1: + try { + mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); + mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); + Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType"); + kSystemFolderType = systemFolderField.get(null); + findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass }); + getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class }); + getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (SecurityException se) { + errorMessage = se.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_3_0: + try { + Class linker = Class.forName("com.apple.mrj.jdirect.Linker"); + Constructor constructor = linker.getConstructor(new Class[] { Class.class }); + linkage = constructor.newInstance(new Object[] { BrowserLauncher.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } catch (InvocationTargetException ite) { + errorMessage = ite.getMessage(); + return false; + } catch (InstantiationException ie) { + errorMessage = ie.getMessage(); + return false; + } catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_3_1: + try { + mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); + openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class }); + } catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } + break; + default: + break; + } + return true; + } + + /** + * Attempts to locate the default web browser on the local system. Caches + * results so it only locates the browser once * for each use of this class per + * JVM instance. + * + * @return The browser for the system. Note that this may not be what you would + * consider to be a standard web browser; instead, it's the application + * that gets called to open the default web browser. In some cases, this + * will be a non-String object that provides the means of calling the + * default browser. + */ + private static Object locateBrowser() { + if (browser != null) { + return browser; + } + switch (jvm) { + case MRJ_2_0: + try { + Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR }); + Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode }); + Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT }); + Object appleEvent = appleEventConstructor.newInstance( + new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID }); + // Don't set browser = appleEvent because then the + // next time we call locateBrowser(), we'll get the + // same AppleEvent, to which we'll already have added + // the relevant parameter. Instead, regenerate the + // AppleEvent every time. There's probably a way to + // do this better; if any has any ideas, please let me + // know. + return appleEvent; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InstantiationException ie) { + browser = null; + errorMessage = ie.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getMessage(); + return browser; + } + case MRJ_2_1: + File systemFolder; + try { + systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType }); + } catch (IllegalArgumentException iare) { + browser = null; + errorMessage = iare.getMessage(); + return browser; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + String[] systemFolderFiles = systemFolder.list(); + // Avoid a FilenameFilter because that can't be stopped mid-list + for (int i = 0; i < systemFolderFiles.length; i++) { + try { + File file = new File(systemFolder, systemFolderFiles[i]); + if (!file.isFile()) { + continue; + } + // We're looking for a file with a creator code of + // 'MACS' and a type of 'FNDR'. Only requiring + // the type results in non-Finder applications + // being picked up on certain Mac OS 9 systems, + // especially German ones, and sending a GURL + // event to those applications results in a logout + // under Multiple Users. + Object fileType = getFileType.invoke(null, new Object[] { file }); + if (FINDER_TYPE.equals(fileType.toString())) { + Object fileCreator = getFileCreator.invoke(null, new Object[] { file }); + if (FINDER_CREATOR.equals(fileCreator.toString())) { + browser = file.toString(); // Actually the + // Finder, but that's OK + return browser; + } + } + } catch (IllegalArgumentException iare) { + // WTF? browser = browser; + errorMessage = iare.getMessage(); + return null; + } catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + } + browser = null; + break; + case MRJ_3_0: + case MRJ_3_1: + browser = ""; // Return something non-null + break; + case WINDOWS_NT: + browser = "cmd.exe"; + break; + case WINDOWS_9x: + browser = "command.com"; + break; + case OTHER: + default: + browser = "netscape"; + break; + } + return browser; + } + + /** + * Attempts to open the default web browser to the given URL. + * + * @param url The URL to open + * @throws IOException If the web browser could not be located or does not run + */ + public static void openURL(String url) throws IOException { + if (!loadedWithoutErrors) { + throw new IOException("Exception in finding browser: " + errorMessage); + } + Object browser = locateBrowser(); + if (browser == null) { + throw new IOException("Unable to locate browser: " + errorMessage); + } + + switch (jvm) { + case MRJ_2_0: + Object aeDesc = null; + try { + aeDesc = aeDescConstructor.newInstance(new Object[] { url }); + putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc }); + sendNoReply.invoke(browser, new Object[] {}); + } catch (InvocationTargetException ite) { + throw new IOException("InvocationTargetException while creating" + " AEDesc: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + throw new IOException("IllegalAccessException while building " + "AppleEvent: " + iae.getMessage()); + } catch (InstantiationException ie) { + throw new IOException("InstantiationException while creating " + "AEDesc: " + ie.getMessage()); + } finally { + aeDesc = null; // Encourage it to get disposed if it + // was created + browser = null; // Ditto + } + break; + case MRJ_2_1: + Runtime.getRuntime().exec(new String[] { (String) browser, url }); + break; + case MRJ_3_0: + int[] instance = new int[1]; + int result = ICStart(instance, 0); + if (result == 0) { + int[] selectionStart = new int[] { 0 }; + byte[] urlBytes = url.getBytes(); + int[] selectionEnd = new int[] { urlBytes.length }; + result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, urlBytes.length, selectionStart, + selectionEnd); + if (result == 0) { + // Ignore the return value; the URL was launched + // successfully regardless of what happens here. + ICStop(instance); + } else { + throw new IOException("Unable to launch URL: " + result); + } + } else { + throw new IOException("Unable to create an Internet Config " + "instance: " + result); + } + break; + case MRJ_3_1: + try { + openURL.invoke(null, new Object[] { url }); + } catch (InvocationTargetException ite) { + throw new IOException("InvocationTargetException while calling " + "openURL: " + ite.getMessage()); + } catch (IllegalAccessException iae) { + throw new IOException("IllegalAccessException while calling " + "openURL: " + iae.getMessage()); + } + break; + case WINDOWS_NT: + // Add quotes around the URL to allow ampersands and other special + // characters to work. + Process process = Runtime.getRuntime().exec(new String[] { (String) browser, FIRST_WINDOWS_PARAMETER, + SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER, '"' + url + '"' }); + // This avoids a memory leak on some versions of Java on + // Windows. That's hinted at in + // . + try { + process.waitFor(); + process.exitValue(); + } catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching " + "browser: " + ie.getMessage()); + } + break; + case WINDOWS_9x: + // Add quotes around the URL to allow ampersands and other special + // characters to work. + // Note: windows 98 doesn't expect the THIRD_WINDOWS_PARAMETER for + // its title. + process = Runtime.getRuntime().exec(new String[] { (String) browser, FIRST_WINDOWS_PARAMETER, + SECOND_WINDOWS_PARAMETER, '"' + url + '"' }); + // This avoids a memory leak on some versions of Java on + // Windows. That's hinted at in + // . + try { + process.waitFor(); + process.exitValue(); + } catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching " + "browser: " + ie.getMessage()); + } + break; + case OTHER: + // Assume that we're on Unix and that Netscape is installed + + // First, attempt to open the URL in a currently running + // session of Netscape + process = Runtime.getRuntime().exec(new String[] { (String) browser, NETSCAPE_REMOTE_PARAMETER, + NETSCAPE_OPEN_PARAMETER_START + url + NETSCAPE_OPEN_PARAMETER_END }); + try { + int exitCode = process.waitFor(); + if (exitCode != 0) { // if the command had an error + Runtime.getRuntime().exec(new String[] { (String) browser, url }); + } else if (process.getErrorStream() != null) { + // Netscape may not be open, so the command may not have an + // error, it just wouldn't have a process to attach to... + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + String errorStr = reader.readLine(); + + if (errorStr != null) { + // Command failed, start up the browser + process = Runtime.getRuntime().exec(new String[] { (String) browser, url }); + } + } + } catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching " + "browser: " + ie.getMessage()); + } + break; + default: + // This should never occur, but if it does, we'll try the + // simplest thing possible + Runtime.getRuntime().exec(new String[] { (String) browser, url }); + break; + } + } + + /** + * Methods required for Mac OS X. The presence of native methods does not cause + * any problems on other platforms. + */ + private native static int ICStart(int[] instance, int signature); + + private native static int ICStop(int[] instance); + + private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, int[] selectionStart, + int[] selectionEnd); } - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java index 8da71fe..54658c0 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java @@ -32,83 +32,73 @@ import net.wotonomy.foundation.xml.XMLDecoder; import org.xml.sax.SAXException; /** -* An implementation of XMLDecoder that reads objects from -* XMLRPC format. -* This implementation is not thread-safe, so a new instances -* should be created to accomodate multiple threads. -*/ -public class XMLRPCDecoder implements XMLDecoder -{ - XMLRPCDecoderHelper helper = new XMLRPCDecoderHelper(); - - /** - * Decodes an object in XML-RPC format from the specified input stream. - * @param anInputStream The input stream from which to read. - * The stream will be read fully. - * @param aDescription A description to accompany error messages - * for the stream, typically a file name. - * @param aURL A URL against which relative references within the - * XML will be resolved. - * @return The object that was constructed from the XML content, - * or null if no object could be constructed. - */ - public Object decode( - InputStream anInputStream, String aDescription, URL aURL ) - { - Object result = null; - - try { - SAXParser sparser = SAXParserFactory.newInstance().newSAXParser(); - sparser.parse(anInputStream,helper,aURL.toExternalForm()); - result = helper.getResult(); - } catch (ParserConfigurationException e) { - throw new WotonomyException("Problem in parser configuration", e ); - } catch (IOException e) { - throw new WotonomyException("IOException thrown while parsing", e ); - } catch (SAXException e) { - throw new WotonomyException("SAXException thrown while parsing", e ); - } - helper.reset(); - return result; - } + * An implementation of XMLDecoder that reads objects from XMLRPC format. This + * implementation is not thread-safe, so a new instances should be created to + * accomodate multiple threads. + */ +public class XMLRPCDecoder implements XMLDecoder { + XMLRPCDecoderHelper helper = new XMLRPCDecoderHelper(); + + /** + * Decodes an object in XML-RPC format from the specified input stream. + * + * @param anInputStream The input stream from which to read. The stream will be + * read fully. + * @param aDescription A description to accompany error messages for the + * stream, typically a file name. + * @param aURL A URL against which relative references within the XML + * will be resolved. + * @return The object that was constructed from the XML content, or null if no + * object could be constructed. + */ + public Object decode(InputStream anInputStream, String aDescription, URL aURL) { + Object result = null; + + try { + SAXParser sparser = SAXParserFactory.newInstance().newSAXParser(); + sparser.parse(anInputStream, helper, aURL.toExternalForm()); + result = helper.getResult(); + } catch (ParserConfigurationException e) { + throw new WotonomyException("Problem in parser configuration", e); + } catch (IOException e) { + throw new WotonomyException("IOException thrown while parsing", e); + } catch (SAXException e) { + throw new WotonomyException("SAXException thrown while parsing", e); + } + helper.reset(); + return result; + } + + /** + * Decodes an XML-RPC message from the specified input stream. Stand-alone + * values not wrapped in "methodCall" or "param" tags will be treated as a + * response. + * + * @param anInputStream The input stream from which to read. The stream will be + * read fully. + * @param aReceiver an XMLRPCReceiver that will be invoked with the + * appropriate method: request, response, or fault. + */ + public void decode(InputStream anInputStream, XMLRPCReceiver aReceiver) { + try { + SAXParser sparser = SAXParserFactory.newInstance().newSAXParser(); + sparser.parse(anInputStream, helper); - /** - * Decodes an XML-RPC message from the specified input stream. - * Stand-alone values not wrapped in "methodCall" or "param" - * tags will be treated as a response. - * @param anInputStream The input stream from which to read. - * The stream will be read fully. - * @param aReceiver an XMLRPCReceiver that will be invoked with - * the appropriate method: request, response, or fault. - */ - public void decode( - InputStream anInputStream, XMLRPCReceiver aReceiver ) - { - try - { - SAXParser sparser = SAXParserFactory.newInstance().newSAXParser(); - sparser.parse(anInputStream,helper); - - if ( helper.isRequest() ) - { - aReceiver.request( helper.getMethodName(), helper.getParameters() ); - } - else - if ( helper.isFault() ) - { - aReceiver.fault( helper.getFaultCode(), helper.getFaultString() ); - } - else // all else is considered a response - { - aReceiver.response( helper.getResult() ); - } - } catch (ParserConfigurationException e) { - throw new WotonomyException("Problem in parser configuration", e ); - } catch (IOException e) { - throw new WotonomyException("IOException thrown while parsing", e ); - } catch (SAXException e) { - throw new WotonomyException("SAXException thrown while parsing", e ); - } - helper.reset(); - } + if (helper.isRequest()) { + aReceiver.request(helper.getMethodName(), helper.getParameters()); + } else if (helper.isFault()) { + aReceiver.fault(helper.getFaultCode(), helper.getFaultString()); + } else // all else is considered a response + { + aReceiver.response(helper.getResult()); + } + } catch (ParserConfigurationException e) { + throw new WotonomyException("Problem in parser configuration", e); + } catch (IOException e) { + throw new WotonomyException("IOException thrown while parsing", e); + } catch (SAXException e) { + throw new WotonomyException("SAXException thrown while parsing", e); + } + helper.reset(); + } } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java index 2368672..0806d88 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java @@ -38,77 +38,64 @@ import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** -* Used by XMLDecoder to implement the necessary interfaces -* required by the jclark xp parser. -* This class is not thread safe. -*/ -class XMLRPCDecoderHelper extends DefaultHandler -{ - protected final Object nilMarker = new Object(); - protected Stack valueStack; - protected String methodName; - protected int faultCode; - protected String faultString; - protected List parameters; - protected StringBuffer cdataBuffer; - - - - public XMLRPCDecoderHelper() - { - valueStack = new Stack(); - parameters = new LinkedList(); - cdataBuffer = new StringBuffer(); - reset(); - } - - public void reset() - { - valueStack.clear(); - parameters.clear(); - cdataBuffer.setLength( 0 ); - methodName = null; - faultCode = 0; - faultString = null; - } - - public boolean isRequest() - { - return ( methodName != null ); - } - - public boolean isResponse() - { - return ( methodName == null ); - } - - public boolean isFault() - { - // faults are responses - return ( isResponse() ) && ( faultString != null ); - } - - public int getFaultCode() - { - return faultCode; - } - - public String getFaultString() - { - return faultString; - } - - public String getMethodName() - { - return methodName; - } - - public Object[] getParameters() - { - return parameters.toArray(); - } - - public void endDocument() throws SAXException { + * Used by XMLDecoder to implement the necessary interfaces required by the + * jclark xp parser. This class is not thread safe. + */ +class XMLRPCDecoderHelper extends DefaultHandler { + protected final Object nilMarker = new Object(); + protected Stack valueStack; + protected String methodName; + protected int faultCode; + protected String faultString; + protected List parameters; + protected StringBuffer cdataBuffer; + + public XMLRPCDecoderHelper() { + valueStack = new Stack(); + parameters = new LinkedList(); + cdataBuffer = new StringBuffer(); + reset(); + } + + public void reset() { + valueStack.clear(); + parameters.clear(); + cdataBuffer.setLength(0); + methodName = null; + faultCode = 0; + faultString = null; + } + + public boolean isRequest() { + return (methodName != null); + } + + public boolean isResponse() { + return (methodName == null); + } + + public boolean isFault() { + // faults are responses + return (isResponse()) && (faultString != null); + } + + public int getFaultCode() { + return faultCode; + } + + public String getFaultString() { + return faultString; + } + + public String getMethodName() { + return methodName; + } + + public Object[] getParameters() { + return parameters.toArray(); + } + + public void endDocument() throws SAXException { // TODO Auto-generated method stub super.endDocument(); } @@ -118,47 +105,40 @@ class XMLRPCDecoderHelper extends DefaultHandler super.startDocument(); reset(); } - - public Object getResult() - { - if ( valueStack.empty() ) return null; - Object result = valueStack.peek(); - if ( result == nilMarker ) result = null; - return result; - } - - + + public Object getResult() { + if (valueStack.empty()) + return null; + Object result = valueStack.peek(); + if (result == nilMarker) + result = null; + return result; + } + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); - if ( XMLRPCEncoder.VALUE.equals( localName ) ) - { - ValueMarker marker = new ValueMarker(); - String classname = attributes.getValue( uri, XMLRPCEncoder.CLASS ); - if ( classname != null ) - { - try - { - Class c = Class.forName( classname ); - if ( c != null ) - { - marker.setMarkerClass( c ); - } - } - catch ( Exception exc ) - { - System.out.println( "XMLRPCDecoderHelper.startElement: " + - "Can't find class: " + classname ); - } - } - valueStack.push( marker ); - } + if (XMLRPCEncoder.VALUE.equals(localName)) { + ValueMarker marker = new ValueMarker(); + String classname = attributes.getValue(uri, XMLRPCEncoder.CLASS); + if (classname != null) { + try { + Class c = Class.forName(classname); + if (c != null) { + marker.setMarkerClass(c); + } + } catch (Exception exc) { + System.out.println("XMLRPCDecoderHelper.startElement: " + "Can't find class: " + classname); + } + } + valueStack.push(marker); + } } - - public void characters(char[] ch, int start, int length) throws SAXException { + + public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); - char[] someChars = new char[((start+length)<=ch.length) ? length : ch.length-start]; - for (int i = 0; i < someChars.length; i++ ) { - someChars[i] = ch[start+i]; + char[] someChars = new char[((start + length) <= ch.length) ? length : ch.length - start]; + for (int i = 0; i < someChars.length; i++) { + someChars[i] = ch[start + i]; } cdataBuffer.append(someChars); } @@ -166,231 +146,140 @@ class XMLRPCDecoderHelper extends DefaultHandler public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); - // if any cdata is buffered or if string value - if ( ( XMLRPCEncoder.STRING.equals( localName ) ) - || ( cdataBuffer.length() > 0 ) ) - { - // push value on the stack - valueStack.push( cdataBuffer.toString() ); - cdataBuffer.setLength( 0 ); - } - - if ( XMLRPCEncoder.VALUE.equals( localName ) ) - { - Object value = valueStack.pop(); - try - { + // if any cdata is buffered or if string value + if ((XMLRPCEncoder.STRING.equals(localName)) || (cdataBuffer.length() > 0)) { + // push value on the stack + valueStack.push(cdataBuffer.toString()); + cdataBuffer.setLength(0); + } + + if (XMLRPCEncoder.VALUE.equals(localName)) { + Object value = valueStack.pop(); + try { // ValueMarker marker = (ValueMarker) valueStack.pop(); - ValueMarker marker = null; - Object markerValue = valueStack.pop(); - if ( markerValue instanceof ValueMarker ) - { - marker = (ValueMarker) markerValue; - } - else - { - throw new WotonomyException( "Expected value marker, found" - + markerValue.getClass() + " : " + markerValue ); - } - + ValueMarker marker = null; + Object markerValue = valueStack.pop(); + if (markerValue instanceof ValueMarker) { + marker = (ValueMarker) markerValue; + } else { + throw new WotonomyException( + "Expected value marker, found" + markerValue.getClass() + " : " + markerValue); + } + //System.out.println( "getMarkerClass: " + marker.getMarkerClass() + " : " + value ); //System.out.println( valueStack ); - if ( marker.getMarkerClass() != null ) - { - // apply introspection - if ( value instanceof Map ) - { - Map map = (Map)value; - Map.Entry entry; - value = marker.getMarkerClass().newInstance(); - Iterator it = map.entrySet().iterator(); - Object entryValue; - while( it.hasNext() ) - { - entry = (Map.Entry) it.next(); - entryValue = entry.getValue(); - if ( entryValue == nilMarker ) entryValue = null; - Introspector.set( - value, entry.getKey().toString(), entryValue ); - } - } - if ( ! ( value.getClass().equals( marker.getMarkerClass() ) ) ) - { - Object converted = - ValueConverter.convertObjectToClass( - value, marker.getMarkerClass() ); - if ( converted != null ) - { - value = converted; - } - } - } - } - catch ( Exception exc ) - { - // fall back on unconverted value - } - - valueStack.push( value ); + if (marker.getMarkerClass() != null) { + // apply introspection + if (value instanceof Map) { + Map map = (Map) value; + Map.Entry entry; + value = marker.getMarkerClass().newInstance(); + Iterator it = map.entrySet().iterator(); + Object entryValue; + while (it.hasNext()) { + entry = (Map.Entry) it.next(); + entryValue = entry.getValue(); + if (entryValue == nilMarker) + entryValue = null; + Introspector.set(value, entry.getKey().toString(), entryValue); + } + } + if (!(value.getClass().equals(marker.getMarkerClass()))) { + Object converted = ValueConverter.convertObjectToClass(value, marker.getMarkerClass()); + if (converted != null) { + value = converted; + } + } + } + } catch (Exception exc) { + // fall back on unconverted value + } + + valueStack.push(value); // System.out.println( "convertedValue: " + value + "("+ value.getClass() +")" ); - } - else - if ( XMLRPCEncoder.MEMBER.equals( localName ) ) // Map.Entry - { - // leave key and value to be handled by struct - } - else - if ( XMLRPCEncoder.STRUCT.equals( localName ) ) // write Entries to map or object - { - // write values to array (reverse the order) - Object value; - Map map = new HashMap(); - while ( ( ! valueStack.empty() ) - && ( ! ( valueStack.peek() instanceof ValueMarker ) ) ) - { - value = valueStack.pop(); - map.put( valueStack.pop(), value ); - } - // push the list on the stack - valueStack.push( map ); - } - else - if ( XMLRPCEncoder.ARRAY.equals( localName ) ) - { + } else if (XMLRPCEncoder.MEMBER.equals(localName)) // Map.Entry + { + // leave key and value to be handled by struct + } else if (XMLRPCEncoder.STRUCT.equals(localName)) // write Entries to map or object + { + // write values to array (reverse the order) + Object value; + Map map = new HashMap(); + while ((!valueStack.empty()) && (!(valueStack.peek() instanceof ValueMarker))) { + value = valueStack.pop(); + map.put(valueStack.pop(), value); + } + // push the list on the stack + valueStack.push(map); + } else if (XMLRPCEncoder.ARRAY.equals(localName)) { //System.out.println( "ended ARRAY: " + valueStack.size() ); - // write values to array (reverse the order) - Object value; - LinkedList list = new LinkedList(); - while ( ( ! valueStack.empty() ) - && ( ! ( valueStack.peek() instanceof ValueMarker ) ) ) - { - value = valueStack.pop(); - if ( value == nilMarker ) value = null; - list.addFirst( value ); - } - // push the list on the stack - valueStack.push( list ); - } - else - if ( XMLRPCEncoder.INT.equals( localName ) ) - { - Object value = valueStack.pop(); - try - { - valueStack.push( - new Integer( value.toString() ) ); - } - catch ( NumberFormatException exc ) - { - throw new WotonomyException( - "Invalid double format: " + value.toString() ); - } - } - else - if ( XMLRPCEncoder.I4.equals( localName ) ) - { - Object value = valueStack.pop(); - try - { - valueStack.push( - new Integer( value.toString() ) ); - } - catch ( NumberFormatException exc ) - { - throw new WotonomyException( - "Invalid double format: " + value.toString() ); - } - } - else - if ( XMLRPCEncoder.NIL.equals( localName ) ) - { - valueStack.push( nilMarker ); - } - else - if ( XMLRPCEncoder.DOUBLE.equals( localName ) ) - { - Object value = valueStack.pop(); - try - { - valueStack.push( - new Double( value.toString() ) ); - } - catch ( NumberFormatException exc ) - { - throw new WotonomyException( - "Invalid double format: " + value.toString() ); - } - } - else - if ( XMLRPCEncoder.DATE.equals( localName ) ) - { - Object value = valueStack.pop(); - try - { - valueStack.push( - XMLRPCEncoder.DATEFORMAT8601.parseObject( - value.toString() ) ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Invalid date format: " + value ); - } - } - else - if ( XMLRPCEncoder.BOOLEAN.equals( localName ) ) - { - Object value = valueStack.pop(); - if ( XMLRPCEncoder.TRUE.equals( value ) ) - { - valueStack.push( Boolean.TRUE ); - } - else - if ( XMLRPCEncoder.FALSE.equals( value ) ) - { - valueStack.push( Boolean.FALSE ); - } - else - { - throw new WotonomyException( - "Invalid boolean format: " + value ); - } - } - else - if ( XMLRPCEncoder.BASE64.equals( localName ) ) - { - throw new WotonomyException( "Not implemented yet." ); - } - else - if ( XMLRPCEncoder.FAULT.equals( localName ) ) - { - Map faultMap = (Map) valueStack.pop(); - try - { - faultCode = ((Integer) - faultMap.get( XMLRPCEncoder.FAULTCODE )).intValue(); - faultString = (String) faultMap.get( XMLRPCEncoder.FAULTSTRING ); - } - catch ( Exception exc ) - { - throw new WotonomyException( - "Invalid fault format: " + faultMap ); - } - } - else - if ( XMLRPCEncoder.METHODNAME.equals( localName ) ) - { - methodName = (String) valueStack.pop(); - } - else - if ( XMLRPCEncoder.PARAM.equals( localName ) ) - { - //NOTE: this leaves the parameter on the stack - parameters.add( getResult() ); - } - } - - + // write values to array (reverse the order) + Object value; + LinkedList list = new LinkedList(); + while ((!valueStack.empty()) && (!(valueStack.peek() instanceof ValueMarker))) { + value = valueStack.pop(); + if (value == nilMarker) + value = null; + list.addFirst(value); + } + // push the list on the stack + valueStack.push(list); + } else if (XMLRPCEncoder.INT.equals(localName)) { + Object value = valueStack.pop(); + try { + valueStack.push(new Integer(value.toString())); + } catch (NumberFormatException exc) { + throw new WotonomyException("Invalid double format: " + value.toString()); + } + } else if (XMLRPCEncoder.I4.equals(localName)) { + Object value = valueStack.pop(); + try { + valueStack.push(new Integer(value.toString())); + } catch (NumberFormatException exc) { + throw new WotonomyException("Invalid double format: " + value.toString()); + } + } else if (XMLRPCEncoder.NIL.equals(localName)) { + valueStack.push(nilMarker); + } else if (XMLRPCEncoder.DOUBLE.equals(localName)) { + Object value = valueStack.pop(); + try { + valueStack.push(new Double(value.toString())); + } catch (NumberFormatException exc) { + throw new WotonomyException("Invalid double format: " + value.toString()); + } + } else if (XMLRPCEncoder.DATE.equals(localName)) { + Object value = valueStack.pop(); + try { + valueStack.push(XMLRPCEncoder.DATEFORMAT8601.parseObject(value.toString())); + } catch (Exception exc) { + throw new WotonomyException("Invalid date format: " + value); + } + } else if (XMLRPCEncoder.BOOLEAN.equals(localName)) { + Object value = valueStack.pop(); + if (XMLRPCEncoder.TRUE.equals(value)) { + valueStack.push(Boolean.TRUE); + } else if (XMLRPCEncoder.FALSE.equals(value)) { + valueStack.push(Boolean.FALSE); + } else { + throw new WotonomyException("Invalid boolean format: " + value); + } + } else if (XMLRPCEncoder.BASE64.equals(localName)) { + throw new WotonomyException("Not implemented yet."); + } else if (XMLRPCEncoder.FAULT.equals(localName)) { + Map faultMap = (Map) valueStack.pop(); + try { + faultCode = ((Integer) faultMap.get(XMLRPCEncoder.FAULTCODE)).intValue(); + faultString = (String) faultMap.get(XMLRPCEncoder.FAULTSTRING); + } catch (Exception exc) { + throw new WotonomyException("Invalid fault format: " + faultMap); + } + } else if (XMLRPCEncoder.METHODNAME.equals(localName)) { + methodName = (String) valueStack.pop(); + } else if (XMLRPCEncoder.PARAM.equals(localName)) { + // NOTE: this leaves the parameter on the stack + parameters.add(getResult()); + } + } public void endPrefixMapping(String prefix) throws SAXException { // TODO Auto-generated method stub @@ -423,12 +312,14 @@ class XMLRPCDecoderHelper extends DefaultHandler } public InputSource resolveEntity(String publicId, String systemId) throws SAXException { - // NOTE: Sun accepted an incompatible api difference. The (false) should be tossed by hotspot + // NOTE: Sun accepted an incompatible api difference. The (false) should be + // tossed by hotspot try { - if (false) throw new IOException("Fake exception to make it compile in both 1.4 and 1.5"); - return super.resolveEntity(publicId, systemId); + if (false) + throw new IOException("Fake exception to make it compile in both 1.4 and 1.5"); + return super.resolveEntity(publicId, systemId); } catch (IOException e) { - throw new SAXException(e.getClass().getName() + " thrown while resolving entity.",e); + throw new SAXException(e.getClass().getName() + " thrown while resolving entity.", e); } } @@ -442,14 +333,13 @@ class XMLRPCDecoderHelper extends DefaultHandler super.skippedEntity(name); } - - public void startPrefixMapping(String prefix, String uri) throws SAXException { // TODO Auto-generated method stub super.startPrefixMapping(prefix, uri); } - public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { + public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) + throws SAXException { // TODO Auto-generated method stub super.unparsedEntityDecl(name, publicId, systemId, notationName); } @@ -459,76 +349,65 @@ class XMLRPCDecoderHelper extends DefaultHandler super.warning(e); } - - // marker class - - private class ValueMarker - { - private Class theClass; - - public ValueMarker() - { - theClass = null; - } - - public void setMarkerClass( Class aClass ) - { - theClass = aClass; - } - - public Class getMarkerClass() - { - return theClass; - } - - public String toString() - { - return "[ValueMarker: " + theClass + "]"; - } - } - + // marker class + + private class ValueMarker { + private Class theClass; + + public ValueMarker() { + theClass = null; + } + + public void setMarkerClass(Class aClass) { + theClass = aClass; + } + + public Class getMarkerClass() { + return theClass; + } + + public String toString() { + return "[ValueMarker: " + theClass + "]"; + } + } + } /* - * $Log$ - * Revision 1.1 2006/02/19 01:44:03 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.1 2006/02/19 01:44:03 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.7 2003/08/06 23:07:53 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.7 2003/08/06 23:07:53 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.6 2001/03/03 15:16:35 mpowers - * Fixed bug in decoding empty strings: string handler did nothing and - * empty cdatas were ignored, so no value was placed on the stack. + * Revision 1.6 2001/03/03 15:16:35 mpowers Fixed bug in decoding empty strings: + * string handler did nothing and empty cdatas were ignored, so no value was + * placed on the stack. * - * Revision 1.5 2001/02/17 16:52:06 mpowers - * Changes in imports to support building with jdk1.1 collections. + * Revision 1.5 2001/02/17 16:52:06 mpowers Changes in imports to support + * building with jdk1.1 collections. * - * Revision 1.4 2001/02/09 15:51:39 mpowers - * Fixed a pernicious bug: I was using the character event entirely - * incorrectly, but the problem only exhibited itself with large data - * and even then only randomly. Now using a string buffer. + * Revision 1.4 2001/02/09 15:51:39 mpowers Fixed a pernicious bug: I was using + * the character event entirely incorrectly, but the problem only exhibited + * itself with large data and even then only randomly. Now using a string + * buffer. * - * Revision 1.3 2001/02/07 19:24:28 mpowers - * Moved XML classes to separate package. + * Revision 1.3 2001/02/07 19:24:28 mpowers Moved XML classes to separate + * package. * - * Revision 1.2 2001/02/06 14:34:23 mpowers - * Forgot to rename the package declarations. + * Revision 1.2 2001/02/06 14:34:23 mpowers Forgot to rename the package + * declarations. * - * Revision 1.1 2001/02/06 14:31:19 mpowers - * Moving XML utilities from util to xml package. + * Revision 1.1 2001/02/06 14:31:19 mpowers Moving XML utilities from util to + * xml package. * - * Revision 1.1.1.1 2000/12/21 15:52:39 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:39 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:48 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:48 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java index 3a63d45..d84f164 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java @@ -43,484 +43,398 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** -* An implementation of XMLEncoder that serializes objects -* into XMLRPC format, which is found at http://xmlrpc.com/spec. -* We extend that standard only in that we add a "class" -* attribute to the "value" tag, so that a java-based decoder -* can more closely reconstruct the original java data structure. -* The class attribute can be safely ignored by clients. -* This implementation is not thread-safe, so a new instances -* should be created to accomodate multiple threads. -*/ -public class XMLRPCEncoder implements XMLEncoder -{ - public static final String METHODCALL = "methodCall"; - public static final String METHODNAME = "methodName"; - public static final String METHODRESPONSE = "methodResponse"; - public static final String PARAMS = "params"; - public static final String PARAM = "param"; - public static final String FAULT = "fault"; - public static final String FAULTCODE = "faultCode"; - public static final String FAULTSTRING = "faultString"; - - public static final String VALUE = "value"; - public static final String CLASS = "class"; - - public static final String STRUCT = "struct"; - public static final String MEMBER = "member"; - public static final String NAME = "name"; - - public static final String ARRAY = "array"; - public static final String DATA = "data"; - - public static final String NIL = "nil"; - public static final String INT = "int"; - public static final String I4 = "i4"; - public static final String BOOLEAN = "boolean"; - public static final String STRING = "string"; - public static final String DOUBLE = "double"; - public static final String DATE = "dateTime.iso8601"; - public static final String BASE64 = "base64"; - - public static final String TRUE = "1"; - public static final String FALSE = "0"; - - public static final Format DATEFORMAT8601 = - new SimpleDateFormat( "yyyyMMdd'T'HHmmss" ); - - /** - * Encodes an object to the specified output stream as XML. - * @param anObject The object to be serialized to XML format. - * @param anOutputStream The output stream to which the object - * will be written. - */ - public void encode( Object anObject, OutputStream anOutputStream ) - { - try - { - - //XMLWriter writer = new UTF8XMLWriter( anOutputStream ); - RPCXMLWriter writer = new RPCXMLWriter(anOutputStream,OutputFormat.createCompactFormat()); - writeValueToXMLWriter( anObject, writer ); - writer.flush(); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - } - - /** - * Encodes a method request in XML-RPC format in a "methodCall" tag, - * and writes the XML to the specified output stream. - * This method only writes XML: the caller is responsible for - * generating the appropriate header, if any, which should set - * the content type as "text/xml" and the content length as appropriate. - * The caller is also responsible for writing the xml version tag. - * @param aMethodName The method name to appear in the "methodName" tag. - * @param aParameterArray An array of objects, each of which will be - * encoded as values enclosed in a "param" tag, all of which will be - * enclosed in a "params" tag. - * @param anOutputStream The stream to which the XML will be written. - */ - public void encodeRequest( - String aMethodName, Object[] aParameterArray, - OutputStream anOutputStream ) - { - try - { - RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat()); - writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ); - writer.startElement( METHODCALL ); - writer.startElement( METHODNAME ); - writer.write( aMethodName ); - writer.endElement( METHODNAME ); - writer.startElement( PARAMS ); - for ( int i = 0; i < aParameterArray.length; i++ ) - { - writer.startElement( PARAM ); - writeValueToXMLWriter( aParameterArray[i], writer ); - writer.endElement( PARAM ); - } - writer.endElement( PARAMS ); - writer.endElement( METHODCALL ); - writer.flush(); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - - //TODO: should this return the content-length? - } - - /** - * Encodes a method response in XML-RPC format in a "methodResponse" tag, - * and writes the XML to the specified output stream. - * This method only writes XML: the caller is responsible for - * generating the appropriate header, if any, which should set - * the content type as "text/xml" and the content length as appropriate. - * The caller is also responsible for writing the xml version tag. - * @param aResult A object which will be - * encoded as values enclosed in a "param" tag, all of which will be - * enclosed in a "params" tag. - * @param anOutputStream The stream to which the XML will be written. - */ - public void encodeResponse( - Object aResult, OutputStream anOutputStream ) - { - try - { - RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() ); - writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ); - writer.startElement( METHODRESPONSE ); - writer.startElement( PARAMS ); - writer.startElement( PARAM ); - writeValueToXMLWriter( aResult, writer ); - writer.endElement( PARAM ); - writer.endElement( PARAMS ); - writer.endElement( METHODRESPONSE ); - writer.flush(); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - - //TODO: should this return the content-length? - } - - /** - * Encodes a fault response in XML-RPC format in a "methodResponse" tag, - * and writes the XML to the specified output stream. - * This method only writes XML: the caller is responsible for first - * generating the appropriate header, if any, which should set - * the content type as "text/xml" and the content length as appropriate. - * The caller is also responsible for writing the xml version tag. - * @param aFaultCode An application-defined error code. - * @param aFaultString A human-readable error description. - * @param anOutputStream The stream to which the XML will be written. - */ - public void encodeFault( - int aFaultCode, String aFaultString, OutputStream anOutputStream ) - { - try - { - RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() ); - writer.processingInstruction( "xml", "version=\"1.0\"" ); - writer.startElement( METHODRESPONSE ); - writer.startElement( FAULT ); - writer.startElement( VALUE ); - writer.startElement( STRUCT ); - - writer.startElement( MEMBER ); - writer.startElement( NAME ); - writer.write( FAULTCODE ); - writer.endElement( NAME ); - writer.startElement( VALUE ); - writer.startElement( INT ); - writer.write( new Integer( aFaultCode ).toString() ); - writer.endElement( INT ); - writer.endElement( VALUE ); - writer.endElement( MEMBER ); - - writer.startElement( MEMBER ); - writer.startElement( NAME ); - writer.write( FAULTSTRING ); - writer.endElement( NAME ); - writer.startElement( VALUE ); - writer.startElement( STRING ); - writer.write( aFaultString ); - writer.endElement( STRING ); - writer.endElement( VALUE ); - writer.endElement( MEMBER ); - - writer.endElement( STRUCT ); - writer.endElement( VALUE ); - writer.endElement( FAULT ); - writer.endElement( METHODRESPONSE ); - writer.flush(); - } - catch ( Exception exc ) - { - throw new WotonomyException( exc ); - } - - //TODO: should this return the content-length? - } - - /** - * Performs the actual writing of the file to XML. - */ - private void writeValueToXMLWriter( - Object anObject, RPCXMLWriter writer ) - { - try - { - - - if ( anObject == null ) - { - writer.startElement( VALUE ); - // write nil for null - Element nill = new NonLazyElement(NIL); - writer.write(nill); - } - else - if ( anObject instanceof Collection ) - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - // write items in the order we get them from the iterator - - writer.startElement( ARRAY ); - writer.startElement( DATA ); - Iterator it = ((Collection)anObject).iterator(); - while ( it.hasNext() ) - { - writeValueToXMLWriter( it.next(), writer ); - } - writer.endElement( DATA ); - writer.endElement( ARRAY ); - - } - else - if ( anObject instanceof Map ) - { - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - // write items in the order we get them from the iterator - //FIXME: The method-based properties are being ignored! - - Map.Entry entry; - writer.startElement( STRUCT ); - writer.startElement( MEMBER ); - Iterator it = ((Map)anObject).entrySet().iterator(); - while ( it.hasNext() ) - { - entry = (Map.Entry) it.next(); - writer.startElement( NAME ); - writeValueToXMLWriter( entry.getKey(), writer ); - writer.endElement( NAME ); - writeValueToXMLWriter( entry.getValue(), writer ); - } - writer.endElement( MEMBER ); - writer.endElement( STRUCT ); - } - else // not a collection - { - // check for primitive types - if ( anObject instanceof String ) - { - writer.startElement( VALUE ); - - writer.startElement( STRING ); - writer.write( anObject.toString() ); - writer.endElement( STRING ); - } - else - if ( anObject instanceof StringBuffer ) - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - writer.startElement( STRING ); - writer.write( anObject.toString() ); - writer.endElement( STRING ); - } - else - if ( anObject instanceof Number ) - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - if ( ( anObject instanceof Double ) - || ( anObject instanceof Float ) ) - { - writer.startElement( DOUBLE ); - writer.write( anObject.toString() ); - writer.endElement( DOUBLE ); - } - else - { - writer.startElement( INT ); - writer.write( anObject.toString() ); - writer.endElement( INT ); - } - } - else - if ( anObject instanceof Date ) - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - writer.startElement( DATE ); - writer.write( DATEFORMAT8601.format( anObject ) ); - writer.endElement( DATE ); - } - else - if ( anObject instanceof Boolean ) - { - writer.startElement( BOOLEAN ); - if ( ((Boolean)anObject).booleanValue() ) - { - writer.write( "1" ); - } - else - { - writer.write( "0" ); - } - writer.endElement( BOOLEAN ); - } - else - if ( anObject.getClass().isArray() ) - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - writer.startElement( ARRAY ); - writer.startElement( DATA ); - - int length = Array.getLength( anObject ); - for ( int i = 0; i < length; i++ ) - { - writeValueToXMLWriter( Array.get( anObject, i ), writer ); - } - - writer.endElement( DATA ); - writer.endElement( ARRAY ); - } - else // not primitive or collection, treat as struct - { - // write class so we can restore if possible - AttributesImpl a = new AttributesImpl(); - a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() ); - - writer.startElement( VALUE, a ); - - List readProperties = new ArrayList(); - String[] read = Introspector.getReadPropertiesForObject( anObject ); - for ( int i = 0; i < read.length; i++ ) - { - readProperties.add( read[i] ); - } - - List properties = new ArrayList(); - String[] write = Introspector.getWritePropertiesForObject( anObject ); - for ( int i = 0; i < write.length; i++ ) - { - properties.add( write[i] ); - } - - // only use readable properties - properties.retainAll( readProperties ); - + * An implementation of XMLEncoder that serializes objects into XMLRPC format, + * which is found at http://xmlrpc.com/spec. We extend that standard only in + * that we add a "class" attribute to the "value" tag, so that a java-based + * decoder can more closely reconstruct the original java data structure. The + * class attribute can be safely ignored by clients. This implementation is not + * thread-safe, so a new instances should be created to accomodate multiple + * threads. + */ +public class XMLRPCEncoder implements XMLEncoder { + public static final String METHODCALL = "methodCall"; + public static final String METHODNAME = "methodName"; + public static final String METHODRESPONSE = "methodResponse"; + public static final String PARAMS = "params"; + public static final String PARAM = "param"; + public static final String FAULT = "fault"; + public static final String FAULTCODE = "faultCode"; + public static final String FAULTSTRING = "faultString"; + + public static final String VALUE = "value"; + public static final String CLASS = "class"; + + public static final String STRUCT = "struct"; + public static final String MEMBER = "member"; + public static final String NAME = "name"; + + public static final String ARRAY = "array"; + public static final String DATA = "data"; + + public static final String NIL = "nil"; + public static final String INT = "int"; + public static final String I4 = "i4"; + public static final String BOOLEAN = "boolean"; + public static final String STRING = "string"; + public static final String DOUBLE = "double"; + public static final String DATE = "dateTime.iso8601"; + public static final String BASE64 = "base64"; + + public static final String TRUE = "1"; + public static final String FALSE = "0"; + + public static final Format DATEFORMAT8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + + /** + * Encodes an object to the specified output stream as XML. + * + * @param anObject The object to be serialized to XML format. + * @param anOutputStream The output stream to which the object will be written. + */ + public void encode(Object anObject, OutputStream anOutputStream) { + try { + + // XMLWriter writer = new UTF8XMLWriter( anOutputStream ); + RPCXMLWriter writer = new RPCXMLWriter(anOutputStream, OutputFormat.createCompactFormat()); + writeValueToXMLWriter(anObject, writer); + writer.flush(); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + } + + /** + * Encodes a method request in XML-RPC format in a "methodCall" tag, and writes + * the XML to the specified output stream. This method only writes XML: the + * caller is responsible for generating the appropriate header, if any, which + * should set the content type as "text/xml" and the content length as + * appropriate. The caller is also responsible for writing the xml version tag. + * + * @param aMethodName The method name to appear in the "methodName" tag. + * @param aParameterArray An array of objects, each of which will be encoded as + * values enclosed in a "param" tag, all of which will be + * enclosed in a "params" tag. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeRequest(String aMethodName, Object[] aParameterArray, OutputStream anOutputStream) { + try { + RPCXMLWriter writer = new RPCXMLWriter(anOutputStream, OutputFormat.createCompactFormat()); + writer.processingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); + writer.startElement(METHODCALL); + writer.startElement(METHODNAME); + writer.write(aMethodName); + writer.endElement(METHODNAME); + writer.startElement(PARAMS); + for (int i = 0; i < aParameterArray.length; i++) { + writer.startElement(PARAM); + writeValueToXMLWriter(aParameterArray[i], writer); + writer.endElement(PARAM); + } + writer.endElement(PARAMS); + writer.endElement(METHODCALL); + writer.flush(); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + + // TODO: should this return the content-length? + } + + /** + * Encodes a method response in XML-RPC format in a "methodResponse" tag, and + * writes the XML to the specified output stream. This method only writes XML: + * the caller is responsible for generating the appropriate header, if any, + * which should set the content type as "text/xml" and the content length as + * appropriate. The caller is also responsible for writing the xml version tag. + * + * @param aResult A object which will be encoded as values enclosed in a + * "param" tag, all of which will be enclosed in a + * "params" tag. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeResponse(Object aResult, OutputStream anOutputStream) { + try { + RPCXMLWriter writer = new RPCXMLWriter(anOutputStream, OutputFormat.createCompactFormat()); + writer.processingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); + writer.startElement(METHODRESPONSE); + writer.startElement(PARAMS); + writer.startElement(PARAM); + writeValueToXMLWriter(aResult, writer); + writer.endElement(PARAM); + writer.endElement(PARAMS); + writer.endElement(METHODRESPONSE); + writer.flush(); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + + // TODO: should this return the content-length? + } + + /** + * Encodes a fault response in XML-RPC format in a "methodResponse" tag, and + * writes the XML to the specified output stream. This method only writes XML: + * the caller is responsible for first generating the appropriate header, if + * any, which should set the content type as "text/xml" and the content length + * as appropriate. The caller is also responsible for writing the xml version + * tag. + * + * @param aFaultCode An application-defined error code. + * @param aFaultString A human-readable error description. + * @param anOutputStream The stream to which the XML will be written. + */ + public void encodeFault(int aFaultCode, String aFaultString, OutputStream anOutputStream) { + try { + RPCXMLWriter writer = new RPCXMLWriter(anOutputStream, OutputFormat.createCompactFormat()); + writer.processingInstruction("xml", "version=\"1.0\""); + writer.startElement(METHODRESPONSE); + writer.startElement(FAULT); + writer.startElement(VALUE); + writer.startElement(STRUCT); + + writer.startElement(MEMBER); + writer.startElement(NAME); + writer.write(FAULTCODE); + writer.endElement(NAME); + writer.startElement(VALUE); + writer.startElement(INT); + writer.write(new Integer(aFaultCode).toString()); + writer.endElement(INT); + writer.endElement(VALUE); + writer.endElement(MEMBER); + + writer.startElement(MEMBER); + writer.startElement(NAME); + writer.write(FAULTSTRING); + writer.endElement(NAME); + writer.startElement(VALUE); + writer.startElement(STRING); + writer.write(aFaultString); + writer.endElement(STRING); + writer.endElement(VALUE); + writer.endElement(MEMBER); + + writer.endElement(STRUCT); + writer.endElement(VALUE); + writer.endElement(FAULT); + writer.endElement(METHODRESPONSE); + writer.flush(); + } catch (Exception exc) { + throw new WotonomyException(exc); + } + + // TODO: should this return the content-length? + } + + /** + * Performs the actual writing of the file to XML. + */ + private void writeValueToXMLWriter(Object anObject, RPCXMLWriter writer) { + try { + + if (anObject == null) { + writer.startElement(VALUE); + // write nil for null + Element nill = new NonLazyElement(NIL); + writer.write(nill); + } else if (anObject instanceof Collection) { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + // write items in the order we get them from the iterator + + writer.startElement(ARRAY); + writer.startElement(DATA); + Iterator it = ((Collection) anObject).iterator(); + while (it.hasNext()) { + writeValueToXMLWriter(it.next(), writer); + } + writer.endElement(DATA); + writer.endElement(ARRAY); + + } else if (anObject instanceof Map) { + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + // write items in the order we get them from the iterator + // FIXME: The method-based properties are being ignored! + + Map.Entry entry; + writer.startElement(STRUCT); + writer.startElement(MEMBER); + Iterator it = ((Map) anObject).entrySet().iterator(); + while (it.hasNext()) { + entry = (Map.Entry) it.next(); + writer.startElement(NAME); + writeValueToXMLWriter(entry.getKey(), writer); + writer.endElement(NAME); + writeValueToXMLWriter(entry.getValue(), writer); + } + writer.endElement(MEMBER); + writer.endElement(STRUCT); + } else // not a collection + { + // check for primitive types + if (anObject instanceof String) { + writer.startElement(VALUE); + + writer.startElement(STRING); + writer.write(anObject.toString()); + writer.endElement(STRING); + } else if (anObject instanceof StringBuffer) { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + writer.startElement(STRING); + writer.write(anObject.toString()); + writer.endElement(STRING); + } else if (anObject instanceof Number) { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + if ((anObject instanceof Double) || (anObject instanceof Float)) { + writer.startElement(DOUBLE); + writer.write(anObject.toString()); + writer.endElement(DOUBLE); + } else { + writer.startElement(INT); + writer.write(anObject.toString()); + writer.endElement(INT); + } + } else if (anObject instanceof Date) { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + writer.startElement(DATE); + writer.write(DATEFORMAT8601.format(anObject)); + writer.endElement(DATE); + } else if (anObject instanceof Boolean) { + writer.startElement(BOOLEAN); + if (((Boolean) anObject).booleanValue()) { + writer.write("1"); + } else { + writer.write("0"); + } + writer.endElement(BOOLEAN); + } else if (anObject.getClass().isArray()) { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + writer.startElement(ARRAY); + writer.startElement(DATA); + + int length = Array.getLength(anObject); + for (int i = 0; i < length; i++) { + writeValueToXMLWriter(Array.get(anObject, i), writer); + } + + writer.endElement(DATA); + writer.endElement(ARRAY); + } else // not primitive or collection, treat as struct + { + // write class so we can restore if possible + AttributesImpl a = new AttributesImpl(); + a.addAttribute(null, CLASS, null, null, anObject.getClass().getName()); + + writer.startElement(VALUE, a); + + List readProperties = new ArrayList(); + String[] read = Introspector.getReadPropertiesForObject(anObject); + for (int i = 0; i < read.length; i++) { + readProperties.add(read[i]); + } + + List properties = new ArrayList(); + String[] write = Introspector.getWritePropertiesForObject(anObject); + for (int i = 0; i < write.length; i++) { + properties.add(write[i]); + } + + // only use readable properties + properties.retainAll(readProperties); + // if ( properties.size() > 0 ) // { - String key; - Object value; - Iterator it = properties.iterator(); - writer.startElement( STRUCT ); - while ( it.hasNext() ) - { - key = (String) it.next(); - value = Introspector.get( anObject, key ); - - writer.startElement( MEMBER ); - writer.startElement( NAME ); - writer.write( key ); - writer.endElement( NAME ); - writeValueToXMLWriter( value, writer ); - writer.endElement( MEMBER ); - } - writer.endElement( STRUCT ); -/* - } - else // no properties - write a converted string - { - writer.startElement( STRING ); - Object converted = - ValueConverter.convertObjectToClass( anObject, String.class ); - if ( converted != null ) - { - writer.write( converted.toString() ); - } - else - { - writer.write( anObject.toString() ); - } - writer.endElement( STRING ); - } -*/ - } - } - - writer.endElement( VALUE ); - } - catch ( Exception exc ) - { - System.err.println( "XMLFileSoup.writeValueToXMLWriter: " + exc ); - exc.printStackTrace(); - } - - } -/* - public static void main( String[] argv ) - { - System.out.println( "" ); - XMLRPCEncoder encoder = new XMLRPCEncoder(); - encoder.encodeRequest( "systemObject.test", new Object[] { - new net.wotonomy.test.TestObject(), - new net.wotonomy.test.TestObject(), - new net.wotonomy.test.TestObject() }, System.out ); - System.out.println(); - System.out.println(); - encoder.encodeResponse( new net.wotonomy.test.TestObject(), System.out ); - System.out.println(); - System.out.println(); - encoder.encodeFault( -1, "This is a fault.", System.out ); - System.out.println(); - System.out.println(); - System.out.println( "" ); - } -*/ - - private class RPCXMLWriter extends XMLWriter { + String key; + Object value; + Iterator it = properties.iterator(); + writer.startElement(STRUCT); + while (it.hasNext()) { + key = (String) it.next(); + value = Introspector.get(anObject, key); + + writer.startElement(MEMBER); + writer.startElement(NAME); + writer.write(key); + writer.endElement(NAME); + writeValueToXMLWriter(value, writer); + writer.endElement(MEMBER); + } + writer.endElement(STRUCT); + /* + * } else // no properties - write a converted string { writer.startElement( + * STRING ); Object converted = ValueConverter.convertObjectToClass( anObject, + * String.class ); if ( converted != null ) { writer.write( converted.toString() + * ); } else { writer.write( anObject.toString() ); } writer.endElement( STRING + * ); } + */ + } + } + + writer.endElement(VALUE); + } catch (Exception exc) { + System.err.println("XMLFileSoup.writeValueToXMLWriter: " + exc); + exc.printStackTrace(); + } + + } + /* + * public static void main( String[] argv ) { System.out.println( "" ); + * XMLRPCEncoder encoder = new XMLRPCEncoder(); encoder.encodeRequest( + * "systemObject.test", new Object[] { new net.wotonomy.test.TestObject(), new + * net.wotonomy.test.TestObject(), new net.wotonomy.test.TestObject() }, + * System.out ); System.out.println(); System.out.println(); + * encoder.encodeResponse( new net.wotonomy.test.TestObject(), System.out ); + * System.out.println(); System.out.println(); encoder.encodeFault( -1, + * "This is a fault.", System.out ); System.out.println(); System.out.println(); + * System.out.println( "" ); } + */ + + private class RPCXMLWriter extends XMLWriter { public RPCXMLWriter(OutputStream arg0, OutputFormat arg1) throws UnsupportedEncodingException { super(arg0, arg1); } - public void endElement(String localname) throws SAXException { + public void endElement(String localname) throws SAXException { super.endElement(null, localname, null); } public void startElement(String localname) throws SAXException { this.startElement(localname, null); } + public void startElement(String localname, Attributes attributes) throws SAXException { this.startElement(null, localname, null, attributes); } - } - + } + } diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java index 48c9e41..5848cd0 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java @@ -19,54 +19,51 @@ License along with this library; if not, see http://www.gnu.org package net.wotonomy.web.xml; /** -* A call-back interface that receives an XML-RPC transaction message. -* Used by XMLRPCDecoder to return values from a message. -*/ -public interface XMLRPCReceiver -{ - /** - * Receives an XML-RPC request. - * @param aMethodName The method name of the request. - * @param aParameterArray The objects contained in the request, in order. - */ - void request( String aMethodName, Object[] aParameterArray ); - - /** - * Receives an XML-RPC response. - * @param aResult The object contained in the response. - */ - void response( Object aResult ); - - /** - * Receives an XML-RPC fault response. - * @param aFaultCode The fault code contained in the response. - * @param aFaultString The fault string contained in the response. - */ - void fault( int aFaultCode, String aFaultString ); + * A call-back interface that receives an XML-RPC transaction message. Used by + * XMLRPCDecoder to return values from a message. + */ +public interface XMLRPCReceiver { + /** + * Receives an XML-RPC request. + * + * @param aMethodName The method name of the request. + * @param aParameterArray The objects contained in the request, in order. + */ + void request(String aMethodName, Object[] aParameterArray); + + /** + * Receives an XML-RPC response. + * + * @param aResult The object contained in the response. + */ + void response(Object aResult); + + /** + * Receives an XML-RPC fault response. + * + * @param aFaultCode The fault code contained in the response. + * @param aFaultString The fault string contained in the response. + */ + void fault(int aFaultCode, String aFaultString); } /* - * $Log$ - * Revision 1.1 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.1 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.2 2001/02/06 14:34:23 mpowers - * Forgot to rename the package declarations. + * Revision 1.2 2001/02/06 14:34:23 mpowers Forgot to rename the package + * declarations. * - * Revision 1.1 2001/02/06 14:31:19 mpowers - * Moving XML utilities from util to xml package. + * Revision 1.1 2001/02/06 14:31:19 mpowers Moving XML utilities from util to + * xml package. * - * Revision 1.1.1.1 2000/12/21 15:52:44 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:52:44 mpowers Contributing wotonomy. * - * Revision 1.2 2000/12/20 16:25:49 michael - * Added log to all files. + * Revision 1.2 2000/12/20 16:25:49 michael Added log to all files. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java index 7c1f104..599481f 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java @@ -32,207 +32,155 @@ import net.wotonomy.foundation.NSSelector; import net.wotonomy.foundation.internal.WotonomyException; /** -* An NSSelector customized to invoke methods with XMLRPC -* when a URL is passed in as the object to the invoke() method. -* The method name and parameters will be marshalled and sent -* as an XMLRPC request to the host specified by the URL.

            -* -* To use this class simply as an XMLRPC client, just call -* invoke() with a URL referencing the XMLRPC server and an -* optional array of parameters. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 905 $ -*/ -public class XMLRPCSelector extends NSSelector -{ - private boolean copyStream = false; - - /** - * Constructor specifying a method name. - */ - public XMLRPCSelector (String aMethodName) - { - super( aMethodName, EMPTY_CLASS_ARRAY ); - } - - /** - * Constructor specifying a method name and an array of parameter types. - * When accessing XMLRPC servers, invoke() does require that the - * specified objects match the types in the parameter type array. - */ - public XMLRPCSelector (String aMethodName, Class[] aParameterTypeArray) - { - super( aMethodName, aParameterTypeArray ); - } - - /** - * Invokes this selector's method on the specified object - * using the specified parameters. - */ - public Object invoke (Object anObject, Object[] parameters) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - if ( anObject instanceof URL ) - { - Receiver receiver = new Receiver(); - byte[] copyOfResponse = null; - try - { - URLConnection cn = ((URL)anObject).openConnection(); - - // set properties - cn.setDoOutput(true); - cn.setDoInput(true); - cn.setRequestProperty( - "content-type","text/xml"); - - // send parameters - OutputStream out = cn.getOutputStream(); - new XMLRPCEncoder().encodeRequest( - name(), parameters, out ); - out.flush(); - out.close(); - - // get response: getInputStream initiates the request - InputStream input = - new BufferedInputStream( cn.getInputStream() ); - if ( copyStream ) - { - ByteArrayOutputStream byteArray = - new ByteArrayOutputStream(); - int b; - while ( ( b = input.read() ) != -1 ) - { - byteArray.write( b ); - } - copyOfResponse = byteArray.toByteArray(); - input = new ByteArrayInputStream( copyOfResponse ); - } - new XMLRPCDecoder().decode( input, receiver ); - } - catch ( FileNotFoundException exc ) - { - throw new WotonomyException( "Server did not return a response." ); - } - catch ( Exception exc ) - { - if ( copyOfResponse != null ) - { - System.out.println( new String( copyOfResponse ) ); - exc.printStackTrace(); - } - throw new InvocationTargetException( exc ); - } - - if ( receiver.faultString == null ) - { - return receiver.result; - } - else - { - throw new InvocationTargetException( - new WotonomyException( - receiver.faultCode + ": " + receiver.faultString ) ); - } - } - - // else: not a URL - return super.invoke( anObject, parameters ); + * An NSSelector customized to invoke methods with XMLRPC when a URL is passed + * in as the object to the invoke() method. The method name and parameters will + * be marshalled and sent as an XMLRPC request to the host specified by the URL. + *
            + *
            + * + * To use this class simply as an XMLRPC client, just call invoke() with a URL + * referencing the XMLRPC server and an optional array of parameters. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ +public class XMLRPCSelector extends NSSelector { + private boolean copyStream = false; + + /** + * Constructor specifying a method name. + */ + public XMLRPCSelector(String aMethodName) { + super(aMethodName, EMPTY_CLASS_ARRAY); + } + + /** + * Constructor specifying a method name and an array of parameter types. When + * accessing XMLRPC servers, invoke() does require that the specified objects + * match the types in the parameter type array. + */ + public XMLRPCSelector(String aMethodName, Class[] aParameterTypeArray) { + super(aMethodName, aParameterTypeArray); } - public static Object invoke - (String methodName, Class[] parameterTypes, Object anObject, Object[] parameters) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return new XMLRPCSelector( methodName, parameterTypes ).invoke( anObject, parameters ); + /** + * Invokes this selector's method on the specified object using the specified + * parameters. + */ + public Object invoke(Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + if (anObject instanceof URL) { + Receiver receiver = new Receiver(); + byte[] copyOfResponse = null; + try { + URLConnection cn = ((URL) anObject).openConnection(); + + // set properties + cn.setDoOutput(true); + cn.setDoInput(true); + cn.setRequestProperty("content-type", "text/xml"); + + // send parameters + OutputStream out = cn.getOutputStream(); + new XMLRPCEncoder().encodeRequest(name(), parameters, out); + out.flush(); + out.close(); + + // get response: getInputStream initiates the request + InputStream input = new BufferedInputStream(cn.getInputStream()); + if (copyStream) { + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + int b; + while ((b = input.read()) != -1) { + byteArray.write(b); + } + copyOfResponse = byteArray.toByteArray(); + input = new ByteArrayInputStream(copyOfResponse); + } + new XMLRPCDecoder().decode(input, receiver); + } catch (FileNotFoundException exc) { + throw new WotonomyException("Server did not return a response."); + } catch (Exception exc) { + if (copyOfResponse != null) { + System.out.println(new String(copyOfResponse)); + exc.printStackTrace(); + } + throw new InvocationTargetException(exc); + } + + if (receiver.faultString == null) { + return receiver.result; + } else { + throw new InvocationTargetException( + new WotonomyException(receiver.faultCode + ": " + receiver.faultString)); + } + } + + // else: not a URL + return super.invoke(anObject, parameters); } - public static Object invoke - (String methodName, Object anObject) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return XMLRPCSelector.invoke( - methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY ); + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return new XMLRPCSelector(methodName, parameterTypes).invoke(anObject, parameters); } - public static Object invoke - (String methodName, Class[] parameterTypes, - Object anObject, Object aParameter) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return XMLRPCSelector.invoke( - methodName, parameterTypes, anObject, new Object[] { aParameter } ); + public static Object invoke(String methodName, Object anObject) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return XMLRPCSelector.invoke(methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY); } - public static Object invoke - (String methodName, Class[] parameterTypes, - Object anObject, Object p1, Object p2) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return XMLRPCSelector.invoke( - methodName, parameterTypes, anObject, new Object[] { p1, p2 } ); + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object aParameter) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return XMLRPCSelector.invoke(methodName, parameterTypes, anObject, new Object[] { aParameter }); + } + + public static Object invoke(String methodName, Class[] parameterTypes, Object anObject, Object p1, Object p2) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return XMLRPCSelector.invoke(methodName, parameterTypes, anObject, new Object[] { p1, p2 }); + } + + private class Receiver implements XMLRPCReceiver { + public Object result; + public int faultCode; + public String faultString; + + public Receiver() { + result = null; + faultCode = -1; + faultString = null; + } + + public void request(String aMethodName, Object[] aParameterArray) { + throw new WotonomyException("Invalid response: Expected response but received request."); + } + + public void response(Object aResult) { + result = aResult; + faultCode = -1; + faultString = null; + } + + public void fault(int aFaultCode, String aFaultString) { + result = null; + faultCode = aFaultCode; + faultString = aFaultString; + } } - - private class Receiver implements XMLRPCReceiver - { - public Object result; - public int faultCode; - public String faultString; - - public Receiver() - { - result = null; - faultCode = -1; - faultString = null; - } - - public void request( - String aMethodName, Object[] aParameterArray ) - { - throw new WotonomyException( - "Invalid response: Expected response but received request." ); - } - - public void response( - Object aResult ) - { - result = aResult; - faultCode = -1; - faultString = null; - } - - public void fault( - int aFaultCode, String aFaultString) - { - result = null; - faultCode = aFaultCode; - faultString = aFaultString; - } - } - } /* - * $Log$ - * Revision 1.1 2006/02/19 01:44:03 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.1 2006/02/19 01:44:03 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2001/02/07 19:24:28 mpowers - * Moved XML classes to separate package. + * Revision 1.1 2001/02/07 19:24:28 mpowers Moved XML classes to separate + * package. * * */ - diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java index a9981a4..94b5680 100644 --- a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java @@ -34,250 +34,195 @@ import net.wotonomy.foundation.NSSelector; import net.wotonomy.foundation.internal.WotonomyException; /** -* A servlet that can make any java object into an XML-RPC server. -* Simply pass in the object to the constructor and XML-RPC requests -* to this servlet will call the appropriate methods and convert -* the results to an XML-RPC response.

            -* -* Depending on your servlet container, it may be necessary to -* create a simple subclass that creates your handler object -* in its constructor and then calls setHandler().

            -* -* Responses are in the specification's standard response format.

            -* -* Faults are returned if any exception is thrown in the method, -* or if the specified method is not found on the object. -* The fault string is the toString value of the exception, -* and the fault code is the hasCode value of the exception.

            -* -* Remember that this servlet only responds to POSTs, -* per the XML-RPC spec. -*/ -public class XMLRPCServlet extends HttpServlet -{ - protected Object handler; - protected boolean synchronizing; - private Hashtable selectorCache = new Hashtable(); // thread safe - private boolean copyStream = false; - - /** - * Default constructor initializes internal state. - */ - public XMLRPCServlet() - { - handler = null; - synchronizing = false; - } - - /** - * Constructor takes any java object and allows its methods - * to be invoked via XMLRPC requests to this servlet. - * Simply calls setHandler(). - */ - public XMLRPCServlet( Object aHandler ) - { - this(); - setHandler( aHandler ); - } - - /** - * Gets the object whose methods will be invoked to - * handle incoming requests. - */ - public Object getHandler() - { - return handler; - } - - /** - * Sets the object whose methods will be invoked to - * handle incoming requests. - */ - public void setHandler( Object aHandler ) - { - handler = aHandler; - } - - /** - * Gets whether the servlet should synchonize on the - * object before invoking methods on it. - * Defaults to false. - */ - public boolean isSynchronizing() - { - return synchronizing; - } - - /** - * Sets whether the servlet should synchonize on the - * object before invoking methods on it. - * Defaults to false. - */ - public void setSynchronizing( boolean willSynchronize ) - { - synchronizing = willSynchronize; - } - - /** - * Overridden to service the request. - */ - protected void doPost( - HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException - { - if ( getHandler() != null ) - { - InputStream input = req.getInputStream(); - byte[] copyOfRequest = null; + * A servlet that can make any java object into an XML-RPC server. Simply pass + * in the object to the constructor and XML-RPC requests to this servlet will + * call the appropriate methods and convert the results to an XML-RPC response. + *
            + *
            + * + * Depending on your servlet container, it may be necessary to create a simple + * subclass that creates your handler object in its constructor and then calls + * setHandler().
            + *
            + * + * Responses are in the specification's standard response format.
            + *
            + * + * Faults are returned if any exception is thrown in the method, or if the + * specified method is not found on the object. The fault string is the toString + * value of the exception, and the fault code is the hasCode value of the + * exception.
            + *
            + * + * Remember that this servlet only responds to POSTs, per the XML-RPC spec. + */ +public class XMLRPCServlet extends HttpServlet { + protected Object handler; + protected boolean synchronizing; + private Hashtable selectorCache = new Hashtable(); // thread safe + private boolean copyStream = false; + + /** + * Default constructor initializes internal state. + */ + public XMLRPCServlet() { + handler = null; + synchronizing = false; + } + + /** + * Constructor takes any java object and allows its methods to be invoked via + * XMLRPC requests to this servlet. Simply calls setHandler(). + */ + public XMLRPCServlet(Object aHandler) { + this(); + setHandler(aHandler); + } + + /** + * Gets the object whose methods will be invoked to handle incoming requests. + */ + public Object getHandler() { + return handler; + } + + /** + * Sets the object whose methods will be invoked to handle incoming requests. + */ + public void setHandler(Object aHandler) { + handler = aHandler; + } + + /** + * Gets whether the servlet should synchonize on the object before invoking + * methods on it. Defaults to false. + */ + public boolean isSynchronizing() { + return synchronizing; + } + + /** + * Sets whether the servlet should synchonize on the object before invoking + * methods on it. Defaults to false. + */ + public void setSynchronizing(boolean willSynchronize) { + synchronizing = willSynchronize; + } + + /** + * Overridden to service the request. + */ + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (getHandler() != null) { + InputStream input = req.getInputStream(); + byte[] copyOfRequest = null; - if ( copyStream ) - { - ByteArrayOutputStream byteArray = - new ByteArrayOutputStream(); - int b; - while ( ( b = input.read() ) != -1 ) - { - byteArray.write( b ); - } - copyOfRequest = byteArray.toByteArray(); - input = new ByteArrayInputStream( copyOfRequest ); - } - - try - { - new XMLRPCDecoder().decode( input, - new Receiver( this, resp ) ); - } - catch ( WotonomyException exc ) - { - if ( copyOfRequest != null ) - { - System.out.println( new String( copyOfRequest ) ); - exc.printStackTrace(); - } - // catches io exceptions thrown in handleRequest. - Throwable t = exc.getWrappedThrowable(); - if ( t instanceof IOException ) - { - throw (IOException)t; - } - throw exc; - } - } - } - - /** - * Called by doPost after parsing an incoming request, - * and is responsible for invoking the specified method - * with the specified parameters on the handler object. - * (This implementation calls getOutputStream() on the response.) - * Override to customize the handling of the request. - */ - protected void handleRequest( String aMethodName, - Object[] aParameterArray, HttpServletResponse aResponse ) - { - OutputStream output = null; - - try - { - output = aResponse.getOutputStream(); - aResponse.setStatus( HttpServletResponse.SC_OK ); // always 200 - aResponse.setContentType( "text/xml" ); - } - catch ( IOException exc ) - { - // caught in doPost - throw new WotonomyException( exc ); - } - - // get the array of types - XMLRPCEncoder encoder = new XMLRPCEncoder(); - Class[] types = new Class[ aParameterArray.length ]; - for ( int i = 0; i < aParameterArray.length; i++ ) - { - types[i] = aParameterArray[i].getClass(); - } - - //TODO: selectors should be cached if possible - - Object handler = getHandler(); - if ( isSynchronizing() ) - { - synchronized ( handler ) - { - execute( encoder, handler, output, - new NSSelector( aMethodName, types ), aParameterArray ); - } - } - else - { - execute( encoder, handler, output, - new NSSelector( aMethodName, types ), aParameterArray ); - } - } - - private void execute( XMLRPCEncoder anEncoder, Object aHandler, - OutputStream output, NSSelector aSelector, Object[] aParameterArray ) - { - try - { - Object result = - aSelector.invoke( aHandler, aParameterArray ); - anEncoder.encodeResponse( result, output ); - } - catch ( Exception exc ) - { - anEncoder.encodeFault( - exc.hashCode(), exc.toString(), output ); - } - } - - private class Receiver implements XMLRPCReceiver - { - XMLRPCServlet controller; - HttpServletResponse response; - - public Receiver( - XMLRPCServlet aController, - HttpServletResponse aResponse ) - { - controller = aController; - response = aResponse; - } - - public void request( - String aMethodName, Object[] aParameterArray ) - { - controller.handleRequest( - aMethodName, aParameterArray, response ); - } - - public void response( - Object aResult ) - { - // does nothing - } - - public void fault( - int aFaultCode, String aFaultString) - { - // does nothing - } - } + if (copyStream) { + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + int b; + while ((b = input.read()) != -1) { + byteArray.write(b); + } + copyOfRequest = byteArray.toByteArray(); + input = new ByteArrayInputStream(copyOfRequest); + } + + try { + new XMLRPCDecoder().decode(input, new Receiver(this, resp)); + } catch (WotonomyException exc) { + if (copyOfRequest != null) { + System.out.println(new String(copyOfRequest)); + exc.printStackTrace(); + } + // catches io exceptions thrown in handleRequest. + Throwable t = exc.getWrappedThrowable(); + if (t instanceof IOException) { + throw (IOException) t; + } + throw exc; + } + } + } + + /** + * Called by doPost after parsing an incoming request, and is responsible for + * invoking the specified method with the specified parameters on the handler + * object. (This implementation calls getOutputStream() on the response.) + * Override to customize the handling of the request. + */ + protected void handleRequest(String aMethodName, Object[] aParameterArray, HttpServletResponse aResponse) { + OutputStream output = null; + + try { + output = aResponse.getOutputStream(); + aResponse.setStatus(HttpServletResponse.SC_OK); // always 200 + aResponse.setContentType("text/xml"); + } catch (IOException exc) { + // caught in doPost + throw new WotonomyException(exc); + } + + // get the array of types + XMLRPCEncoder encoder = new XMLRPCEncoder(); + Class[] types = new Class[aParameterArray.length]; + for (int i = 0; i < aParameterArray.length; i++) { + types[i] = aParameterArray[i].getClass(); + } + + // TODO: selectors should be cached if possible + + Object handler = getHandler(); + if (isSynchronizing()) { + synchronized (handler) { + execute(encoder, handler, output, new NSSelector(aMethodName, types), aParameterArray); + } + } else { + execute(encoder, handler, output, new NSSelector(aMethodName, types), aParameterArray); + } + } + + private void execute(XMLRPCEncoder anEncoder, Object aHandler, OutputStream output, NSSelector aSelector, + Object[] aParameterArray) { + try { + Object result = aSelector.invoke(aHandler, aParameterArray); + anEncoder.encodeResponse(result, output); + } catch (Exception exc) { + anEncoder.encodeFault(exc.hashCode(), exc.toString(), output); + } + } + + private class Receiver implements XMLRPCReceiver { + XMLRPCServlet controller; + HttpServletResponse response; + + public Receiver(XMLRPCServlet aController, HttpServletResponse aResponse) { + controller = aController; + response = aResponse; + } + + public void request(String aMethodName, Object[] aParameterArray) { + controller.handleRequest(aMethodName, aParameterArray, response); + } + + public void response(Object aResult) { + // does nothing + } + + public void fault(int aFaultCode, String aFaultString) { + // does nothing + } + } } /* - * $Log$ - * Revision 1.1 2006/02/19 01:44:02 cgruber - * Add xmlrpc files - * Remove jclark and replace with dom4j and javax.xml.sax stuff - * Re-work dependencies and imports so it all compiles. + * $Log$ Revision 1.1 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark + * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and + * imports so it all compiles. * - * Revision 1.1 2006/02/16 13:22:22 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.1 2001/02/07 19:24:28 mpowers - * Moved XML classes to separate package. + * Revision 1.1 2001/02/07 19:24:28 mpowers Moved XML classes to separate + * package. * */ - diff --git a/projects/net.wotonomy.web/src/test/java/net/wotonomy/web/xml/XMLRPCSelectorTest.java b/projects/net.wotonomy.web/src/test/java/net/wotonomy/web/xml/XMLRPCSelectorTest.java index 989a7ff..726fcd0 100644 --- a/projects/net.wotonomy.web/src/test/java/net/wotonomy/web/xml/XMLRPCSelectorTest.java +++ b/projects/net.wotonomy.web/src/test/java/net/wotonomy/web/xml/XMLRPCSelectorTest.java @@ -1,6 +1,5 @@ package net.wotonomy.web.xml; - import java.io.Serializable; import java.net.URL; import java.util.Date; @@ -21,49 +20,41 @@ public class XMLRPCSelectorTest extends TestCase { super.tearDown(); } - public static void testFoo( ) { - try - { - // create url for server - URL url = new URL( "http://localhost:8080/xmlrpctest" ); + public static void testFoo() { + try { + // create url for server + URL url = new URL("http://localhost:8080/xmlrpctest"); - // set up selectors - XMLRPCSelector getFullName = - new XMLRPCSelector( "getFullName" ); - XMLRPCSelector getCreateDate = - new XMLRPCSelector( "getCreateDate" ); - XMLRPCSelector setCreateDate = - new XMLRPCSelector( "setCreateDate" ); - XMLRPCSelector getChildList = - new XMLRPCSelector( "getChildList" ); - XMLRPCSelector setChildList = - new XMLRPCSelector( "setChildList" ); + // set up selectors + XMLRPCSelector getFullName = new XMLRPCSelector("getFullName"); + XMLRPCSelector getCreateDate = new XMLRPCSelector("getCreateDate"); + XMLRPCSelector setCreateDate = new XMLRPCSelector("setCreateDate"); + XMLRPCSelector getChildList = new XMLRPCSelector("getChildList"); + XMLRPCSelector setChildList = new XMLRPCSelector("setChildList"); - // fetch the full name - System.out.println( getFullName.invoke( url ) ); + // fetch the full name + System.out.println(getFullName.invoke(url)); - // fetch the create date - System.out.println( getCreateDate.invoke( url ) ); - // set date to current time - setCreateDate.invoke( url, new java.util.Date() ); - // re-fetch the create date - System.out.println( getCreateDate.invoke( url ) ); + // fetch the create date + System.out.println(getCreateDate.invoke(url)); + // set date to current time + setCreateDate.invoke(url, new java.util.Date()); + // re-fetch the create date + System.out.println(getCreateDate.invoke(url)); - // fetch the child list - java.util.List childList = (java.util.List) getChildList.invoke( url ); - System.out.println( childList ); - // add a new child - childList.add( new MockSerializableObject(new Long(5),"John","Doe", new Date()) ); - setChildList.invoke( url, childList ); - // re-fetch the child list - System.out.println( getChildList.invoke( url ) ); - } - catch (Exception exc) - { - //exc.printStackTrace(); + // fetch the child list + java.util.List childList = (java.util.List) getChildList.invoke(url); + System.out.println(childList); + // add a new child + childList.add(new MockSerializableObject(new Long(5), "John", "Doe", new Date())); + setChildList.invoke(url, childList); + // re-fetch the child list + System.out.println(getChildList.invoke(url)); + } catch (Exception exc) { + // exc.printStackTrace(); } } - + public static class MockSerializableObject implements Serializable { public String firstname; public String lastname; @@ -77,7 +68,7 @@ public class XMLRPCSelectorTest extends TestCase { this.id = id; this.lastname = lastname; } - + } - + } -- cgit v1.2.3