From 824998d08071af6d9877fb5314aa25d360d1c172 Mon Sep 17 00:00:00 2001 From: kmoore Date: Sat, 5 Feb 2011 16:22:02 +0000 Subject: rename org.eclipse.jpt.utility to org.eclipse.jpt.common.utility and move to common component --- .../org.eclipse.jpt.common.utility/.classpath | 7 + .../org.eclipse.jpt.common.utility/.cvsignore | 4 + .../org.eclipse.jpt.common.utility/.project | 28 + .../.settings/org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 134 + .../org.eclipse.jpt.common.utility/about.html | 34 + .../build.properties | 17 + .../org.eclipse.jpt.common.utility/component.xml | 11 + .../plugin.properties | 24 + .../org/eclipse/jpt/common/utility/Command.java | 87 + .../jpt/common/utility/CommandExecutor.java | 61 + .../src/org/eclipse/jpt/common/utility/Filter.java | 125 + .../jpt/common/utility/IndentingPrintWriter.java | 155 + .../org/eclipse/jpt/common/utility/JavaType.java | 135 + .../jpt/common/utility/MethodSignature.java | 73 + .../jpt/common/utility/ObjectReference.java | 29 + .../common/utility/ReadOnlyObjectReference.java | 43 + .../utility/internal/AbstractAssociation.java | 69 + .../jpt/common/utility/internal/ArrayTools.java | 3122 +++++++++++++ .../jpt/common/utility/internal/Association.java | 46 + .../internal/AsynchronousCommandExecutor.java | 168 + .../eclipse/jpt/common/utility/internal/Bag.java | 197 + .../jpt/common/utility/internal/BidiFilter.java | 122 + .../utility/internal/BidiStringConverter.java | 149 + .../common/utility/internal/BidiTransformer.java | 93 + .../jpt/common/utility/internal/BitTools.java | 214 + .../common/utility/internal/BooleanReference.java | 48 + .../jpt/common/utility/internal/BooleanTools.java | 105 + .../jpt/common/utility/internal/ClassName.java | 431 ++ .../jpt/common/utility/internal/Classpath.java | 939 ++++ .../common/utility/internal/CollectionTools.java | 1957 ++++++++ .../common/utility/internal/CommandRunnable.java | 37 + .../common/utility/internal/CompositeCommand.java | 44 + .../utility/internal/CompositeException.java | 96 + .../internal/ConsumerThreadCoordinator.java | 253 ++ .../common/utility/internal/ExceptionHandler.java | 87 + .../jpt/common/utility/internal/FileTools.java | 1002 +++++ .../utility/internal/FlaggedObjectReference.java | 69 + .../jpt/common/utility/internal/HashBag.java | 877 ++++ .../common/utility/internal/IdentityHashBag.java | 924 ++++ .../jpt/common/utility/internal/IntReference.java | 40 + .../jpt/common/utility/internal/JDBCTools.java | 349 ++ .../jpt/common/utility/internal/JDBCType.java | 162 + .../jpt/common/utility/internal/KeyedSet.java | 129 + .../internal/LazyReadOnlyObjectReference.java | 107 + .../jpt/common/utility/internal/ListenerList.java | 171 + .../jpt/common/utility/internal/NameTools.java | 376 ++ .../internal/NonNullBooleanTransformer.java | 79 + .../utility/internal/NotBooleanTransformer.java | 56 + .../jpt/common/utility/internal/NotNullFilter.java | 51 + .../jpt/common/utility/internal/NullList.java | 153 + .../eclipse/jpt/common/utility/internal/Queue.java | 75 + .../eclipse/jpt/common/utility/internal/Range.java | 87 + .../utility/internal/ReadOnlyBooleanReference.java | 46 + .../utility/internal/ReadOnlyIntReference.java | 145 + .../common/utility/internal/ReflectionTools.java | 1544 +++++++ .../common/utility/internal/ReverseComparator.java | 40 + .../common/utility/internal/RunnableCommand.java | 37 + .../common/utility/internal/SimpleAssociation.java | 69 + .../utility/internal/SimpleBooleanReference.java | 107 + .../utility/internal/SimpleCommandExecutor.java | 46 + .../jpt/common/utility/internal/SimpleFilter.java | 107 + .../utility/internal/SimpleIntReference.java | 186 + .../common/utility/internal/SimpleJavaType.java | 213 + .../utility/internal/SimpleMethodSignature.java | 240 + .../utility/internal/SimpleObjectReference.java | 98 + .../jpt/common/utility/internal/SimpleQueue.java | 90 + .../jpt/common/utility/internal/SimpleStack.java | 100 + .../utility/internal/SimpleStringMatcher.java | 259 ++ .../utility/internal/SimpleThreadFactory.java | 53 + .../eclipse/jpt/common/utility/internal/Stack.java | 75 + .../utility/internal/StatefulCommandExecutor.java | 33 + .../common/utility/internal/StringConverter.java | 80 + .../jpt/common/utility/internal/StringMatcher.java | 65 + .../jpt/common/utility/internal/StringTools.java | 4708 ++++++++++++++++++++ .../common/utility/internal/SynchronizedBag.java | 220 + .../utility/internal/SynchronizedBoolean.java | 437 ++ .../common/utility/internal/SynchronizedInt.java | 914 ++++ .../utility/internal/SynchronizedObject.java | 472 ++ .../common/utility/internal/SynchronizedQueue.java | 348 ++ .../common/utility/internal/SynchronizedStack.java | 325 ++ .../utility/internal/ThreadLocalCommand.java | 63 + .../internal/ThreadLocalCommandExecutor.java | 65 + .../eclipse/jpt/common/utility/internal/Tools.java | 89 + .../jpt/common/utility/internal/Transformer.java | 95 + .../common/utility/internal/XMLStringEncoder.java | 182 + .../internal/enumerations/EmptyEnumeration.java | 62 + .../internal/enumerations/IteratorEnumeration.java | 57 + .../utility/internal/iterables/ArrayIterable.java | 77 + .../internal/iterables/ArrayListIterable.java | 59 + .../utility/internal/iterables/ChainIterable.java | 96 + .../utility/internal/iterables/CloneIterable.java | 66 + .../internal/iterables/CloneListIterable.java | 92 + .../internal/iterables/CompositeIterable.java | 98 + .../internal/iterables/CompositeListIterable.java | 135 + .../utility/internal/iterables/EmptyIterable.java | 65 + .../internal/iterables/EmptyListIterable.java | 65 + .../internal/iterables/FilteringIterable.java | 95 + .../utility/internal/iterables/GraphIterable.java | 156 + .../utility/internal/iterables/ListIterable.java | 27 + .../internal/iterables/ListListIterable.java | 35 + .../internal/iterables/LiveCloneIterable.java | 85 + .../internal/iterables/LiveCloneListIterable.java | 85 + .../internal/iterables/PeekableIterable.java | 56 + .../utility/internal/iterables/QueueIterable.java | 51 + .../iterables/ReadOnlyCompositeListIterable.java | 100 + .../internal/iterables/ReadOnlyIterable.java | 50 + .../internal/iterables/ReadOnlyListIterable.java | 50 + .../internal/iterables/SingleElementIterable.java | 55 + .../iterables/SingleElementListIterable.java | 58 + .../internal/iterables/SnapshotCloneIterable.java | 124 + .../iterables/SnapshotCloneListIterable.java | 102 + .../utility/internal/iterables/StackIterable.java | 51 + .../internal/iterables/SubIterableWrapper.java | 47 + .../internal/iterables/SubListIterableWrapper.java | 52 + .../internal/iterables/SuperIterableWrapper.java | 48 + .../iterables/SuperListIterableWrapper.java | 54 + .../internal/iterables/TransformationIterable.java | 91 + .../iterables/TransformationListIterable.java | 111 + .../utility/internal/iterables/TreeIterable.java | 137 + .../utility/internal/iterators/ArrayIterator.java | 88 + .../internal/iterators/ArrayListIterator.java | 93 + .../utility/internal/iterators/ChainIterator.java | 159 + .../utility/internal/iterators/CloneIterator.java | 191 + .../internal/iterators/CloneListIterator.java | 291 ++ .../internal/iterators/CompositeIterator.java | 162 + .../internal/iterators/CompositeListIterator.java | 270 ++ .../utility/internal/iterators/EmptyIterator.java | 69 + .../internal/iterators/EmptyListIterator.java | 93 + .../internal/iterators/EnumerationIterator.java | 52 + .../internal/iterators/FilteringIterator.java | 148 + .../utility/internal/iterators/GraphIterator.java | 283 ++ .../internal/iterators/PeekableIterator.java | 112 + .../utility/internal/iterators/QueueIterator.java | 59 + .../iterators/ReadOnlyCompositeListIterator.java | 252 ++ .../internal/iterators/ReadOnlyIterator.java | 65 + .../internal/iterators/ReadOnlyListIterator.java | 108 + .../internal/iterators/ResultSetIterator.java | 162 + .../internal/iterators/ReverseIterator.java | 82 + .../internal/iterators/SingleElementIterator.java | 67 + .../iterators/SingleElementListIterator.java | 98 + .../utility/internal/iterators/StackIterator.java | 59 + .../internal/iterators/SubIteratorWrapper.java | 59 + .../internal/iterators/SubListIteratorWrapper.java | 90 + .../internal/iterators/SuperIteratorWrapper.java | 58 + .../iterators/SuperListIteratorWrapper.java | 88 + .../internal/iterators/SynchronizedIterator.java | 76 + .../iterators/SynchronizedListIterator.java | 122 + .../internal/iterators/TransformationIterator.java | 103 + .../iterators/TransformationListIterator.java | 152 + .../utility/internal/iterators/TreeIterator.java | 254 ++ .../utility/internal/model/AbstractModel.java | 1007 +++++ .../internal/model/AspectChangeSupport.java | 349 ++ .../utility/internal/model/ChangeSupport.java | 2844 ++++++++++++ .../internal/model/SingleAspectChangeSupport.java | 380 ++ .../listener/awt/AWTChangeListenerWrapper.java | 454 ++ .../awt/AWTCollectionChangeListenerWrapper.java | 161 + .../listener/awt/AWTListChangeListenerWrapper.java | 211 + .../awt/AWTPropertyChangeListenerWrapper.java | 87 + .../awt/AWTStateChangeListenerWrapper.java | 86 + .../listener/awt/AWTTreeChangeListenerWrapper.java | 161 + .../model/value/AbstractCollectionValueModel.java | 124 + .../model/value/AbstractListValueModel.java | 124 + .../model/value/AbstractPropertyValueModel.java | 124 + .../value/AbstractPropertyValueModelAdapter.java | 117 + .../model/value/AbstractTreeNodeValueModel.java | 194 + .../internal/model/value/AspectAdapter.java | 266 ++ .../value/AspectCollectionValueModelAdapter.java | 155 + .../model/value/AspectListValueModelAdapter.java | 197 + .../value/AspectPropertyValueModelAdapter.java | 178 + .../model/value/AspectTreeValueModelAdapter.java | 119 + .../value/BufferedWritablePropertyValueModel.java | 392 ++ .../CachingTransformationPropertyValueModel.java | 112 + ...ngTransformationWritablePropertyValueModel.java | 107 + .../value/ChangePropertyValueModelAdapter.java | 99 + .../model/value/CollectionAspectAdapter.java | 158 + .../value/CollectionListValueModelAdapter.java | 217 + .../value/CollectionPropertyValueModelAdapter.java | 139 + .../model/value/CollectionValueModelWrapper.java | 132 + .../value/CompositeBooleanPropertyValueModel.java | 338 ++ .../model/value/CompositeCollectionValueModel.java | 448 ++ .../model/value/CompositeListValueModel.java | 683 +++ .../model/value/CompositePropertyValueModel.java | 198 + .../model/value/ExtendedListValueModelWrapper.java | 211 + .../model/value/FilteringCollectionValueModel.java | 179 + .../model/value/FilteringPropertyValueModel.java | 142 + .../value/FilteringWritablePropertyValueModel.java | 123 + .../value/ItemAspectListValueModelAdapter.java | 274 ++ .../value/ItemChangeListValueModelAdapter.java | 68 + .../value/ItemCollectionListValueModelAdapter.java | 101 + .../model/value/ItemListListValueModelAdapter.java | 109 + .../value/ItemPropertyListValueModelAdapter.java | 84 + .../value/ItemStateListValueModelAdapter.java | 74 + .../model/value/ItemTreeListValueModelAdapter.java | 101 + .../internal/model/value/ListAspectAdapter.java | 176 + .../value/ListCollectionValueModelAdapter.java | 233 + .../utility/internal/model/value/ListCurator.java | 226 + .../model/value/ListPropertyValueModelAdapter.java | 167 + .../model/value/ListValueModelWrapper.java | 164 + .../model/value/NullCollectionValueModel.java | 58 + .../internal/model/value/NullListValueModel.java | 71 + .../model/value/NullPropertyValueModel.java | 49 + .../internal/model/value/NullTreeValueModel.java | 52 + .../model/value/PropertyAspectAdapter.java | 128 + .../value/PropertyCollectionValueModelAdapter.java | 141 + .../model/value/PropertyListValueModelAdapter.java | 157 + .../model/value/PropertyValueModelWrapper.java | 92 + .../ReadOnlyWritablePropertyValueModelWrapper.java | 49 + .../model/value/SetCollectionValueModel.java | 134 + .../model/value/SimpleCollectionValueModel.java | 188 + .../internal/model/value/SimpleListValueModel.java | 322 ++ .../model/value/SimplePropertyValueModel.java | 66 + .../model/value/SortedListValueModelAdapter.java | 125 + .../model/value/SortedListValueModelWrapper.java | 250 ++ .../value/StatePropertyValueModelAdapter.java | 96 + .../model/value/StaticCollectionValueModel.java | 73 + .../internal/model/value/StaticListValueModel.java | 93 + .../model/value/StaticPropertyValueModel.java | 53 + .../internal/model/value/StaticTreeValueModel.java | 57 + .../model/value/TransformationListValueModel.java | 309 ++ .../value/TransformationPropertyValueModel.java | 144 + .../TransformationWritablePropertyValueModel.java | 131 + .../internal/model/value/TreeAspectAdapter.java | 155 + .../model/value/TreePropertyValueModelAdapter.java | 144 + .../internal/model/value/ValueAspectAdapter.java | 201 + .../internal/model/value/ValueChangeAdapter.java | 75 + .../model/value/ValueCollectionAdapter.java | 107 + .../internal/model/value/ValueListAdapter.java | 123 + .../internal/model/value/ValuePropertyAdapter.java | 82 + .../internal/model/value/ValueStateAdapter.java | 73 + .../internal/model/value/ValueTreeAdapter.java | 107 + ...ritablePropertyCollectionValueModelAdapter.java | 62 + .../WritablePropertyListValueModelAdapter.java | 62 + .../value/prefs/PreferencePropertyValueModel.java | 346 ++ .../prefs/PreferencesCollectionValueModel.java | 203 + .../model/value/swing/AbstractTreeModel.java | 216 + .../model/value/swing/CheckBoxModelAdapter.java | 43 + .../internal/model/value/swing/ColumnAdapter.java | 49 + .../model/value/swing/ComboBoxModelAdapter.java | 140 + .../model/value/swing/DateSpinnerModelAdapter.java | 198 + .../model/value/swing/DocumentAdapter.java | 375 ++ .../model/value/swing/ListModelAdapter.java | 292 ++ .../model/value/swing/ListSpinnerModelAdapter.java | 218 + .../value/swing/NumberSpinnerModelAdapter.java | 223 + .../value/swing/ObjectListSelectionModel.java | 427 ++ .../model/value/swing/PrimitiveListTreeModel.java | 239 + .../model/value/swing/RadioButtonModelAdapter.java | 151 + .../model/value/swing/SpinnerModelAdapter.java | 207 + .../model/value/swing/TableModelAdapter.java | 420 ++ .../value/swing/ToggleButtonModelAdapter.java | 224 + .../model/value/swing/TreeModelAdapter.java | 914 ++++ .../common/utility/internal/node/AbstractNode.java | 941 ++++ .../internal/node/AsynchronousValidator.java | 50 + .../utility/internal/node/DefaultProblem.java | 85 + .../jpt/common/utility/internal/node/Node.java | 377 ++ .../utility/internal/node/PluggableValidator.java | 121 + .../jpt/common/utility/internal/node/Problem.java | 51 + .../internal/node/SynchronousValidator.java | 44 + .../internal/swing/CachingComboBoxModel.java | 42 + .../internal/swing/CheckBoxTableCellRenderer.java | 206 + .../internal/swing/ComboBoxTableCellRenderer.java | 328 ++ .../common/utility/internal/swing/Displayable.java | 44 + .../common/utility/internal/swing/EmptyIcon.java | 54 + .../internal/swing/FilteringListBrowser.java | 140 + .../utility/internal/swing/FilteringListPanel.java | 455 ++ .../common/utility/internal/swing/ListChooser.java | 430 ++ .../utility/internal/swing/NodeSelector.java | 32 + .../internal/swing/NonCachingComboBoxModel.java | 73 + .../utility/internal/swing/SimpleDisplayable.java | 170 + .../utility/internal/swing/SimpleListBrowser.java | 86 + .../internal/swing/SimpleListCellRenderer.java | 128 + .../internal/swing/SpinnerTableCellRenderer.java | 186 + .../internal/swing/TableCellEditorAdapter.java | 96 + .../synchronizers/AsynchronousSynchronizer.java | 188 + .../CallbackAsynchronousSynchronizer.java | 120 + .../CallbackSynchronousSynchronizer.java | 83 + .../synchronizers/SynchronousSynchronizer.java | 263 ++ .../eclipse/jpt/common/utility/model/Model.java | 143 + .../common/utility/model/event/ChangeEvent.java | 66 + .../utility/model/event/CollectionAddEvent.java | 124 + .../utility/model/event/CollectionChangeEvent.java | 105 + .../utility/model/event/CollectionClearEvent.java | 61 + .../utility/model/event/CollectionEvent.java | 63 + .../utility/model/event/CollectionRemoveEvent.java | 112 + .../common/utility/model/event/ListAddEvent.java | 134 + .../utility/model/event/ListChangeEvent.java | 105 + .../common/utility/model/event/ListClearEvent.java | 61 + .../jpt/common/utility/model/event/ListEvent.java | 64 + .../common/utility/model/event/ListMoveEvent.java | 120 + .../utility/model/event/ListRemoveEvent.java | 134 + .../utility/model/event/ListReplaceEvent.java | 150 + .../utility/model/event/PropertyChangeEvent.java | 109 + .../utility/model/event/StateChangeEvent.java | 51 + .../common/utility/model/event/TreeAddEvent.java | 81 + .../utility/model/event/TreeChangeEvent.java | 90 + .../common/utility/model/event/TreeClearEvent.java | 61 + .../jpt/common/utility/model/event/TreeEvent.java | 62 + .../utility/model/event/TreeRemoveEvent.java | 81 + .../utility/model/listener/ChangeAdapter.java | 109 + .../utility/model/listener/ChangeListener.java | 25 + .../model/listener/CollectionChangeAdapter.java | 51 + .../model/listener/CollectionChangeListener.java | 65 + .../model/listener/CommandChangeListener.java | 44 + .../utility/model/listener/ListChangeAdapter.java | 61 + .../utility/model/listener/ListChangeListener.java | 87 + .../MultiMethodReflectiveChangeListener.java | 160 + .../model/listener/PropertyChangeAdapter.java | 31 + .../model/listener/PropertyChangeListener.java | 37 + .../model/listener/ReflectiveChangeListener.java | 377 ++ .../model/listener/SimpleChangeListener.java | 131 + .../SingleMethodReflectiveChangeListener.java | 60 + .../utility/model/listener/StateChangeAdapter.java | 31 + .../model/listener/StateChangeListener.java | 37 + .../utility/model/listener/TreeChangeAdapter.java | 51 + .../utility/model/listener/TreeChangeListener.java | 67 + .../utility/model/value/CollectionValueModel.java | 42 + .../common/utility/model/value/ListValueModel.java | 57 + .../utility/model/value/PropertyValueModel.java | 36 + .../utility/model/value/TreeNodeValueModel.java | 74 + .../common/utility/model/value/TreeValueModel.java | 36 + .../model/value/WritableCollectionValueModel.java | 34 + .../model/value/WritableListValueModel.java | 34 + .../model/value/WritablePropertyValueModel.java | 33 + .../synchronizers/CallbackSynchronizer.java | 92 + .../common/utility/synchronizers/Synchronizer.java | 83 + 326 files changed, 62391 insertions(+) create mode 100644 common/plugins/org.eclipse.jpt.common.utility/.classpath create mode 100644 common/plugins/org.eclipse.jpt.common.utility/.cvsignore create mode 100644 common/plugins/org.eclipse.jpt.common.utility/.project create mode 100644 common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.core.resources.prefs create mode 100644 common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.jdt.core.prefs create mode 100644 common/plugins/org.eclipse.jpt.common.utility/META-INF/MANIFEST.MF create mode 100644 common/plugins/org.eclipse.jpt.common.utility/about.html create mode 100644 common/plugins/org.eclipse.jpt.common.utility/build.properties create mode 100644 common/plugins/org.eclipse.jpt.common.utility/component.xml create mode 100644 common/plugins/org.eclipse.jpt.common.utility/plugin.properties create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Command.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/CommandExecutor.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Filter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/IndentingPrintWriter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/JavaType.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/MethodSignature.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ObjectReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ReadOnlyObjectReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AbstractAssociation.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ArrayTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Association.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AsynchronousCommandExecutor.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Bag.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiFilter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiStringConverter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiTransformer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BitTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ClassName.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Classpath.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CollectionTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CommandRunnable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeCommand.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeException.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ConsumerThreadCoordinator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ExceptionHandler.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FileTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FlaggedObjectReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/HashBag.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IdentityHashBag.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IntReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCType.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/KeyedSet.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/LazyReadOnlyObjectReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ListenerList.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NameTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NonNullBooleanTransformer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotBooleanTransformer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotNullFilter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NullList.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Queue.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Range.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyBooleanReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyIntReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReflectionTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReverseComparator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/RunnableCommand.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleAssociation.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleBooleanReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleCommandExecutor.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleFilter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleIntReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleJavaType.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleMethodSignature.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleObjectReference.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleQueue.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStack.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStringMatcher.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleThreadFactory.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Stack.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StatefulCommandExecutor.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringConverter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringMatcher.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringTools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBag.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBoolean.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedInt.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedObject.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedQueue.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedStack.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommand.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommandExecutor.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Tools.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Transformer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/XMLStringEncoder.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/EmptyEnumeration.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/IteratorEnumeration.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ChainIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/FilteringIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/GraphIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/PeekableIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/QueueIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyCompositeListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/StackIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubIterableWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubListIterableWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperIterableWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperListIterableWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationListIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TreeIterable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ChainIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EnumerationIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/FilteringIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/GraphIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/PeekableIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/QueueIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyCompositeListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ResultSetIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReverseIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/StackIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubIteratorWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubListIteratorWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperIteratorWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperListIteratorWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationListIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TreeIterator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AbstractModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AspectChangeSupport.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/ChangeSupport.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/SingleAspectChangeSupport.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTCollectionChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTListChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTPropertyChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTStateChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTTreeChangeListenerWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractTreeNodeValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectCollectionValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectPropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectTreeValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/BufferedWritablePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationWritablePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ChangePropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionAspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionPropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeBooleanPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ExtendedListValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringWritablePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemAspectListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemChangeListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemCollectionListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemListListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemPropertyListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemStateListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemTreeListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListAspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCollectionValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCurator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListPropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullTreeValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyAspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyCollectionValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ReadOnlyWritablePropertyValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SetCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimplePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelWrapper.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StatePropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticTreeValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationPropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationWritablePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreeAspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreePropertyValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueAspectAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueCollectionAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueListAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValuePropertyAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueStateAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueTreeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyCollectionValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyListValueModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencesCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/AbstractTreeModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/CheckBoxModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ColumnAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ComboBoxModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DateSpinnerModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DocumentAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListSpinnerModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ObjectListSelectionModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/PrimitiveListTreeModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/RadioButtonModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/SpinnerModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ToggleButtonModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TreeModelAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AbstractNode.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AsynchronousValidator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/DefaultProblem.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Node.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/PluggableValidator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Problem.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/SynchronousValidator.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CachingComboBoxModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CheckBoxTableCellRenderer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ComboBoxTableCellRenderer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/Displayable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/EmptyIcon.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListBrowser.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListPanel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ListChooser.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NodeSelector.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NonCachingComboBoxModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleDisplayable.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListBrowser.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListCellRenderer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SpinnerTableCellRenderer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/TableCellEditorAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/AsynchronousSynchronizer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackSynchronousSynchronizer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/SynchronousSynchronizer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/Model.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionAddEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionClearEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionRemoveEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListAddEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListClearEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListMoveEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListRemoveEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListReplaceEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/PropertyChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/StateChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeAddEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeChangeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeClearEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeRemoveEvent.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CommandChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/MultiMethodReflectiveChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ReflectiveChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SimpleChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SingleMethodReflectiveChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeAdapter.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeListener.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/CollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/ListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/PropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeNodeValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableCollectionValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableListValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritablePropertyValueModel.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/CallbackSynchronizer.java create mode 100644 common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/Synchronizer.java (limited to 'common/plugins') diff --git a/common/plugins/org.eclipse.jpt.common.utility/.classpath b/common/plugins/org.eclipse.jpt.common.utility/.classpath new file mode 100644 index 0000000000..304e86186a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/common/plugins/org.eclipse.jpt.common.utility/.cvsignore b/common/plugins/org.eclipse.jpt.common.utility/.cvsignore new file mode 100644 index 0000000000..a128605b1f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/.cvsignore @@ -0,0 +1,4 @@ +bin +@dot +temp.folder +build.xml \ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.utility/.project b/common/plugins/org.eclipse.jpt.common.utility/.project new file mode 100644 index 0000000000..dbe6e576b1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/.project @@ -0,0 +1,28 @@ + + + org.eclipse.jpt.common.utility + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.core.resources.prefs b/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..8e5b2c2b65 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Jan 15 11:12:22 EST 2008 +eclipse.preferences.version=1 +encoding/=ISO-8859-1 diff --git a/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.jdt.core.prefs b/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..443826069d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +#Sun May 27 14:55:37 EDT 2007 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/common/plugins/org.eclipse.jpt.common.utility/META-INF/MANIFEST.MF b/common/plugins/org.eclipse.jpt.common.utility/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..3699c38402 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/META-INF/MANIFEST.MF @@ -0,0 +1,134 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-Vendor: %providerName +Bundle-SymbolicName: org.eclipse.jpt.common.utility +Bundle-Version: 1.6.0.qualifier +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Export-Package: org.eclipse.jpt.common.utility, + org.eclipse.jpt.common.utility.internal; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.enumerations; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.iterables; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.iterators; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.model; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.model.listener.awt; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.model.value; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.model.value.prefs; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.model.value.swing; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.node; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.swing; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.internal.synchronizers; + x-friends:="org.eclipse.jpt.core, + org.eclipse.jpt.common.core, + org.eclipse.jpt.common.ui, + org.eclipse.jpt.jpa.db, + org.eclipse.jpt.jpa.db.ui, + org.eclipse.jpt.gen, + org.eclipse.jpt.jaxb.core, + org.eclipse.jpt.jaxb.ui, + org.eclipse.jpt.ui", + org.eclipse.jpt.common.utility.model, + org.eclipse.jpt.common.utility.model.event, + org.eclipse.jpt.common.utility.model.listener, + org.eclipse.jpt.common.utility.model.value, + org.eclipse.jpt.common.utility.synchronizers diff --git a/common/plugins/org.eclipse.jpt.common.utility/about.html b/common/plugins/org.eclipse.jpt.common.utility/about.html new file mode 100644 index 0000000000..be534ba44f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/about.html @@ -0,0 +1,34 @@ + + + + +About + + + + + +

About This Content

+ +

May 02, 2008

+ +

License

+ +

The Eclipse Foundation makes available all content in this plug-in +("Content"). Unless otherwise indicated below, the Content is provided to you +under the terms and conditions of the Eclipse Public License Version 1.0 +("EPL"). A copy of the EPL is available at +http://www.eclipse.org/org/documents/epl-v10.php. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the +Content is being redistributed by another party ("Redistributor") and different +terms and conditions may apply to your use of any object code in the Content. +Check the Redistributor's license that was provided with the Content. If no such +license exists, contact the Redistributor. Unless otherwise indicated below, the +terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at +http://www.eclipse.org/.

+ + + diff --git a/common/plugins/org.eclipse.jpt.common.utility/build.properties b/common/plugins/org.eclipse.jpt.common.utility/build.properties new file mode 100644 index 0000000000..11ab8d42f6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/build.properties @@ -0,0 +1,17 @@ +################################################################################ +# Copyright (c) 2006, 2007 Oracle. All rights reserved. +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v1.0, which accompanies this distribution +# and is available at http://www.eclipse.org/legal/epl-v10.html. +# +# Contributors: +# Oracle - initial API and implementation +################################################################################ +javacSource=1.5 +javacTarget=1.5 +source.. = src/ +output.. = bin/ +bin.includes = .,\ + META-INF/,\ + about.html,\ + plugin.properties diff --git a/common/plugins/org.eclipse.jpt.common.utility/component.xml b/common/plugins/org.eclipse.jpt.common.utility/component.xml new file mode 100644 index 0000000000..80c3a500b9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/component.xml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.utility/plugin.properties b/common/plugins/org.eclipse.jpt.common.utility/plugin.properties new file mode 100644 index 0000000000..c959ed0246 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/plugin.properties @@ -0,0 +1,24 @@ +################################################################################ +# Copyright (c) 2006, 2009 Oracle. All rights reserved. +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v1.0, which accompanies this distribution +# and is available at http://www.eclipse.org/legal/epl-v10.html. +# +# Contributors: +# Oracle - initial API and implementation +################################################################################ +# ==================================================================== +# To code developer: +# Do NOT change the properties between this line and the +# "%%% END OF TRANSLATED PROPERTIES %%%" line. +# Make a new property name, append to the end of the file and change +# the code to use the new property. +# ==================================================================== + +# ==================================================================== +# %%% END OF TRANSLATED PROPERTIES %%% +# ==================================================================== + +pluginName = Dali Java Persistence Tools - Utility +providerName = Eclipse Web Tools Platform + diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Command.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Command.java new file mode 100644 index 0000000000..f152ba5fa6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Command.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.Serializable; + +/** + * Simple interface for implementing the GOF Command design pattern, + * and it doesn't carry the baggage of {@link java.lang.Runnable}. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface Command { + + /** + * Execute the command. The semantics of the command + * is determined by the contract between the client and server. + */ + void execute(); + + /** + * Singleton implementation of the command interface that will do nothing + * when executed. + */ + final class Null implements Command, Serializable { + public static final Command INSTANCE = new Null(); + public static Command instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void execute() { + // do nothing + } + @Override + public String toString() { + return "Command.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * Singleton implementation of the command interface that will throw an + * exception when executed. + */ + final class Disabled implements Command, Serializable { + public static final Command INSTANCE = new Disabled(); + public static Command instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public void execute() { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "Command.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/CommandExecutor.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/CommandExecutor.java new file mode 100644 index 0000000000..461ab8615d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/CommandExecutor.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.Serializable; + +/** + * This interface allows clients to control how a command is executed. + * This is useful when the server provides the command but the client provides + * the context (e.g. the client would like to dispatch the command to the UI + * thread). + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface CommandExecutor { + + /** + * Execute the specified command. + */ + void execute(Command command); + + + /** + * Singleton implementation of the command executor interface + * that simply executes the command without any sort of enhancement. + */ + final class Default implements CommandExecutor, Serializable { + public static final CommandExecutor INSTANCE = new Default(); + public static CommandExecutor instance() { + return INSTANCE; + } + // ensure single instance + private Default() { + super(); + } + public void execute(Command command) { + command.execute(); + } + @Override + public String toString() { + return "CommandExecutor.Default"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Filter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Filter.java new file mode 100644 index 0000000000..222185940e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Filter.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.Serializable; + +/** + * Used by various "pluggable" classes to filter objects. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of objects to be filtered + */ +public interface Filter { + + /** + * Return whether the specified object is "accepted" by the + * filter. The semantics of "accept" is determined by the + * contract between the client and the server. + */ + boolean accept(T o); + + + /** + * Singleton implemetation of the filter interface that accepts all the + * objects (i.e. it does no filtering). + */ + final class Null implements Filter, Serializable { + @SuppressWarnings("rawtypes") + public static final Filter INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static Filter instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // nothing is filtered - everything is accepted + public boolean accept(S o) { + return true; + } + @Override + public String toString() { + return "Filter.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * Singleton implemetation of the filter interface that accepts none of the + * objects (i.e. it filters out all the objects). + */ + final class Opaque implements Filter, Serializable { + @SuppressWarnings("rawtypes") + public static final Filter INSTANCE = new Opaque(); + @SuppressWarnings("unchecked") + public static Filter instance() { + return INSTANCE; + } + // ensure single instance + private Opaque() { + super(); + } + // everything is filtered - nothing is accepted + public boolean accept(S o) { + return false; + } + @Override + public String toString() { + return "Filter.Opaque"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * Singleton implemetation of the filter interface that throws an exception + * if called. + */ + final class Disabled implements Filter, Serializable { + @SuppressWarnings("rawtypes") + public static final Filter INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static Filter instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public boolean accept(S o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "Filter.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/IndentingPrintWriter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/IndentingPrintWriter.java new file mode 100644 index 0000000000..11641aa132 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/IndentingPrintWriter.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Extend {@link PrintWriter} to automatically indent new lines. + */ +public class IndentingPrintWriter + extends PrintWriter +{ + private final String indent; + private int indentLevel; + private boolean needsIndent; + + public static String DEFAULT_INDENT = "\t"; //$NON-NLS-1$ + + + /** + * Construct a writer that indents with tabs. + */ + public IndentingPrintWriter(Writer out) { + this(out, DEFAULT_INDENT); + } + + /** + * Construct a writer that indents with the specified string. + */ + public IndentingPrintWriter(Writer out, String indent) { + super(out); + this.indent = indent; + this.indentLevel = 0; + this.needsIndent = true; + } + + /** + * Set flag so following line is indented. + */ + @Override + public void println() { + synchronized (this.lock) { + super.println(); + this.needsIndent = true; + } + } + + /** + * Print the appropriate indent. + * Pre-condition: synchronized + */ + private void printIndent() { + if (this.needsIndent) { + this.needsIndent = false; + for (int i = this.indentLevel; i-- > 0; ) { + this.print(this.indent); + } + } + } + + /** + * Write a portion of an array of characters. + */ + @Override + public void write(char buf[], int off, int len) { + synchronized (this.lock) { + this.printIndent(); + super.write(buf, off, len); + } + } + + /** + * Write a single character. + */ + @Override + public void write(int c) { + synchronized (this.lock) { + this.printIndent(); + super.write(c); + } + } + + /** + * Write a portion of a string. + */ + @Override + public void write(String s, int off, int len) { + synchronized (this.lock) { + this.printIndent(); + super.write(s, off, len); + } + } + + /** + * Bump the indent level. + */ + public void indent() { + this.incrementIndentLevel(); + } + + /** + * Decrement the indent level. + */ + public void undent() { + this.decrementIndentLevel(); + } + + /** + * Bump the indent level. + */ + public void incrementIndentLevel() { + synchronized (this.lock) { + this.indentLevel++; + } + } + + /** + * Decrement the indent level. + */ + public void decrementIndentLevel() { + synchronized (this.lock) { + this.indentLevel--; + } + } + + /** + * Return the current indent level. + */ + public int getIndentLevel() { + synchronized (this.lock) { + return this.indentLevel; + } + } + + /** + * Allow the indent level to be set directly. + * Return the previous indent level. + */ + public int setIndentLevel(int indentLevel) { + synchronized (this.lock) { + int old = this.indentLevel; + this.indentLevel = indentLevel; + return old; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/JavaType.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/JavaType.java new file mode 100644 index 0000000000..fb019d5ebe --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/JavaType.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.PrintWriter; + +/** + * This interface describes a Java type; i.e. its "element type" + * and its "array depth". The element type is referenced by name, + * allowing us to reference classes that are not (or cannot be) loaded. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + *

+ * This interface is not intended to be implemented by clients. + */ +public interface JavaType { + + /** + * Return the name of the type's "element type". + * A member type will have one or more '$' characters in its name. + */ + String getElementTypeName(); + + /** + * Return the type's "array depth". + */ + int getArrayDepth(); + + /** + * Return whether the type is an array (i.e. its "array depth" is greater + * than zero). + */ + boolean isArray(); + + /** + * Return whether the type is a "primitive" (e.g. int, float). + *

+ * NB: void.class.isPrimitive() == true + */ + boolean isPrimitive(); + + /** + * Return whether the type is a "primitive wrapper" (e.g. {@link java.lang.Integer}, + * {@link java.lang.Float}). + *

+ * NB: void.class.isPrimitive() == true + */ + boolean isPrimitiveWrapper(); + + /** + * Return whether the type is a "variable primitive" (e.g. int, float, + * but not void). + *

+ * NB: variables cannot be declared void + */ + boolean isVariablePrimitive(); + + /** + * Return whether the type is a "variable primitive wrapper" (e.g. + * {@link java.lang.Integer}, {@link java.lang.Float}, + * but not {@link java.lang.Void}). + *

+ * NB: variables cannot be declared void + */ + boolean isVariablePrimitiveWrapper(); + + /** + * Return the class corresponding to the type's element type and array depth. + */ + Class getJavaClass() throws ClassNotFoundException; + + /** + * Return the version of the type's name that matches that + * returned by {@link java.lang.Class#getName()} + * (e.g. "[[J", "[Ljava.lang.Object;", + * "java.util.Map$Entry"). + */ + String getJavaClassName(); + + /** + * Return whether the type is equal to the specified type. + */ + boolean equals(String otherElementTypeName, int otherArrayDepth); + + /** + * Return whether the type describes to the specified type. + */ + boolean describes(String className); + + /** + * Return whether the type describes to the specified type. + */ + boolean describes(Class javaClass); + + /** + * Return whether the type is equal to the specified type. + */ + boolean equals(JavaType other); + + /** + * Return the version of the type's name that can be used in source code:

+ */ + String declaration(); + + /** + * Append the version of the type's name that can be used in source code:
    + *
  • "[[J" => "long[][]" + *
  • "java.util.Map$Entry" => "java.util.Map.Entry" + *
+ */ + void appendDeclarationTo(StringBuilder sb); + + /** + * Print the version of the type's name that can be used in source code:
    + *
  • "[[J" => "long[][]" + *
  • "java.util.Map$Entry" => "java.util.Map.Entry" + *
+ */ + void printDeclarationOn(PrintWriter pw); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/MethodSignature.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/MethodSignature.java new file mode 100644 index 0000000000..32e793a071 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/MethodSignature.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +import java.io.PrintWriter; +import java.lang.reflect.Method; + +/** + * This interface describes a Java method signature; i.e. its "name" + * and its "parameter types". The parameter types are referenced by name, + * allowing us to reference classes that are not (or cannot be) loaded. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + *

+ * This interface is not intended to be implemented by clients. + */ +public interface MethodSignature { + + /** + * Return the method's name. + */ + String getName(); + + /** + * Return the method's parameter types. + */ + JavaType[] getParameterTypes(); + + /** + * Return whether the method signature describes the specified method. + */ + boolean describes(Method method); + + /** + * Return whether the method signature equals the specified signature. + */ + boolean equals(String otherName, JavaType[] otherParameterTypes); + + /** + * Return whether the method signature equals the specified signature. + */ + boolean equals(MethodSignature other); + + /** + * Return a string representation of the method's signature:

+ * "foo(int, java.lang.String)" + */ + String getSignature(); + + /** + * Append a string representation of the method's signature:

+ * "foo(int, java.lang.String)" + */ + void appendSignatureTo(StringBuilder sb); + + /** + * Print a string representation of the method's signature:

+ * "foo(int, java.lang.String)" + */ + void printSignatureOn(PrintWriter pw); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ObjectReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ObjectReference.java new file mode 100644 index 0000000000..991c2c97d2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ObjectReference.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +/** + * Provide a container for passing an object that can be changed by the recipient. + */ +public interface ObjectReference + extends ReadOnlyObjectReference +{ + /** + * Set the value. + * Return the previous value. + */ + V setValue(V value); + + /** + * Set the value to null. + * Return the previous value. + */ + V setNull(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ReadOnlyObjectReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ReadOnlyObjectReference.java new file mode 100644 index 0000000000..02c4ff6a24 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ReadOnlyObjectReference.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility; + +/** + * Provide a container for holding an object that cannot be changed. + * + * @see ObjectReference + */ +public interface ReadOnlyObjectReference +{ + /** + * Return the current value. + */ + V getValue(); + + /** + * Return whether the current value is equal to the specified value. + */ + boolean valueEquals(Object object); + + /** + * Return whether the current value is not equal to the specified value. + */ + boolean valueNotEqual(Object object); + + /** + * Return whether the current value is null. + */ + boolean isNull(); + + /** + * Return whether the current value is not null. + */ + boolean isNotNull(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AbstractAssociation.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AbstractAssociation.java new file mode 100644 index 0000000000..744d95c727 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AbstractAssociation.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Implement some of the methods in {@link Association} that can + * be defined in terms of the other methods. + */ +public abstract class AbstractAssociation + implements Association +{ + /** + * Default constructor. + */ + protected AbstractAssociation() { + super(); + } + + @Override + public synchronized boolean equals(Object o) { + if ( ! (o instanceof Association)) { + return false; + } + Association other = (Association) o; + return this.keyEquals(other) && this.valueEquals(other); + } + + protected boolean keyEquals(Association other) { + Object key = this.getKey(); + return (key == null) ? + (other.getKey() == null) : + key.equals(other.getKey()); + } + + protected boolean valueEquals(Association other) { + Object value = this.getValue(); + return (value == null) ? + (other.getValue() == null) : + value.equals(other.getValue()); + } + + @Override + public synchronized int hashCode() { + return this.keyHashCode() ^ this.valueHashCode(); + } + + protected int keyHashCode() { + Object key = this.getKey(); + return (key == null) ? 0 : key.hashCode(); + } + + protected int valueHashCode() { + Object value = this.getValue(); + return (value == null) ? 0 : value.hashCode(); + } + + @Override + public synchronized String toString() { + return this.getKey() + " => " + this.getValue(); //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ArrayTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ArrayTools.java new file mode 100644 index 0000000000..4f152023e7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ArrayTools.java @@ -0,0 +1,3122 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; + +/** + * Array-related utility methods. + */ +public final class ArrayTools { + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + public static final int[] EMPTY_INT_ARRAY = new int[0]; + + // ********** instantiation ********** + + /** + * Return a new array with the same length + * and the same component type as the specified array. + *

+ * Arrays.newArray(Object[] array) + */ + public static E[] newArray(E[] array) { + return newArray(array, array.length); + } + + /** + * Return a new array with the specified length + * and the same component type as the specified array. + *

+ * Arrays.newArray(Object[] array, int length) + */ + public static E[] newArray(E[] array, int length) { + return newArray(componentType(array), length); + } + + /** + * Return the specified array's component type, with appropriate support + * for generics. + */ + public static Class componentType(E[] array) { + Class rawComponentType = array.getClass().getComponentType(); + @SuppressWarnings("unchecked") + Class componentType = (Class) rawComponentType; + return componentType; + } + + /** + * Return a new array with the specified component type and length, + * with appropriate support for generics. The component type cannot be a + * primitive type. + */ + public static E[] newArray(Class componentType, int length) { + if (componentType.isPrimitive()) { + throw new IllegalArgumentException("Array class cannot be primitive: " + componentType); //$NON-NLS-1$ + } + return newArray_(componentType, length); + } + + /** + * assume the component type is not a primitive class + */ + @SuppressWarnings("unchecked") + private static E[] newArray_(Class componentType, int length) { + return (E[]) ((componentType == OBJECT_CLASS) ? + new Object[length] : + Array.newInstance(componentType, length)); + } + private static final Class OBJECT_CLASS = Object.class; + + + // ********** conversion ********** + + /** + * Return an array corresponding to the specified iterable. + *

+ * Iterable.toArray() + * @see Collection#toArray() + */ + public static Object[] array(Iterable iterable) { + return array(iterable.iterator()); + } + + /** + * Return an array corresponding to the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.toArray() + * @see Collection#toArray() + */ + public static Object[] array(Iterable iterable, int iterableSize) { + return array(iterable.iterator(), iterableSize); + } + + /** + * Return an array corresponding to the specified iterable; + * the runtime type of the returned array is that of the specified array. + * If the iterable fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of the iterable. + *

+ * Iterable.toArray(Object[]) + * @see Collection#toArray(Object[]) + */ + public static E[] array(Iterable iterable, E[] array) { + return array(iterable.iterator(), array); + } + + /** + * Return an array corresponding to the specified iterable; + * the runtime type of the returned array is that of the specified array. + * If the iterable fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of the iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.toArray(Object[]) + * @see Collection#toArray(Object[]) + */ + public static E[] array(Iterable iterable, int iterableSize, E[] array) { + return array(iterable.iterator(), iterableSize, array); + } + + /** + * Return an array corresponding to the specified iterator. + *

+ * Iterator.toArray() + * @see Collection#toArray() + */ + public static Object[] array(Iterator iterator) { + return iterator.hasNext() ? + CollectionTools.list(iterator).toArray() : + EMPTY_OBJECT_ARRAY; + } + + /** + * Return an array corresponding to the specified iterator. + * The specified iterator size is a performance hint. + *

+ * Iterator.toArray() + * @see Collection#toArray() + */ + public static Object[] array(Iterator iterator, int iteratorSize) { + return iterator.hasNext() ? + CollectionTools.list(iterator, iteratorSize).toArray() : + EMPTY_OBJECT_ARRAY; + } + + /** + * Return an array corresponding to the specified iterator; + * the runtime type of the returned array is that of the specified array. + * If the iterator fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of the iterator. + *

+ * Iterator.toArray(Object[]) + * @see Collection#toArray(Object[]) + */ + public static E[] array(Iterator iterator, E[] array) { + return iterator.hasNext() ? + CollectionTools.list(iterator).toArray(array) : + emptyArray(array); + } + + /** + * Return an array corresponding to the specified iterator; + * the runtime type of the returned array is that of the specified array. + * If the iterator fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of the iterator. + * The specified iterator size is a performance hint. + *

+ * Iterator.toArray(Object[]) + * @see Collection#toArray(Object[]) + */ + public static E[] array(Iterator iterator, int iteratorSize, E[] array) { + return iterator.hasNext() ? + CollectionTools.list(iterator, iteratorSize).toArray(array) : + emptyArray(array); + } + + /** + * If the specified array is empty, return it; + * otherwise, set its first element to null. + * @see Collection#toArray(Object[]) + */ + private static E[] emptyArray(E[] array) { + return (array.length == 0) ? array : clearFirst(array); + } + + /** + * Set the specified array's first element to null and and return the array. + * Assume the array length > 0. + */ + private static E[] clearFirst(E[] array) { + array[0] = null; + return array; + } + + + // ********** add ********** + + /** + * Return a new array containing the elements in the + * specified array followed by the specified object to be added. + *

+ * Arrays.add(Object[] array, Object o) + */ + public static E[] add(E[] array, E value) { + int len = array.length; + E[] result = newArray(array, len + 1); + if (len > 0) { + System.arraycopy(array, 0, result, 0, len); + } + result[len] = value; + return result; + } + + /** + * Return a new array containing the elements in the + * specified array with the specified object added at the specified index. + *

+ * Arrays.add(Object[] array, int index, Object o) + */ + public static E[] add(E[] array, int index, E value) { + int len = array.length; + E[] result = newArray(array, len + 1); + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + result[index] = value; + if (index < len) { + System.arraycopy(array, index, result, index + 1, len - index); + } + return result; + } + + /** + * Return a new array containing the elements in the + * specified array followed by the specified value to be added. + *

+ * Arrays.add(char[] array, char value) + */ + public static char[] add(char[] array, char value) { + int len = array.length; + char[] result = new char[len + 1]; + if (len > 0) { + System.arraycopy(array, 0, result, 0, len); + } + result[len] = value; + return result; + } + + /** + * Return a new array containing the elements in the + * specified array with the specified value added at the specified index. + *

+ * Arrays.add(char[] array, int index, char value) + */ + public static char[] add(char[] array, int index, char value) { + int len = array.length; + char[] result = new char[len + 1]; + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + result[index] = value; + if (index < len) { + System.arraycopy(array, index, result, index + 1, len - index); + } + return result; + } + + /** + * Return a new array containing the elements in the + * specified array followed by the specified value to be added. + *

+ * Arrays.add(int[] array, int value) + */ + public static int[] add(int[] array, int value) { + int len = array.length; + int[] result = new int[len + 1]; + if (len > 0) { + System.arraycopy(array, 0, result, 0, len); + } + result[len] = value; + return result; + } + + /** + * Return a new array containing the elements in the + * specified array with the specified value added at the specified index. + *

+ * Arrays.add(int[] array, int index, int value) + */ + public static int[] add(int[] array, int index, int value) { + int len = array.length; + int[] result = new int[len + 1]; + if (index > 0) { + System.arraycopy(array, 0, result, 0, index); + } + result[index] = value; + if (index < len) { + System.arraycopy(array, index, result, index + 1, len - index); + } + return result; + } + + + // ********** add all ********** + + /** + * Return an array containing the elements in the + * specified array followed by the elements + * in the specified collection. + *

+ * Arrays.addAll(Object[] array, Collection collection) + */ + public static E[] addAll(E[] array, Collection collection) { + return addAll(array, collection, collection.size()); + } + + /** + * check collection size + */ + private static E[] addAll(E[] array, Collection collection, int collectionSize) { + return (collectionSize == 0) ? array : addAll_(array, collection, collectionSize); + } + + /** + * assume the collection is non-empty + */ + private static E[] addAll_(E[] array, Collection collection) { + return addAll_(array, collection, collection.size()); + } + + /** + * assume collection size > zero + */ + private static E[] addAll_(E[] array, Collection collection, int collectionSize) { + return addAll(array, collection, array.length, collectionSize); + } + + /** + * assume collection size > zero; check array length + */ + private static E[] addAll(E[] array, Collection collection, int arrayLength, int collectionSize) { + return (arrayLength == 0) ? + collection.toArray(newArray(array, collectionSize)) : + addAll_(array, collection, arrayLength, collectionSize); + } + + /** + * assume array length and collection size > zero + */ + private static E[] addAll_(E[] array, Collection collection, int arrayLength, int collectionSize) { + E[] result = newArray(array, arrayLength + collectionSize); + System.arraycopy(array, 0, result, 0, arrayLength); + int i = arrayLength; + for (E element : collection) { + result[i++] = element; + } + return result; + } + + /** + * Return an array containing the elements in the + * specified array followed by the elements + * in the specified iterable. + *

+ * Arrays.addAll(Object[] array, Iterable iterable) + */ + public static E[] addAll(E[] array, Iterable iterable) { + return addAll(array, iterable.iterator()); + } + + /** + * Return an array containing the elements in the + * specified array followed by the elements + * in the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Arrays.addAll(Object[] array, Iterable iterable) + */ + public static E[] addAll(E[] array, Iterable iterable, int iterableSize) { + return addAll(array, iterable.iterator(), iterableSize); + } + + /** + * Return an array containing the elements in the + * specified array followed by the elements + * in the specified iterator. + *

+ * Arrays.addAll(Object[] array, Iterator iterator) + */ + public static E[] addAll(E[] array, Iterator iterator) { + return iterator.hasNext() ? addAll_(array, CollectionTools.list(iterator)) : array; + } + + /** + * Return an array containing the elements in the + * specified array followed by the elements + * in the specified iterator. + * The specified iterator size is a performance hint. + *

+ * Arrays.addAll(Object[] array, Iterator iterator) + */ + public static E[] addAll(E[] array, Iterator iterator, int iteratorSize) { + return iterator.hasNext() ? addAll_(array, CollectionTools.list(iterator, iteratorSize)) : array; + } + + /** + * Return an array containing the elements in the + * specified array 1 followed by the elements + * in the specified array 2. + *

+ * Arrays.addAll(Object[] array1, Object[] array2) + */ + public static E[] addAll(E[] array1, E... array2) { + return addAll(array1, array2, array2.length); + } + + /** + * check array 2 length + */ + private static E[] addAll(E[] array1, E[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static E[] addAll_(E[] array1, E[] array2, int array2Length) { + return addAll(array1, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static E[] addAll(E[] array1, E[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? array2 : addAll_(array1, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 + */ + private static E[] addAll_(E[] array1, E[] array2, int array1Length, int array2Length) { + E[] result = newArray(array1, array1Length + array2Length); + System.arraycopy(array1, 0, result, 0, array1Length); + System.arraycopy(array2, 0, result, array1Length, array2Length); + return result; + } + + /** + * Return an array containing the elements in the + * first specified array with the objects in the second + * specified array added at the specified index. + *

+ * Arrays.addAll(Object[] array1, int index, Object[] array2) + */ + public static E[] addAll(E[] array1, int index, E... array2) { + return addAll(array1, index, array2, array2.length); + } + + /** + * check array 2 length + */ + private static E[] addAll(E[] array1, int index, E[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, index, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static E[] addAll_(E[] array1, int index, E[] array2, int array2Length) { + return addAll(array1, index, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static E[] addAll(E[] array1, int index, E[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? + array2 : + (index == array1Length) ? // 'array2' added to end of 'array1' + addAll_(array1, array2, array1Length, array2Length) : + addAll_(array1, index, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 and index != array 1 length + */ + private static E[] addAll_(E[] array1, int index, E[] array2, int array1Length, int array2Length) { + E[] result = newArray(array1, array1Length + array2Length); + System.arraycopy(array1, 0, result, 0, index); + System.arraycopy(array2, 0, result, index, array2Length); + System.arraycopy(array1, index, result, index + array2Length, array1Length - index); + return result; + } + + /** + * Return an array containing the elements in the + * specified array with the elements + * in the specified collection inserted at the specified index. + *

+ * Arrays.addAll(Object[] array, int index, Collection c) + */ + public static E[] addAll(E[] array, int index, Collection collection) { + return addAll(array, index, collection, collection.size()); + } + + /** + * check collection size + */ + private static E[] addAll(E[] array, int index, Collection collection, int collectionSize) { + return (collectionSize == 0) ? array : addAll_(array, index, collection, collectionSize); + } + + /** + * assume collection size > 0 + */ + private static E[] addAll_(E[] array, int index, Collection collection, int collectionSize) { + return addAll(array, index, collection, array.length, collectionSize); + } + + /** + * assume collection size > 0; check array length + */ + private static E[] addAll(E[] array, int index, Collection collection, int arrayLength, int collectionSize) { + if (arrayLength == 0) { + if (index == 0) { + return collection.toArray(newArray(array, collectionSize)); + } + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return (index == arrayLength) ? // 'collection' added to end of 'array' + addAll_(array, collection, arrayLength, collectionSize) : + addAll_(array, index, collection, arrayLength, collectionSize); + } + + /** + * assume array length and collection size > 0 and index != array length + */ + private static E[] addAll_(E[] array, int index, Collection collection, int arrayLength, int collectionSize) { + E[] result = newArray(array, arrayLength + collectionSize); + System.arraycopy(array, 0, result, 0, index); + int i = index; + for (E item : collection) { + result[i++] = item; + } + System.arraycopy(array, index, result, index + collectionSize, arrayLength - index); + return result; + } + + /** + * Return an array containing the elements in the + * specified array with the elements + * in the specified iterable inserted at the specified index. + *

+ * Arrays.addAll(Object[] array, int index, Iterable iterable) + */ + public static E[] addAll(E[] array, int index, Iterable iterable) { + return addAll(array, index, iterable.iterator()); + } + + /** + * Return an array containing the elements in the + * specified array with the elements + * in the specified iterable inserted at the specified index. + *

+ * Arrays.addAll(Object[] array, int index, Iterable iterable) + */ + public static E[] addAll(E[] array, int index, Iterable iterable, int iterableSize) { + return addAll(array, index, iterable.iterator(), iterableSize); + } + + /** + * Return an array containing the elements in the + * specified array with the elements + * in the specified iterator inserted at the specified index. + *

+ * Arrays.addAll(Object[] array, int index, Iterator iterator) + */ + public static E[] addAll(E[] array, int index, Iterator iterator) { + return iterator.hasNext() ? addAll_(array, index, CollectionTools.list(iterator)) : array; + } + + /** + * Return an array containing the elements in the + * specified array with the elements + * in the specified iterator inserted at the specified index. + * The specified iterator size is a performance hint. + *

+ * Arrays.addAll(Object[] array, int index, Iterator iterator) + */ + public static E[] addAll(E[] array, int index, Iterator iterator, int iteratorSize) { + return iterator.hasNext() ? addAll_(array, index, CollectionTools.list(iterator, iteratorSize)) : array; + } + + /** + * assume collection is non-empty + */ + private static E[] addAll_(E[] array, int index, Collection collection) { + return addAll_(array, index, collection, collection.size()); + } + + /** + * Return an array containing the elements in the + * specified array 1 followed by the elements + * in the specified array 2. + *

+ * Arrays.addAll(char[] array1, char[] array2) + */ + public static char[] addAll(char[] array1, char... array2) { + return addAll(array1, array2, array2.length); + } + + /** + * check array 2 length + */ + private static char[] addAll(char[] array1, char[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static char[] addAll_(char[] array1, char[] array2, int array2Length) { + return addAll(array1, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static char[] addAll(char[] array1, char[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? array2 : addAll_(array1, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 + */ + private static char[] addAll_(char[] array1, char[] array2, int array1Length, int array2Length) { + char[] result = new char[array1Length + array2Length]; + System.arraycopy(array1, 0, result, 0, array1Length); + System.arraycopy(array2, 0, result, array1Length, array2Length); + return result; + } + + /** + * Return an array containing the elements in the + * first specified array with the objects in the second + * specified array added at the specified index. + *

+ * Arrays.add(char[] array1, int index, char[] array2) + */ + public static char[] addAll(char[] array1, int index, char... array2) { + return addAll(array1, index, array2, array2.length); + } + + /** + * check array 2 length + */ + private static char[] addAll(char[] array1, int index, char[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, index, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static char[] addAll_(char[] array1, int index, char[] array2, int array2Length) { + return addAll(array1, index, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static char[] addAll(char[] array1, int index, char[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? + array2 : + (index == array1Length) ? // 'array2' added to end of 'array1' + addAll_(array1, array2, array1Length, array2Length) : + addAll_(array1, index, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 and index != array 1 length + */ + private static char[] addAll_(char[] array1, int index, char[] array2, int array1Length, int array2Length) { + char[] result = new char[array1Length + array2Length]; + System.arraycopy(array1, 0, result, 0, index); + System.arraycopy(array2, 0, result, index, array2Length); + System.arraycopy(array1, index, result, index + array2Length, array1Length - index); + return result; + } + + /** + * Return an array containing the elements in the + * specified array 1 followed by the elements + * in the specified array 2. + *

+ * Arrays.addAll(int[] array1, int[] array2) + */ + public static int[] addAll(int[] array1, int... array2) { + return addAll(array1, array2, array2.length); + } + + /** + * check array 2 length + */ + private static int[] addAll(int[] array1, int[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static int[] addAll_(int[] array1, int[] array2, int array2Length) { + return addAll(array1, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static int[] addAll(int[] array1, int[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? array2 : addAll_(array1, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 + */ + private static int[] addAll_(int[] array1, int[] array2, int array1Length, int array2Length) { + int[] result = new int[array1Length + array2Length]; + System.arraycopy(array1, 0, result, 0, array1Length); + System.arraycopy(array2, 0, result, array1Length, array2Length); + return result; + } + + /** + * Return an array containing the elements in the + * first specified array with the objects in the second + * specified array added at the specified index. + *

+ * Arrays.add(int[] array1, int index, int[] array2) + */ + public static int[] addAll(int[] array1, int index, int... array2) { + return addAll(array1, index, array2, array2.length); + } + + /** + * check array 2 length + */ + private static int[] addAll(int[] array1, int index, int[] array2, int array2Length) { + return (array2Length == 0) ? array1 : addAll_(array1, index, array2, array2Length); + } + + /** + * assume array 2 length > 0 + */ + private static int[] addAll_(int[] array1, int index, int[] array2, int array2Length) { + return addAll(array1, index, array2, array1.length, array2Length); + } + + /** + * assume array 2 length > 0; check array 1 length + */ + private static int[] addAll(int[] array1, int index, int[] array2, int array1Length, int array2Length) { + return (array1Length == 0) ? + array2 : + (index == array1Length) ? // 'array2' added to end of 'array1' + addAll_(array1, array2, array1Length, array2Length) : + addAll_(array1, index, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 and index != array 1 length + */ + private static int[] addAll_(int[] array1, int index, int[] array2, int array1Length, int array2Length) { + int[] result = new int[array1Length + array2Length]; + System.arraycopy(array1, 0, result, 0, index); + System.arraycopy(array2, 0, result, index, array2Length); + System.arraycopy(array1, index, result, index + array2Length, array1Length - index); + return result; + } + + + // ********** clear ********** + + /** + * Return an empty array with the same component type as the specified array. + *

+ * Arrays.clear(Object[] array) + */ + public static E[] clear(E[] array) { + return (array.length == 0) ? array : newArray(array, 0); + } + + + // ********** concatenate ********** + + /** + * Return an array containing all the elements in all the + * specified arrays, concatenated in the specified order. + * This is useful for building constant arrays out of other constant arrays. + *

+ * Arrays.concatenate(Object[]... arrays) + */ + public static E[] concatenate(E[]... arrays) { + int len = 0; + for (E[] array : arrays) { + len += array.length; + } + E[] result = newArray(arrays[0], len); + if (len == 0) { + return result; + } + int current = 0; + for (E[] array : arrays) { + int arrayLength = array.length; + if (arrayLength > 0) { + System.arraycopy(array, 0, result, current, arrayLength); + current += arrayLength; + } + } + return result; + } + + /** + * Return an array containing all the elements in all the + * specified arrays, concatenated in the specified order. + * This is useful for building constant arrays out other constant arrays. + *

+ * Arrays.concatenate(char[]... arrays) + */ + public static char[] concatenate(char[]... arrays) { + int len = 0; + for (char[] array : arrays) { + len += array.length; + } + if (len == 0) { + return EMPTY_CHAR_ARRAY; + } + char[] result = new char[len]; + int current = 0; + for (char[] array : arrays) { + int arrayLength = array.length; + if (arrayLength != 0) { + System.arraycopy(array, 0, result, current, arrayLength); + current += arrayLength; + } + } + return result; + } + + /** + * Return an array containing all the elements in all the + * specified arrays, concatenated in the specified order. + * This is useful for building constant arrays out other constant arrays. + *

+ * Arrays.concatenate(int[]... arrays) + */ + public static int[] concatenate(int[]... arrays) { + int len = 0; + for (int[] array : arrays) { + len += array.length; + } + if (len == 0) { + return EMPTY_INT_ARRAY; + } + int[] result = new int[len]; + int current = 0; + for (int[] array : arrays) { + int arrayLength = array.length; + if (arrayLength != 0) { + System.arraycopy(array, 0, result, current, arrayLength); + current += arrayLength; + } + } + return result; + } + + + // ********** contains ********** + + /** + * Return whether the specified array contains the + * specified element. + *

+ * Arrays.contains(Object[] array, Object o) + */ + public static boolean contains(Object[] array, Object value) { + return contains(array, value, array.length); + } + + /** + * check array length + */ + private static boolean contains(Object[] array, Object value, int arrayLength) { + return (arrayLength == 0) ? false : contains_(array, value, arrayLength); + } + + /** + * assume array length > 0 + */ + public static boolean contains_(Object[] array, Object value, int arrayLength) { + if (value == null) { + for (int i = arrayLength; i-- > 0; ) { + if (array[i] == null) { + return true; + } + } + } else { + for (int i = arrayLength; i-- > 0; ) { + if (value.equals(array[i])) { + return true; + } + } + } + return false; + } + + /** + * Return whether the specified array contains the + * specified element. + *

+ * Arrays.contains(char[] array, char value) + */ + public static boolean contains(char[] array, char value) { + return contains(array, value, array.length); + } + + /** + * check array length + */ + private static boolean contains(char[] array, char value, int arrayLength) { + return (arrayLength == 0) ? false : contains_(array, value, arrayLength); + } + + /** + * assume array length > 0 + */ + private static boolean contains_(char[] array, char value, int arrayLength) { + for (int i = arrayLength; i-- > 0; ) { + if (array[i] == value) { + return true; + } + } + return false; + } + + /** + * Return whether the specified array contains the + * specified element. + *

+ * Arrays.contains(int[] array, int value) + */ + public static boolean contains(int[] array, int value) { + return contains(array, value, array.length); + } + + /** + * check array length + */ + private static boolean contains(int[] array, int value, int arrayLength) { + return (arrayLength == 0) ? false : contains_(array, value, arrayLength); + } + + /** + * assume array length > 0 + */ + private static boolean contains_(int[] array, int value, int arrayLength) { + for (int i = arrayLength; i-- > 0; ) { + if (array[i] == value) { + return true; + } + } + return false; + } + + + // ********** contains all ********** + + /** + * Return whether the specified array contains all of the + * elements in the specified collection. + *

+ * Arrays.containsAll(Object[] array, Collection collection) + */ + public static boolean containsAll(Object[] array, Collection collection) { + return containsAll(array, collection.iterator()); + } + + /** + * Return whether the specified array contains all of the + * elements in the specified iterable. + *

+ * Arrays.containsAll(Object[] array, Iterable iterable) + */ + public static boolean containsAll(Object[] array, Iterable iterable) { + return containsAll(array, iterable.iterator()); + } + + /** + * Return whether the specified array contains all of the + * elements in the specified iterator. + *

+ * Arrays.containsAll(Object[] array, Iterator iterator) + */ + public static boolean containsAll(Object[] array, Iterator iterator) { + // use hashed lookup + HashSet set = CollectionTools.set(array); + while (iterator.hasNext()) { + if ( ! set.contains(iterator.next())) { + return false; + } + } + return true; + } + + /** + * Return whether the specified array 1 contains all of the + * elements in the specified array 2. + *

+ * Arrays.containsAll(Object[] array1, Object[] array2) + */ + public static boolean containsAll(Object[] array1, Object... array2) { + // use hashed lookup + HashSet set = CollectionTools.set(array1); + for (int i = array2.length; i-- > 0; ) { + if ( ! set.contains(array2[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified array 1 contains all of the + * elements in the specified array 2. + *

+ * Arrays.containsAll(char[] array1, char[] array2) + */ + public static boolean containsAll(char[] array1, char... array2) { + for (int i = array2.length; i-- > 0; ) { + if ( ! contains(array1, array2[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified array 1 contains all of the + * elements in the specified array 2. + *

+ * Arrays.containsAll(int[] array1, int[] array2) + */ + public static boolean containsAll(int[] array1, int... array2) { + for (int i = array2.length; i-- > 0; ) { + if ( ! contains(array1, array2[i])) { + return false; + } + } + return true; + } + + + // ********** diff ********** + + /** + * Return the index of the first elements in the specified + * arrays that are different, beginning at the end. + * If the arrays are identical, return -1. + * If the arrays are different sizes, return the index of the + * last element in the longer array. + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + */ + public static int diffEnd(Object[] array1, Object[] array2) { + int len1 = array1.length; + int len2 = array2.length; + if (len1 != len2) { + return Math.max(len1, len2) - 1; + } + for (int i = len1 - 1; i > -1; i--) { + Object o = array1[i]; + if (o == null) { + if (array2[i] != null) { + return i; + } + } else { + if ( ! o.equals(array2[i])) { + return i; + } + } + } + return -1; + } + + /** + * Return the range of elements in the specified + * arrays that are different. + * If the arrays are identical, return [size, -1]. + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + * @see #diffStart(Object[], Object[]) + * @see #diffEnd(Object[], Object[]) + */ + public static Range diffRange(Object[] array1, Object[] array2) { + int end = diffEnd(array1, array2); + if (end == -1) { + // the lists are identical, the start is the size of the two lists + return new Range(array1.length, end); + } + // the lists are different, calculate the start of the range + return new Range(diffStart(array1, array2), end); + } + + /** + * Return the index of the first elements in the specified + * arrays that are different. If the arrays are identical, return + * the size of the two arrays (i.e. one past the last index). + * If the arrays are different sizes and all the elements in + * the shorter array match their corresponding elements in + * the longer array, return the size of the shorter array + * (i.e. one past the last index of the shorter array). + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + */ + public static int diffStart(Object[] array1, Object[] array2) { + int end = Math.min(array1.length, array2.length); + for (int i = 0; i < end; i++) { + Object o = array1[i]; + if (o == null) { + if (array2[i] != null) { + return i; + } + } else { + if ( ! o.equals(array2[i])) { + return i; + } + } + } + return end; + } + + + // ********** identity diff ********** + + /** + * Return the index of the first elements in the specified + * arrays that are different, beginning at the end. + * If the arrays are identical, return -1. + * If the arrays are different sizes, return the index of the + * last element in the longer array. + * Use object identity to compare the elements. + */ + public static int identityDiffEnd(Object[] array1, Object[] array2) { + int len1 = array1.length; + int len2 = array2.length; + if (len1 != len2) { + return Math.max(len1, len2) - 1; + } + for (int i = len1 - 1; i > -1; i--) { + if (array1[i] != array2[i]) { + return i; + } + } + return -1; + } + + /** + * Return the range of elements in the specified + * arrays that are different. + * If the arrays are identical, return [size, -1]. + * Use object identity to compare the elements. + * @see #identityDiffStart(Object[], Object[]) + * @see #identityDiffEnd(Object[], Object[]) + */ + public static Range identityDiffRange(Object[] array1, Object[] array2) { + int end = identityDiffEnd(array1, array2); + if (end == -1) { + // the lists are identical, the start is the size of the two lists + return new Range(array1.length, end); + } + // the lists are different, calculate the start of the range + return new Range(identityDiffStart(array1, array2), end); + } + + /** + * Return the index of the first elements in the specified + * arrays that are different. If the arrays are identical, return + * the size of the two arrays (i.e. one past the last index). + * If the arrays are different sizes and all the elements in + * the shorter array match their corresponding elements in + * the longer array, return the size of the shorter array + * (i.e. one past the last index of the shorter array). + * Use object identity to compare the elements. + */ + public static int identityDiffStart(Object[] array1, Object[] array2) { + int end = Math.min(array1.length, array2.length); + for (int i = 0; i < end; i++) { + if (array1[i] != array2[i]) { + return i; + } + } + return end; + } + + + // ********** elements are identical ********** + + /** + * Return whether the specified arrays contain the same elements. + *

+ * Arrays.identical(Object[] array1, Object[] array2) + */ + public static boolean elementsAreIdentical(Object[] array1, Object[] array2) { + if (array1 == array2) { + return true; + } + if (array1 == null || array2 == null) { + return false; + } + int length = array1.length; + if (array2.length != length) { + return false; + } + for (int i = length; i-- > 0; ) { + if (array1[i] != array2[i]) { + return false; + } + } + return true; + } + + + // ********** index of ********** + + /** + * Return the index of the first occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.indexOf(Object[] array, Object o) + */ + public static int indexOf(Object[] array, Object value) { + int len = array.length; + if (value == null) { + for (int i = 0; i < len; i++) { + if (array[i] == null) { + return i; + } + } + } else { + for (int i = 0; i < len; i++) { + if (value.equals(array[i])) { + return i; + } + } + } + return -1; + } + + /** + * Return the index of the first occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.identityIndexOf(Object[] array, Object o) + */ + public static int identityIndexOf(Object[] array, Object value) { + int len = array.length; + for (int i = 0; i < len; i++) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + /** + * Return the index of the first occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.indexOf(char[] array, char value) + */ + public static int indexOf(char[] array, char value) { + int len = array.length; + for (int i = 0; i < len; i++) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + /** + * Return the index of the first occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.indexOf(int[] array, int value) + */ + public static int indexOf(int[] array, int value) { + int len = array.length; + for (int i = 0; i < len; i++) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + + // ********** insertion index of ********** + + /** + * Return the maximum index of where the specified comparable object + * should be inserted into the specified sorted array and still keep + * the array sorted. + */ + public static > int insertionIndexOf(E[] sortedArray, Comparable value) { + int len = sortedArray.length; + for (int i = 0; i < len; i++) { + if (value.compareTo(sortedArray[i]) < 0) { + return i; + } + } + return len; + } + + /** + * Return the maximum index of where the specified comparable object + * should be inserted into the specified sorted array and still keep + * the array sorted. + */ + public static int insertionIndexOf(E[] sortedArray, E value, Comparator comparator) { + int len = sortedArray.length; + for (int i = 0; i < len; i++) { + if (comparator.compare(value, sortedArray[i]) < 0) { + return i; + } + } + return len; + } + + + // ********** last index of ********** + + /** + * Return the index of the last occurrence of the + * specified element in the specified array; + * return -1 if there is no such index. + *

+ * Arrays.lastIndexOf(Object[] array, Object o) + */ + public static int lastIndexOf(Object[] array, Object value) { + int len = array.length; + if (value == null) { + for (int i = len; i-- > 0; ) { + if (array[i] == null) { + return i; + } + } + } else { + for (int i = len; i-- > 0; ) { + if (value.equals(array[i])) { + return i; + } + } + } + return -1; + } + + /** + * Return the index of the last occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.lastIndexOf(char[] array, char value) + */ + public static int lastIndexOf(char[] array, char value) { + for (int i = array.length; i-- > 0; ) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + /** + * Return the index of the last occurrence of the + * specified element in the specified array, + * or return -1 if there is no such index. + *

+ * Arrays.lastIndexOf(int[] array, int value) + */ + public static int lastIndexOf(int[] array, int value) { + for (int i = array.length; i-- > 0; ) { + if (array[i] == value) { + return i; + } + } + return -1; + } + + + // ********** min/max ********** + + /** + * Return the character from the specified array with the minimum value. + *

+ * Arrays.min(char[] array) + */ + public static char min(char... array) { + int len = array.length; + if (len == 0) { + throw new IndexOutOfBoundsException(); + } + int last = len - 1; + char min = array[last]; + for (int i = last; i-- > 0; ) { + char c = array[i]; + if (c < min) { + min = c; + } + } + return min; + } + + /** + * Return the integer from the specified array with the minimum value. + *

+ * Arrays.min(int[] array) + */ + public static int min(int... array) { + int len = array.length; + if (len == 0) { + throw new IndexOutOfBoundsException(); + } + int last = len - 1; + int min = array[last]; + for (int i = last; i-- > 0; ) { + int x = array[i]; + if (x < min) { + min = x; + } + } + return min; + } + + /** + * Return the character from the specified array with the maximum value. + *

+ * Arrays.max(char[] array) + */ + public static char max(char... array) { + int len = array.length; + if (len == 0) { + throw new IndexOutOfBoundsException(); + } + int last = len - 1; + char max = array[last]; + for (int i = last; i-- > 0; ) { + char c = array[i]; + if (c > max) { + max = c; + } + } + return max; + } + + /** + * Return the integer from the specified array with the maximum value. + *

+ * Arrays.max(int[] array) + */ + public static int max(int... array) { + int len = array.length; + if (len == 0) { + throw new IndexOutOfBoundsException(); + } + int last = len - 1; + int max = array[last]; + for (int i = last; i-- > 0; ) { + int x = array[i]; + if (x > max) { + max = x; + } + } + return max; + } + + + // ********** move ********** + + /** + * Move an element from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(Object[] array, int targetIndex, int sourceIndex) + */ + public static E[] move(E[] array, int targetIndex, int sourceIndex) { + return (targetIndex == sourceIndex) ? array : move_(array, targetIndex, sourceIndex); + } + + /** + * assume target index != source index + */ + private static E[] move_(E[] array, int targetIndex, int sourceIndex) { + E temp = array[sourceIndex]; + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + 1, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + 1, array, sourceIndex, targetIndex - sourceIndex); + } + array[targetIndex] = temp; + return array; + } + + /** + * Move elements from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(Object[] array, int targetIndex, int sourceIndex, int length) + */ + public static E[] move(E[] array, int targetIndex, int sourceIndex, int length) { + if ((targetIndex == sourceIndex) || (length == 0)) { + return array; + } + if (length == 1) { + return move_(array, targetIndex, sourceIndex); + } + E[] temp = newArray(array, length); + System.arraycopy(array, sourceIndex, temp, 0, length); + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + length, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + length, array, sourceIndex, targetIndex - sourceIndex); + } + System.arraycopy(temp, 0, array, targetIndex, length); + return array; + } + + /** + * Move an element from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(int[] array, int targetIndex, int sourceIndex) + */ + public static int[] move(int[] array, int targetIndex, int sourceIndex) { + return (targetIndex == sourceIndex) ? array : move_(array, targetIndex, sourceIndex); + } + + /** + * assume targetIndex != sourceIndex + */ + private static int[] move_(int[] array, int targetIndex, int sourceIndex) { + int temp = array[sourceIndex]; + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + 1, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + 1, array, sourceIndex, targetIndex - sourceIndex); + } + array[targetIndex] = temp; + return array; + } + + /** + * Move elements from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(int[] array, int targetIndex, int sourceIndex, int length) + */ + public static int[] move(int[] array, int targetIndex, int sourceIndex, int length) { + if ((targetIndex == sourceIndex) || (length == 0)) { + return array; + } + if (length == 1) { + return move_(array, targetIndex, sourceIndex); + } + int[] temp = new int[length]; + System.arraycopy(array, sourceIndex, temp, 0, length); + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + length, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + length, array, sourceIndex, targetIndex - sourceIndex); + } + System.arraycopy(temp, 0, array, targetIndex, length); + return array; + } + + /** + * Move an element from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(char[] array, int targetIndex, int sourceIndex) + */ + public static char[] move(char[] array, int targetIndex, int sourceIndex) { + return (targetIndex == sourceIndex) ? array : move_(array, targetIndex, sourceIndex); + } + + /** + * assume targetIndex != sourceIndex + */ + private static char[] move_(char[] array, int targetIndex, int sourceIndex) { + char temp = array[sourceIndex]; + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + 1, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + 1, array, sourceIndex, targetIndex - sourceIndex); + } + array[targetIndex] = temp; + return array; + } + + /** + * Move elements from the specified source index to the specified target + * index. Return the altered array. + *

+ * Arrays.move(char[] array, int targetIndex, int sourceIndex, int length) + */ + public static char[] move(char[] array, int targetIndex, int sourceIndex, int length) { + if ((targetIndex == sourceIndex) || (length == 0)) { + return array; + } + if (length == 1) { + return move_(array, targetIndex, sourceIndex); + } + char[] temp = new char[length]; + System.arraycopy(array, sourceIndex, temp, 0, length); + if (targetIndex < sourceIndex) { + System.arraycopy(array, targetIndex, array, targetIndex + length, sourceIndex - targetIndex); + } else { + System.arraycopy(array, sourceIndex + length, array, sourceIndex, targetIndex - sourceIndex); + } + System.arraycopy(temp, 0, array, targetIndex, length); + return array; + } + + + // ********** remove ********** + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.remove(Object[] array, Object value) + */ + public static E[] remove(E[] array, Object value) { + return removeElementAtIndex(array, indexOf(array, value)); + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.remove(char[] array, char value) + */ + public static char[] remove(char[] array, char value) { + return removeElementAtIndex(array, indexOf(array, value)); + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.remove(int[] array, int value) + */ + public static int[] remove(int[] array, int value) { + return removeElementAtIndex(array, indexOf(array, value)); + } + + /** + * Return a new array that contains the elements in the + * specified array with the first element removed. + *

+ * Arrays.removeFirst(Object[] array) + */ + public static E[] removeFirst(E[] array) { + return removeElementAtIndex(array, 0); + } + + /** + * Return a new array that contains the elements in the + * specified array with the first element removed. + *

+ * Arrays.removeFirst(char[] array) + */ + public static char[] removeFirst(char[] array) { + return removeElementAtIndex(array, 0); + } + + /** + * Return a new array that contains the elements in the + * specified array with the first element removed. + *

+ * Arrays.removeFirst(int[] array) + */ + public static int[] removeFirst(int[] array) { + return removeElementAtIndex(array, 0); + } + + /** + * Return a new array that contains the elements in the + * specified array with the last element removed. + *

+ * Arrays.removeLast(Object[] array) + */ + public static E[] removeLast(E[] array) { + return removeElementAtIndex(array, array.length - 1); + } + + /** + * Return a new array that contains the elements in the + * specified array with the last element removed. + *

+ * Arrays.removeLast(char[] array) + */ + public static char[] removeLast(char[] array) { + return removeElementAtIndex(array, array.length - 1); + } + + /** + * Return a new array that contains the elements in the + * specified array with the last element removed. + *

+ * Arrays.removeLast(int[] array) + */ + public static int[] removeLast(int[] array) { + return removeElementAtIndex(array, array.length - 1); + } + + + // ********** remove all ********** + + /** + * Remove from the specified array all the elements in + * the specified iterable and return the result. + *

+ * Arrays.removeAll(Object[] array, Iterable iterable) + */ + public static E[] removeAll(E[] array, Iterable iterable) { + return removeAll(array, iterable.iterator()); + } + + /** + * Remove from the specified array all the elements in + * the specified iterable and return the result. + * The specified iterable size is a performance hint. + *

+ * Arrays.removeAll(Object[] array, Iterable iterable) + */ + public static E[] removeAll(E[] array, Iterable iterable, int iterableSize) { + return removeAll(array, iterable.iterator(), iterableSize); + } + + /** + * Remove from the specified array all the elements in + * the specified iterator and return the result. + *

+ * Arrays.removeAll(Object[] array, Iterator iterator) + */ + public static E[] removeAll(E[] array, Iterator iterator) { + // convert to a set to take advantage of hashed look-up + return iterator.hasNext() ? removeAll_(array, CollectionTools.set(iterator)) : array; + } + + /** + * Remove from the specified array all the elements in + * the specified iterator and return the result. + * The specified iterator size is a performance hint. + *

+ * Arrays.removeAll(Object[] array, Iterator iterator) + */ + public static E[] removeAll(E[] array, Iterator iterator, int iteratorSize) { + // convert to a set to take advantage of hashed look-up + return iterator.hasNext() ? removeAll_(array, CollectionTools.set(iterator, iteratorSize)) : array; + } + + /** + * Remove from the specified array all the elements in + * the specified collection and return the result. + *

+ * Arrays.removeAll(Object[] array, Collection collection) + */ + public static E[] removeAll(E[] array, Collection collection) { + return collection.isEmpty() ? array : removeAll_(array, collection); + } + + /** + * assume collection is non-empty + */ + private static E[] removeAll_(E[] array, Collection collection) { + return removeAll(array, collection, array.length); + } + + /** + * assume collection is non-empty; check array length + */ + private static E[] removeAll(E[] array, Collection collection, int arrayLength) { + return (arrayLength == 0) ? array : removeAll_(array, collection, arrayLength); + } + + /** + * assume collection is non-empty and array length > 0 + */ + private static E[] removeAll_(E[] array, Collection collection, int arrayLength) { + // build the indices of the elements that are to remain + int[] indices = new int[arrayLength]; + int j = 0; + for (int i = 0; i < arrayLength; i++) { + if ( ! collection.contains(array[i])) { + indices[j++] = i; + } + } + if (j == arrayLength) { + return array; // nothing was removed + } + E[] result = newArray(array, j); + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array[indices[i]]; + } + return result; + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays.removeAll(Object[] array1, Object[] array2) + */ + public static E[] removeAll(E[] array1, Object... array2) { + // convert to a set to take advantage of hashed look-up + return (array2.length == 0) ? array1 : removeAll_(array1, CollectionTools.set(array2)); + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays#removeAll(char[] array1, char[] array2) + */ + public static char[] removeAll(char[] array1, char... array2) { + if (array2.length == 0) { + return array1; + } + int array1Length = array1.length; + if (array1Length == 0) { + return array1; + } + int[] indices = new int[array1Length]; + int j = 0; + for (int i = 0; i < array1Length; i++) { + if ( ! contains(array2, array1[i])) { + indices[j++] = i; + } + } + if (j == array1Length) { + return array1; // nothing was removed + } + char[] result = new char[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array1[indices[i]]; + } + return result; + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays#removeAll(int[] array1, int[] array2) + */ + public static int[] removeAll(int[] array1, int... array2) { + if (array2.length == 0) { + return array1; + } + int array1Length = array1.length; + if (array1Length == 0) { + return array1; + } + int[] indices = new int[array1Length]; + int j = 0; + for (int i = 0; i < array1Length; i++) { + if ( ! contains(array2, array1[i])) { + indices[j++] = i; + } + } + if (j == array1Length) { + return array1; // nothing was removed + } + int[] result = new int[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array1[indices[i]]; + } + return result; + } + + + // ********** remove all occurrences ********** + + /** + * Remove from the specified array all occurrences of + * the specified element and return the result. + *

+ * Arrays.removeAllOccurrences(Object[] array, Object value) + */ + public static E[] removeAllOccurrences(E[] array, Object value) { + int arrayLength = array.length; + if (arrayLength == 0) { + return array; + } + int[] indices = new int[arrayLength]; + int j = 0; + if (value == null) { + for (int i = arrayLength; i-- > 0; ) { + if (array[i] != null) { + indices[j++] = i; + } + } + } else { + for (int i = array.length; i-- > 0; ) { + if ( ! value.equals(array[i])) { + indices[j++] = i; + } + } + } + if (j == arrayLength) { + return array; // nothing was removed + } + E[] result = newArray(array, j); + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array[indices[i]]; + } + return result; + } + + /** + * Remove from the specified array all occurrences of + * the specified element and return the result. + *

+ * Arrays.removeAllOccurrences(char[] array, char value) + */ + public static char[] removeAllOccurrences(char[] array, char value) { + int arrayLength = array.length; + if (arrayLength == 0) { + return array; + } + int[] indices = new int[arrayLength]; + int j = 0; + for (int i = arrayLength; i-- > 0; ) { + if (array[i] != value) { + indices[j++] = i; + } + } + if (j == arrayLength) { + return array; // nothing was removed + } + char[] result = new char[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array[indices[i]]; + } + return result; + } + + /** + * Remove from the specified array all occurrences of + * the specified element and return the result. + *

+ * Arrays.removeAllOccurrences(int[] array, int value) + */ + public static int[] removeAllOccurrences(int[] array, int value) { + int arrayLength = array.length; + if (arrayLength == 0) { + return array; + } + int[] indices = new int[arrayLength]; + int j = 0; + for (int i = arrayLength; i-- > 0; ) { + if (array[i] != value) { + indices[j++] = i; + } + } + if (j == arrayLength) { + return array; // nothing was removed + } + int[] result = new int[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array[indices[i]]; + } + return result; + } + + + // ********** remove duplicate elements ********** + + /** + * Remove any duplicate elements from the specified array, + * while maintaining the order. + */ + public static E[] removeDuplicateElements(E... array) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + ArrayList temp = CollectionTools.list(array); + return CollectionTools.removeDuplicateElements(temp, len) ? + temp.toArray(newArray(array, temp.size())) : + array; + } + + + // ********** remove element at index ********** + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.removeElementAtIndex(Object[] array, int index) + */ + public static E[] removeElementAtIndex(E[] array, int index) { + return removeElementsAtIndex(array, index, 1); + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.removeElementAtIndex(char[] array, int index) + */ + public static char[] removeElementAtIndex(char[] array, int index) { + return removeElementsAtIndex(array, index, 1); + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified element removed. + *

+ * Arrays.removeElementAtIndex(int[] array, int index) + */ + public static int[] removeElementAtIndex(int[] array, int index) { + return removeElementsAtIndex(array, index, 1); + } + + + // ********** remove elements at index ********** + + /** + * Return a new array that contains the elements in the + * specified array with the specified elements removed. + *

+ * Arrays.removeElementsAtIndex(Object[] array, int index, int length) + */ + public static E[] removeElementsAtIndex(E[] array, int index, int length) { + if (length == 0) { + return array; + } + int arrayLength = array.length; + int newLength = arrayLength - length; + E[] result = newArray(array, newLength); + if ((newLength == 0) && (index == 0)) { + return result; // performance tweak + } + if (index != 0) { + System.arraycopy(array, 0, result, 0, index); + } + int length2 = newLength - index; + if (length2 != 0) { + System.arraycopy(array, index + length, result, index, length2); + } + return result; + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified elements removed. + *

+ * Arrays.removeElementsAtIndex(char[] array, int index, int length) + */ + public static char[] removeElementsAtIndex(char[] array, int index, int length) { + if (length == 0) { + return array; + } + int arrayLength = array.length; + int newLength = arrayLength - length; + if ((newLength == 0) && (index == 0)) { + return EMPTY_CHAR_ARRAY; // performance tweak + } + char[] result = new char[newLength]; + if (index != 0) { + System.arraycopy(array, 0, result, 0, index); + } + int length2 = newLength - index; + if (length2 != 0) { + System.arraycopy(array, index + length, result, index, length2); + } + return result; + } + + /** + * Return a new array that contains the elements in the + * specified array with the specified elements removed. + *

+ * Arrays.removeElementsAtIndex(int[] array, int index, int length) + */ + public static int[] removeElementsAtIndex(int[] array, int index, int length) { + if (length == 0) { + return array; + } + int arrayLength = array.length; + int newLength = arrayLength - length; + if ((newLength == 0) && (index == 0)) { + return EMPTY_INT_ARRAY; // performance tweak + } + int[] result = new int[newLength]; + if (index != 0) { + System.arraycopy(array, 0, result, 0, index); + } + int length2 = newLength - index; + if (length2 != 0) { + System.arraycopy(array, index + length, result, index, length2); + } + return result; + } + + + // ********** replace all ********** + + /** + * Replace all occurrences of the specified old value with + * the specified new value. Return the altered array. + *

+ * Arrays.replaceAll(Object[] array, Object oldValue, Object newValue) + */ + public static E[] replaceAll(E[] array, Object oldValue, E newValue) { + if (oldValue == null) { + for (int i = array.length; i-- > 0; ) { + if (array[i] == null) { + array[i] = newValue; + } + } + } else { + for (int i = array.length; i-- > 0; ) { + if (oldValue.equals(array[i])) { + array[i] = newValue; + } + } + } + return array; + } + + /** + * Replace all occurrences of the specified old value with + * the specified new value. Return the altered array. + *

+ * Arrays.replaceAll(int[] array, int oldValue, int newValue) + */ + public static int[] replaceAll(int[] array, int oldValue, int newValue) { + for (int i = array.length; i-- > 0; ) { + if (array[i] == oldValue) { + array[i] = newValue; + } + } + return array; + } + + /** + * Replace all occurrences of the specified old value with + * the specified new value. Return the altered array. + *

+ * Arrays.replaceAll(char[] array, char oldValue, char newValue) + */ + public static char[] replaceAll(char[] array, char oldValue, char newValue) { + for (int i = array.length; i-- > 0; ) { + if (array[i] == oldValue) { + array[i] = newValue; + } + } + return array; + } + + + // ********** retain all ********** + + /** + * Retain in the specified array all the elements in + * the specified iterable and return the result. + *

+ * Arrays.retainAll(Object[] array, Iterable iterable) + */ + public static E[] retainAll(E[] array, Iterable iterable) { + int arrayLength = array.length; + return (arrayLength == 0) ? array : retainAll(array, arrayLength, iterable.iterator()); + } + + /** + * Retain in the specified array all the elements in + * the specified iterable and return the result. + * The specified iterable size is a performance hint. + *

+ * Arrays.retainAll(Object[] array, Iterable iterable) + */ + public static E[] retainAll(E[] array, Iterable iterable, int iterableSize) { + int arrayLength = array.length; + return (arrayLength == 0) ? array : retainAll(array, arrayLength, iterable.iterator(), iterableSize); + } + + /** + * Retain in the specified array all the elements in + * the specified iterator and return the result. + *

+ * Arrays.retainAll(Object[] array, Iterator iterator) + */ + public static E[] retainAll(E[] array, Iterator iterator) { + int arrayLength = array.length; + return (arrayLength == 0) ? array : retainAll(array, arrayLength, iterator); + } + + /** + * Retain in the specified array all the elements in + * the specified iterator and return the result. + * The specified iterator size is a performance hint. + *

+ * Arrays.retainAll(Object[] array, Iterator iterator) + */ + public static E[] retainAll(E[] array, Iterator iterator, int iteratorSize) { + int arrayLength = array.length; + return (arrayLength == 0) ? array : retainAll(array, arrayLength, iterator, iteratorSize); + } + + /** + * assume array length > 0 + */ + private static E[] retainAll(E[] array, int arrayLength, Iterator iterator) { + return iterator.hasNext() ? + retainAll_(array, CollectionTools.set(iterator), arrayLength) : + newArray(array, 0); + } + + /** + * assume array length > 0 + */ + private static E[] retainAll(E[] array, int arrayLength, Iterator iterator, int iteratorSize) { + return iterator.hasNext() ? + retainAll_(array, CollectionTools.set(iterator, iteratorSize), arrayLength) : + newArray(array, 0); + } + + /** + * Retain in the specified array all the elements in + * the specified collection and return the result. + *

+ * Arrays.retainAll(Object[] array, Collection collection) + */ + public static E[] retainAll(E[] array, Collection collection) { + int arrayLength = array.length; + return (arrayLength == 0) ? array : retainAll(array, collection, arrayLength); + } + + /** + * assume array length > 0 + */ + private static E[] retainAll(E[] array, Collection collection, int arrayLength) { + return collection.isEmpty() ? + newArray(array, 0) : + retainAll_(array, collection, arrayLength); + } + + /** + * assume collection is non-empty and array length > 0 + */ + private static E[] retainAll_(E[] array, Collection collection, int arrayLength) { + int[] indices = new int[arrayLength]; + int j = 0; + for (int i = 0; i < arrayLength; i++) { + if (collection.contains(array[i])) { + indices[j++] = i; + } + } + if (j == arrayLength) { + return array; // everything was retained + } + E[] result = newArray(array, j); + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array[indices[i]]; + } + return result; + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays.retainAll(Object[] array1, Object[] array2) + */ + public static E[] retainAll(E[] array1, Object[] array2) { + int array1Length = array1.length; + return (array1Length == 0) ? + array1 : + (array2.length == 0) ? + newArray(array1, 0) : + retainAll(array1, CollectionTools.set(array2), array1Length); + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays.retainAll(char[] array1, char[] array2) + */ + public static char[] retainAll(char[] array1, char... array2) { + int array1Length = array1.length; + return (array1Length == 0) ? array1 : retainAll(array1, array2, array1Length); + } + + /** + * assume array 1 length > 0 + */ + private static char[] retainAll(char[] array1, char[] array2, int array1Length) { + int array2Length = array2.length; + return (array2Length == 0) ? EMPTY_CHAR_ARRAY : retainAll(array1, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 + */ + private static char[] retainAll(char[] array1, char[] array2, int array1Length, int array2Length) { + int[] indices = new int[array1Length]; + int j = 0; + for (int i = 0; i < array1Length; i++) { + if (contains_(array2, array1[i], array2Length)) { + indices[j++] = i; + } + } + if (j == array1Length) { + return array1; // everything was retained + } + char[] result = new char[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array1[indices[i]]; + } + return result; + } + + /** + * Remove from the first specified array all the elements in + * the second specified array and return the result. + *

+ * Arrays.retainAll(int[] array1, int[] array2) + */ + public static int[] retainAll(int[] array1, int... array2) { + int array1Length = array1.length; + return (array1Length == 0) ? array1 : retainAll(array1, array2, array1Length); + } + + /** + * assume array 1 length > 0 + */ + private static int[] retainAll(int[] array1, int[] array2, int array1Length) { + int array2Length = array2.length; + return (array2Length == 0) ? EMPTY_INT_ARRAY : retainAll(array1, array2, array1Length, array2Length); + } + + /** + * assume both array lengths > 0 + */ + private static int[] retainAll(int[] array1, int[] array2, int array1Length, int array2Length) { + int[] indices = new int[array1Length]; + int j = 0; + for (int i = 0; i < array1Length; i++) { + if (contains_(array2, array1[i], array2Length)) { + indices[j++] = i; + } + } + if (j == array1Length) { + return array1; // everything was retained + } + int[] result = new int[j]; + int resultLength = result.length; + for (int i = 0; i < resultLength; i++) { + result[i] = array1[indices[i]]; + } + return result; + } + + + // ********** reverse ********** + + /** + * Return the array, reversed. + *

+ * Arrays.reverse(Object... array) + */ + public static E[] reverse(E... array) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = 0, mid = len >> 1, j = len - 1; i < mid; i++, j--) { + swap(array, i, j); + } + return array; + } + + /** + * Return the array, reversed. + *

+ * Arrays.reverse(char... array) + */ + public static char[] reverse(char... array) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = 0, mid = len >> 1, j = len - 1; i < mid; i++, j--) { + swap(array, i, j); + } + return array; + } + + /** + * Return the array, reversed. + *

+ * Arrays.reverse(int... array) + */ + public static int[] reverse(int... array) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = 0, mid = len >> 1, j = len - 1; i < mid; i++, j--) { + swap(array, i, j); + } + return array; + } + + + // ********** rotate ********** + + /** + * Return the rotated array after rotating it one position. + *

+ * Arrays.rotate(Object[] array) + */ + public static E[] rotate(E... array) { + return rotate(array, 1); + } + + /** + * Return the rotated array after rotating it the specified distance. + *

+ * Arrays.rotate(Object[] array, int distance) + */ + public static E[] rotate(E[] array, int distance) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + distance = distance % len; + if (distance < 0) { + distance += len; + } + if (distance == 0) { + return array; + } + for (int cycleStart = 0, nMoved = 0; nMoved != len; cycleStart++) { + E displaced = array[cycleStart]; + int i = cycleStart; + do { + i += distance; + if (i >= len) { + i -= len; + } + E temp = array[i]; + array[i] = displaced; + displaced = temp; + nMoved ++; + } while (i != cycleStart); + } + return array; + } + + /** + * Return the rotated array after rotating it one position. + *

+ * Arrays.rotate(char[] array) + */ + public static char[] rotate(char... array) { + return rotate(array, 1); + } + + /** + * Return the rotated array after rotating it the specified distance. + *

+ * Arrays.rotate(char[] array, int distance) + */ + public static char[] rotate(char[] array, int distance) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + distance = distance % len; + if (distance < 0) { + distance += len; + } + if (distance == 0) { + return array; + } + for (int cycleStart = 0, nMoved = 0; nMoved != len; cycleStart++) { + char displaced = array[cycleStart]; + int i = cycleStart; + do { + i += distance; + if (i >= len) { + i -= len; + } + char temp = array[i]; + array[i] = displaced; + displaced = temp; + nMoved ++; + } while (i != cycleStart); + } + return array; + } + + /** + * Return the rotated array after rotating it one position. + *

+ * Arrays.rotate(int[] array) + */ + public static int[] rotate(int... array) { + return rotate(array, 1); + } + + /** + * Return the rotated array after rotating it the specified distance. + *

+ * Arrays.rotate(int[] array, int distance) + */ + public static int[] rotate(int[] array, int distance) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + distance = distance % len; + if (distance < 0) { + distance += len; + } + if (distance == 0) { + return array; + } + for (int cycleStart = 0, nMoved = 0; nMoved != len; cycleStart++) { + int displaced = array[cycleStart]; + int i = cycleStart; + do { + i += distance; + if (i >= len) { + i -= len; + } + int temp = array[i]; + array[i] = displaced; + displaced = temp; + nMoved ++; + } while (i != cycleStart); + } + return array; + } + + + // ********** shuffle ********** + + private static final Random RANDOM = new Random(); + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(Object... array) + */ + public static E[] shuffle(E... array) { + return shuffle(array, RANDOM); + } + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(Object[] array, Random r) + */ + public static E[] shuffle(E[] array, Random random) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = len; i-- > 0; ) { + swap(array, i, random.nextInt(len)); + } + return array; + } + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(char... array) + */ + public static char[] shuffle(char... array) { + return shuffle(array, RANDOM); + } + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(char[] array, Random r) + */ + public static char[] shuffle(char[] array, Random random) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = len; i-- > 0; ) { + swap(array, i, random.nextInt(len)); + } + return array; + } + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(int... array) + */ + public static int[] shuffle(int... array) { + return shuffle(array, RANDOM); + } + + /** + * Return the array after "shuffling" it. + *

+ * Arrays.shuffle(int[] array, Random r) + */ + public static int[] shuffle(int[] array, Random random) { + int len = array.length; + if ((len == 0) || (len == 1)) { + return array; + } + for (int i = len; i-- > 0; ) { + swap(array, i, random.nextInt(len)); + } + return array; + } + + + // ********** sub-array ********** + + /** + * Return a sub-array of the specified array with elements copied from + * the specified range. The "from" index is inclusive; the "to" index is exclusive. + *

+ * Arrays.subArray(E[] array, int fromIndex, int toIndex) + */ + public static E[] subArray(E[] array, int fromIndex, int toIndex) { + int len = toIndex - fromIndex; + E[] result = newArray(array, len); + if (len > 0) { + System.arraycopy(array, fromIndex, result, 0, len); + } + return result; + } + + /** + * Return a sub-array of the specified array with elements copied from + * the specified range. The "from" index is inclusive; the "to" index is exclusive. + *

+ * Arrays.subArray(int[] array, int fromIndex, int toIndex) + */ + public static int[] subArray(int[] array, int fromIndex, int toIndex) { + int len = toIndex - fromIndex; + if (len == 0) { + return EMPTY_INT_ARRAY; + } + int[] result = new int[len]; + System.arraycopy(array, fromIndex, result, 0, len); + return result; + } + + /** + * Return a sub-array of the specified array with elements copied from + * the specified range. The "from" index is inclusive; the "to" index is exclusive. + *

+ * + * Arrays.subArray(char[] array, int fromIndex, int toIndex) + * + */ + public static char[] subArray(char[] array, int fromIndex, int toIndex) { + int len = toIndex - fromIndex; + if (len == 0) { + return EMPTY_CHAR_ARRAY; + } + char[] result = new char[len]; + System.arraycopy(array, fromIndex, result, 0, len); + return result; + } + + + // ********** swap ********** + + /** + * Return the array after the specified elements have been "swapped". + *

+ * Arrays.swap(Object[] array, int i, int j) + */ + public static E[] swap(E[] array, int i, int j) { + return (i == j) ? array : swap_(array, i, j); + } + + /** + * assume the indices are different + */ + private static E[] swap_(E[] array, int i, int j) { + E temp = array[i]; + array[i] = array[j]; + array[j] = temp; + return array; + } + + /** + * Return the array after the specified elements have been "swapped". + *

+ * Arrays.swap(char[] array, int i, int j) + */ + public static char[] swap(char[] array, int i, int j) { + return (i == j) ? array : swap_(array, i, j); + } + + /** + * assume the indices are different + */ + private static char[] swap_(char[] array, int i, int j) { + char temp = array[i]; + array[i] = array[j]; + array[j] = temp; + return array; + } + + /** + * Return the array after the specified elements have been "swapped". + *

+ * Arrays.swap(int[] array, int i, int j) + */ + public static int[] swap(int[] array, int i, int j) { + return (i == j) ? array : swap_(array, i, j); + } + + /** + * assume the indices are different + */ + private static int[] swap_(int[] array, int i, int j) { + int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + return array; + } + + + // ********** Arrays enhancements ********** + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(boolean[], boolean) + */ + public static boolean[] fill(boolean[] array, boolean value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(boolean[], int, int, boolean) + */ + public static boolean[] fill(boolean[] array, int fromIndex, int toIndex, boolean value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(byte[], byte) + */ + public static byte[] fill(byte[] array, byte value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(byte[], int, int, byte) + */ + public static byte[] fill(byte[] array, int fromIndex, int toIndex, byte value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(char[], char) + */ + public static char[] fill(char[] array, char value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(char[], int, int, char) + */ + public static char[] fill(char[] array, int fromIndex, int toIndex, char value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(double[], double) + */ + public static double[] fill(double[] array, double value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(double[], int, int, double) + */ + public static double[] fill(double[] array, int fromIndex, int toIndex, double value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(float[], float) + */ + public static float[] fill(float[] array, float value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(float[], int, int, float) + */ + public static float[] fill(float[] array, int fromIndex, int toIndex, float value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(int[], int) + */ + public static int[] fill(int[] array, int value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(int[], int, int, int) + */ + public static int[] fill(int[] array, int fromIndex, int toIndex, int value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(Object[], Object) + */ + public static E[] fill(E[] array, E value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(Object[], int, int, Object) + */ + public static E[] fill(E[] array, int fromIndex, int toIndex, E value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(long[], long) + */ + public static long[] fill(long[] array, long value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(long[], int, int, long) + */ + public static long[] fill(long[] array, int fromIndex, int toIndex, long value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(short[], short) + */ + public static short[] fill(short[] array, short value) { + Arrays.fill(array, value); + return array; + } + + /** + * Return the array after it has been "filled". + * @see Arrays#fill(short[], int, int, short) + */ + public static short[] fill(short[] array, int fromIndex, int toIndex, short value) { + Arrays.fill(array, fromIndex, toIndex, value); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(byte[]) + */ + public static byte[] sort(byte... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(byte[], int, int) + */ + public static byte[] sort(byte[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(char[]) + */ + public static char[] sort(char... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(char[], int, int) + */ + public static char[] sort(char[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(double[]) + */ + public static double[] sort(double... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(double[], int, int) + */ + public static double[] sort(double[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(float[]) + */ + public static float[] sort(float... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(float[], int, int) + */ + public static float[] sort(float[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(int[]) + */ + public static int[] sort(int... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(int[], int, int) + */ + public static int[] sort(int[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(Object[]) + */ + public static E[] sort(E... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(Object[], Comparator) + */ + public static E[] sort(E[] array, Comparator comparator) { + Arrays.sort(array, comparator); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(Object[], int, int) + */ + public static E[] sort(E[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(Object[], int, int, Comparator) + */ + public static E[] sort(E[] array, int fromIndex, int toIndex, Comparator comparator) { + Arrays.sort(array, fromIndex, toIndex, comparator); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(long[]) + */ + public static long[] sort(long... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(long[], int, int) + */ + public static long[] sort(long[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(short[]) + */ + public static short[] sort(short... array) { + Arrays.sort(array); + return array; + } + + /** + * Return the array after it has been "sorted". + * @see Arrays#sort(short[], int, int) + */ + public static short[] sort(short[] array, int fromIndex, int toIndex) { + Arrays.sort(array, fromIndex, toIndex); + return array; + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private ArrayTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Association.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Association.java new file mode 100644 index 0000000000..02226b4333 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Association.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Straightforward definition of an object pairing. + * The key is immutable. + */ +public interface Association { + + /** + * Return the association's key. + */ + K getKey(); + + /** + * Return the association's value. + */ + V getValue(); + + /** + * Set the association's value. + * Return the previous value. + */ + V setValue(V value); + + /** + * Return true if the associations' keys and values + * are equal. + */ + boolean equals(Object o); + + /** + * Return a hash code based on the association's + * key and value. + */ + int hashCode(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AsynchronousCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AsynchronousCommandExecutor.java new file mode 100644 index 0000000000..62b2e70653 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AsynchronousCommandExecutor.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.util.concurrent.ThreadFactory; + +import org.eclipse.jpt.common.utility.Command; + +/** + * This command executor will dispatch commands to be executed in a separate + * thread, allowing calls to {@link CommandExecutor#execute(Command)} to return + * immediately. + *

+ * NB: The client-supplied commands should handle any + * exception appropriately (e.g. log the exception and return gracefully) so + * the command execution thread can continue executing. + */ +public class AsynchronousCommandExecutor + implements StatefulCommandExecutor +{ + /** + * This command queue is shared with the command execution/consumer thread. + * Adding a command to it will trigger the command to be executed by the + * command execution thread or, if another command is currently executing, + * to execute the new command once the currently executing command has + * finished executing. + */ + final SynchronizedQueue commands = new SynchronizedQueue(); + + /** + * Most of the thread-related behavior is delegated to this coordinator. + */ + private final ConsumerThreadCoordinator consumerThreadCoordinator; + + + // ********** construction ********** + + /** + * Construct an asynchronous command executor. + * Use simple JDK thread(s) for the command execution thread(s). + * Allow the command execution thread(s) to be assigned JDK-generated names. + */ + public AsynchronousCommandExecutor() { + this(SimpleThreadFactory.instance(), null); + } + + /** + * Construct an asynchronous command executor. + * Use the specified thread factory to construct the command execution thread(s). + * Allow the command execution thread(s) to be assigned JDK-generated names. + */ + public AsynchronousCommandExecutor(ThreadFactory threadFactory) { + this(threadFactory, null); + } + + /** + * Construct an asynchronous command executor. + * Use simple JDK thread(s) for the command execution thread(s). + * Assign the command execution thread(s) the specified name. + */ + public AsynchronousCommandExecutor(String threadName) { + this(SimpleThreadFactory.instance(), threadName); + } + + /** + * Construct an asynchronous command executor. + * Assign the command execution thread(s) the specified name. + */ + public AsynchronousCommandExecutor(ThreadFactory threadFactory, String threadName) { + super(); + this.consumerThreadCoordinator = this.buildConsumerThreadCoordinator(threadFactory, threadName); + } + + private ConsumerThreadCoordinator buildConsumerThreadCoordinator(ThreadFactory threadFactory, String threadName) { + return new ConsumerThreadCoordinator(this.buildConsumer(), threadFactory, threadName); + } + + private ConsumerThreadCoordinator.Consumer buildConsumer() { + return new Consumer(); + } + + + // ********** CallbackStatefulCommandExecutor implementation ********** + + /** + * Build and start the command execution/consumer thread. + *

+ * Note: We don't clear the command queue here; so if a command has been + * added to the queue before getting here, the first command will + * be executed promptly (albeit, asynchronously). + * The command queue will be non-empty if:

    + *
  • {@link #execute(Command)} was called after the command executor was + * constructed but before {@link #start()} was called; or + *
  • {@link #execute(Command)} was called after {@link #stop()} was called + * but before {@link #start()} was called (to restart the command executor); or + *
  • {@link #stop()} was called when there were still outstanding commands + * remaining in the command queue + *
+ */ + public void start() { + this.consumerThreadCoordinator.start(); + } + + /** + * Put the specified command on the command queue, to be consumed by the + * command execution thread. + */ + public void execute(Command command) { + this.commands.enqueue(command); + } + + /** + * Interrupt the command execution thread so that it stops executing at the + * end of the current command. Suspend the current thread until + * the command execution thread is finished executing. If any uncaught + * exceptions were thrown while the execution thread was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public void stop() { + this.consumerThreadCoordinator.stop(); + } + + + // ********** consumer ********** + + /** + * This implementation of {@link ConsumerThreadCoordinator.Consumer} + * will execute the commands enqueued by the asynchronous command executor. + * It will wait until the shared command queue is non-empty to begin executing the + * commands in the queue. Once a comand is executed, the thread will quiesce until + * another command is placed in the command queue. If a new command is + * enqueued during the execution of another command (either recursively by + * the command itself or by another thread), + * the new command will be executed immediately after the currently + * executing command is finished. + * Stop the thread by calling {@link Thread#interrupt()}. + */ + class Consumer + implements ConsumerThreadCoordinator.Consumer + { + Consumer() { + super(); + } + + /** + * Wait until a command has been placed in the queue. + */ + public void waitForProducer() throws InterruptedException { + AsynchronousCommandExecutor.this.commands.waitUntilNotEmpty(); + } + + /** + * Execute the first command in the queue and notify our listeners. + */ + public void execute() { + AsynchronousCommandExecutor.this.commands.dequeue().execute(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Bag.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Bag.java new file mode 100644 index 0000000000..0cecf9718c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Bag.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; + +/** + * A collection that allows duplicate elements. + *

+ * The Bag interface places additional stipulations, + * beyond those inherited from the {@link java.util.Collection} interface, + * on the contracts of the {@link #equals(Object)} and {@link #hashCode()} methods. + * + * @see HashBag + */ + +public interface Bag extends java.util.Collection { + + /** + * Compares the specified object with this bag for equality. Returns + * true if the specified object is also a bag, the two bags + * have the same size, and every member of the specified bag is + * contained in this bag with the same number of occurrences (or equivalently, + * every member of this bag is contained in the specified bag with the same + * number of occurrences). This definition ensures that the + * equals method works properly across different implementations of the + * bag interface. + */ + boolean equals(Object o); + + /** + * Returns the hash code value for this bag. The hash code of a bag is + * defined to be the sum of the hash codes of the elements in the bag, + * where the hashcode of a null element is defined to be zero. + * This ensures that b1.equals(b2) implies that + * b1.hashCode() == b2.hashCode() for any two bags + * b1 and b2, as required by the general + * contract of the {@link Object#hashCode()} method. + */ + int hashCode(); + + /** + * Return the number of times the specified object occurs in the bag. + */ + int count(Object o); + + /** + * Add the specified object the specified number of times to the bag. + * Return whether the bag changed. + */ + boolean add(E o, int count); + + /** + * Remove the specified number of occurrences of the specified object + * from the bag. Return whether the bag changed. + */ + boolean remove(Object o, int count); + + /** + * Return an iterator that returns each item in the bag + * once and only once, irrespective of how many times + * the item was added to the bag. + */ + java.util.Iterator uniqueIterator(); + + /** + * Return the number of unique items in the bag. + */ + int uniqueCount(); + + /** + * Return an iterator that returns an entry for each item in the bag + * once and only once, irrespective of how many times + * the item was added to the bag. The entry will indicate the item's + * count. + */ + java.util.Iterator> entries(); + + + /** + * A bag entry (element-count pair). + * The {@link Bag#entries()} method returns an iterator whose + * elements are of this class. The only way to obtain a reference + * to a bag entry is from the iterator returned by this method. These + * Bag.Entry objects are valid only for the duration + * of the iteration; more formally, the behavior of a bag entry is + * undefined if the backing bag has been modified after the entry was + * returned by the iterator, except through the {@link #setCount(int)} + * operation on the bag entry. + */ + interface Entry { + + /** + * Return the entry's element. + */ + E getElement(); + + /** + * Return entry's count; i.e. the number of times the entry's element + * occurs in the bag. + * @see Bag#count(Object) + */ + int getCount(); + + /** + * Set the entry's count; i.e. the number of times the entry's element + * occurs in the bag. The new count must be a positive number. + * Return the previous count of the entry's element. + * NB: Use {@link Iterator#remove()} to set the + * count to zero. + */ + int setCount(int count); + + /** + * Return whether the entry is equal to the specified object; + * i.e. the specified object is a Bag.Entry and its + * element and count are the same as the entry's. + */ + boolean equals(Object obj); + + /** + * Return the entry's hash code. + */ + int hashCode(); + + } + + + final class Empty extends AbstractCollection implements Bag, Serializable { + @SuppressWarnings("rawtypes") + public static final Bag INSTANCE = new Empty(); + @SuppressWarnings("unchecked") + public static Bag instance() { + return INSTANCE; + } + // ensure single instance + private Empty() { + super(); + } + @Override + public Iterator iterator() { + return EmptyIterator.instance(); + } + @Override + public int size() { + return 0; + } + public Iterator uniqueIterator() { + return EmptyIterator.instance(); + } + public int uniqueCount() { + return 0; + } + public int count(Object o) { + return 0; + } + public Iterator> entries() { + return EmptyIterator.instance(); + } + public boolean remove(Object o, int count) { + return false; + } + public boolean add(E o, int count) { + throw new UnsupportedOperationException(); + } + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if ( ! (o instanceof Bag)) { + return false; + } + return ((Bag) o).size() == 0; + } + @Override + public int hashCode() { + return 0; + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiFilter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiFilter.java new file mode 100644 index 0000000000..2894793ffd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiFilter.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +import org.eclipse.jpt.common.utility.Filter; + +/** + * Used by various "pluggable" classes to filter objects + * in both directions. + * + * If anyone can come up with a better class name + * and/or method name, I would love to hear it. ~bjv + */ +public interface BidiFilter extends Filter { + + /** + * Return whether the specified object is "accepted" by the + * "reverse" filter. What that means is determined by the client. + */ + boolean reverseAccept(T o); + + + final class Null implements BidiFilter, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiFilter INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static BidiFilter instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // nothing is filtered - everything is accepted + public boolean accept(S o) { + return true; + } + // nothing is "reverse-filtered" - everything is accepted + public boolean reverseAccept(S o) { + return true; + } + @Override + public String toString() { + return "BidiFilter.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Opaque implements BidiFilter, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiFilter INSTANCE = new Opaque(); + @SuppressWarnings("unchecked") + public static BidiFilter instance() { + return INSTANCE; + } + // ensure single instance + private Opaque() { + super(); + } + // everything is filtered - nothing is accepted + public boolean accept(S o) { + return false; + } + // everything is "reverse-filtered" - nothing is accepted + public boolean reverseAccept(S o) { + return false; + } + @Override + public String toString() { + return "BidiFilter.Opaque"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Disabled implements BidiFilter, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiFilter INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static BidiFilter instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public boolean accept(S o) { + throw new UnsupportedOperationException(); + } + // throw an exception + public boolean reverseAccept(S o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "BidiFilter.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiStringConverter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiStringConverter.java new file mode 100644 index 0000000000..056b29ae9f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiStringConverter.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Used by various "pluggable" classes to transform objects + * into strings and vice versa. + * + * If anyone can come up with a better class name + * and/or method name, I would love to hear it. ~bjv + */ +public interface BidiStringConverter extends StringConverter { + + /** + * Convert the specified string into an object. + * The semantics of "convert to object" is determined by the + * contract between the client and the server. + * Typically, if the string is null, null is returned. + */ + T convertToObject(String s); + + + final class Default implements BidiStringConverter, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiStringConverter INSTANCE = new Default(); + @SuppressWarnings("unchecked") + public static BidiStringConverter instance() { + return INSTANCE; + } + // ensure single instance + private Default() { + super(); + } + // simply return the object's #toString() result + public String convertToString(S o) { + return (o == null) ? null : o.toString(); + } + // simply return the string + @SuppressWarnings("unchecked") + public S convertToObject(String s) { + return (S) s; + } + @Override + public String toString() { + return "BidiStringConverter.Default"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Disabled implements BidiStringConverter, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiStringConverter INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static BidiStringConverter instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public String convertToString(S o) { + throw new UnsupportedOperationException(); + } + // throw an exception + public S convertToObject(String s) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "BidiStringConverter.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class BooleanConverter implements BidiStringConverter, Serializable { + public static final BidiStringConverter INSTANCE = new BooleanConverter(); + public static BidiStringConverter instance() { + return INSTANCE; + } + // ensure single instance + private BooleanConverter() { + super(); + } + /** Return "true" if the Boolean is true, otherwise return "false". */ + public String convertToString(Boolean b) { + return (b == null) ? null : b.toString(); + } + /** Return Boolean.TRUE if the string is "true" (case-insensitive), otherwise return Boolean.FALSE. */ + public Boolean convertToObject(String s) { + return (s == null) ? null : Boolean.valueOf(s); + } + @Override + public String toString() { + return "BidiStringConverter.BooleanConverter"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class IntegerConverter implements BidiStringConverter, Serializable { + public static final BidiStringConverter INSTANCE = new IntegerConverter(); + public static BidiStringConverter instance() { + return INSTANCE; + } + // ensure single instance + private IntegerConverter() { + super(); + } + /** Integer's #toString() works well. */ + public String convertToString(Integer integer) { + return (integer == null) ? null : integer.toString(); + } + /** Convert the string to an Integer, if possible. */ + public Integer convertToObject(String s) { + return (s == null) ? null : Integer.valueOf(s); + } + @Override + public String toString() { + return "BidiStringConverter.IntegerConverter"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiTransformer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiTransformer.java new file mode 100644 index 0000000000..c105d9d50a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiTransformer.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Used by various "pluggable" classes to transform objects + * in both directions. + * + * If anyone can come up with a better class name + * and/or method name, I would love to hear it. ~bjv + */ +public interface BidiTransformer extends Transformer { + + /** + * Return the "reverse-transformed" object. + * The semantics of "reverse-transform" is determined by the + * contract between the client and the server. + */ + T1 reverseTransform(T2 o); + + + final class Null implements BidiTransformer, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiTransformer INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static BidiTransformer instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // simply return the object, unchanged + @SuppressWarnings("unchecked") + public S2 transform(S1 o) { + return (S2) o; + } + // simply return the object, unchanged + @SuppressWarnings("unchecked") + public S1 reverseTransform(S2 o) { + return (S1) o; + } + @Override + public String toString() { + return "BidiTransformer.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Disabled implements BidiTransformer, Serializable { + @SuppressWarnings("rawtypes") + public static final BidiTransformer INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static BidiTransformer instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public S2 transform(S1 o) { + throw new UnsupportedOperationException(); + } + // throw an exception + public S1 reverseTransform(S2 o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "BidiTransformer.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BitTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BitTools.java new file mode 100644 index 0000000000..20cbf7c9f0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BitTools.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Assorted bit tools + */ +public final class BitTools { + + /** + * Return whether the specified 'flags' has the specified + * 'flagToCheck' set. + */ + public static boolean flagIsSet(int flags, int flagToCheck) { + return allFlagsAreSet(flags, flagToCheck); + } + + /** + * Return whether the specified 'flags' has the specified + * 'flagToCheck' turned off. + */ + public static boolean flagIsOff(int flags, int flagToCheck) { + return allFlagsAreOff(flags, flagToCheck); + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagToCheck' set. + */ + public static boolean onlyFlagIsSet(int flags, int flagToCheck) { + return onlyFlagsAreSet(flags, flagToCheck); + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagToCheck' turned off. + */ + public static boolean onlyFlagIsOff(int flags, int flagToCheck) { + return onlyFlagsAreOff(flags, flagToCheck); + } + + /** + * Return whether the specified 'flags' has all the specified + * 'flagsToCheck' set. + */ + public static boolean allFlagsAreSet(int flags, int flagsToCheck) { + return (flags & flagsToCheck) == flagsToCheck; + } + + /** + * Return whether the specified 'flags' has all the specified + * 'flagsToCheck' turned off. + */ + public static boolean allFlagsAreOff(int flags, int flagsToCheck) { + return (flags & flagsToCheck) == 0; + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagsToCheck' set. + */ + public static boolean onlyFlagsAreSet(int flags, int flagsToCheck) { + return allFlagsAreSet(flags, flagsToCheck) && allFlagsAreOff(flags, ~flagsToCheck); + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagsToCheck' turned off. + */ + public static boolean onlyFlagsAreOff(int flags, int flagsToCheck) { + return allFlagsAreOff(flags, flagsToCheck) && allFlagsAreSet(flags, ~flagsToCheck); + } + + /** + * Return whether the specified 'flags' has any one of the specified + * 'flagsToCheck' set. + */ + public static boolean anyFlagsAreSet(int flags, int flagsToCheck) { + return (flags & flagsToCheck) != 0; + } + + /** + * Return whether the specified 'flags' has any one of the specified + * 'flagsToCheck' turned off. + */ + public static boolean anyFlagsAreOff(int flags, int flagsToCheck) { + return (flags & flagsToCheck) != flagsToCheck; + } + + /** + * Return whether the specified 'flags' has all the specified + * 'flagsToCheck' set. + */ + public static boolean allFlagsAreSet(int flags, int... flagsToCheck) { + for (int i = flagsToCheck.length; i-- > 0; ) { + if ( ! allFlagsAreSet(flags, flagsToCheck[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified 'flags' has all the specified + * 'flagsToCheck' turned off. + */ + public static boolean allFlagsAreOff(int flags, int... flagsToCheck) { + for (int i = flagsToCheck.length; i-- > 0; ) { + if ( ! allFlagsAreOff(flags, flagsToCheck[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagsToCheck' set. + */ + public static boolean onlyFlagsAreSet(int flags, int... flagsToCheck) { + int combinedFlags = orFlags(flagsToCheck); + return allFlagsAreSet(flags, combinedFlags) && allFlagsAreOff(flags, ~combinedFlags); + } + + /** + * Return whether the specified 'flags' has ONLY the specified + * 'flagsToCheck' turned off. + */ + public static boolean onlyFlagsAreOff(int flags, int... flagsToCheck) { + int combinedFlags = orFlags(flagsToCheck); + return allFlagsAreOff(flags, combinedFlags) && allFlagsAreSet(flags, ~combinedFlags); + } + + /** + * Return whether the specified 'flags' has any one of the specified + * 'flagsToCheck' set. + */ + public static boolean anyFlagsAreSet(int flags, int... flagsToCheck) { + for (int i = flagsToCheck.length; i-- > 0; ) { + if (anyFlagsAreSet(flags, flagsToCheck[i])) { + return true; + } + } + return false; + } + + /** + * Return whether the specified 'flags' has any one of the specified + * 'flagsToCheck' turned off. + */ + public static boolean anyFlagsAreOff(int flags, int... flagsToCheck) { + for (int i = flagsToCheck.length; i-- > 0; ) { + if (anyFlagsAreOff(flags, flagsToCheck[i])) { + return true; + } + } + return false; + } + + /** + * OR all the specified 'flags' together and return the result. + */ + public static int orFlags(int... flags) { + int last = flags.length - 1; + int result = flags[last]; + for (int i = last; i-- > 0; ) { + result |= flags[i]; + } + return result; + } + + /** + * AND all the specified 'flags' together and return the result. + */ + public static int andFlags(int... flags) { + int last = flags.length - 1; + int result = flags[last]; + for (int i = last; i-- > 0; ) { + result &= flags[i]; + } + return result; + } + + /** + * XOR all the specified 'flags' together and return the result. + */ + public static int xorFlags(int... flags) { + int last = flags.length - 1; + int result = flags[last]; + for (int i = last; i-- > 0; ) { + result ^= flags[i]; + } + return result; + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private BitTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanReference.java new file mode 100644 index 0000000000..9d114257d1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanReference.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Interface for a container for passing a flag that can be changed by + * the recipient. + */ +public interface BooleanReference + extends ReadOnlyBooleanReference +{ + /** + * Set the boolean value. + * Return the previous value. + */ + boolean setValue(boolean value); + + /** + * Set the boolean value to the NOT of its current value. + * Return the new value. + */ + boolean flip(); + + /** + * Set the boolean value to the NOT of the specified value. + * Return the previous value. + */ + boolean setNot(boolean v); + + /** + * Set the boolean value to true. + * Return the previous value. + */ + boolean setTrue(); + + /** + * Set the boolean value to false. + * Return the previous value. + */ + boolean setFalse(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanTools.java new file mode 100644 index 0000000000..783c0f1299 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanTools.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Assorted "Capital-B Boolean" operations. + */ + // commented code is just playing around with building *everything* from NAND +public final class BooleanTools { + + /** + * Return the NOT of the specified Boolean. + * Boolean#not() + */ + public static Boolean not(Boolean b) { + return Boolean.valueOf( ! b.booleanValue()); +// return nand(b, b); + } + + /** + * Return the AND of the specified Booleans. + * Boolean#and(Boolean) + */ + public static Boolean and(Boolean b1, Boolean b2) { + return Boolean.valueOf(b1.booleanValue() && b2.booleanValue()); +// Boolean nand = nand(b1, b2); +// return nand(nand, nand); + } + + /** + * Return the OR of the specified Booleans. + * Boolean#or(Boolean) + */ + public static Boolean or(Boolean b1, Boolean b2) { + return Boolean.valueOf(b1.booleanValue() || b2.booleanValue()); +// Boolean nand = nand(b1, b2); +// Boolean xor = nand(nand(b1, nand), nand(b2, nand)); +// Boolean and = nand(nand, nand); +// Boolean nand2 = nand(xor, and); +// return nand(nand(xor, nand2), nand(and, nand2)); + } + + /** + * Return the XOR of the specified Booleans. + * Boolean#xor(Boolean) + */ + public static Boolean xor(Boolean b1, Boolean b2) { + return and(or(b1, b2), nand(b1, b2)); +// Boolean nand = nand(b1, b2); +// return nand(nand(b1, nand), nand(b2, nand)); + } + + /** + * Return the NAND of the specified Booleans. + * Boolean#nand(Boolean) + */ + public static Boolean nand(Boolean b1, Boolean b2) { + return not(and(b1, b2)); +// return Boolean.valueOf( ! (b1.booleanValue() && b2.booleanValue())); + } + + /** + * Return the NOR of the specified Booleans. + * Boolean#nor(Boolean) + */ + public static Boolean nor(Boolean b1, Boolean b2) { + return not(or(b1, b2)); +// Boolean nand = nand(b1, b2); +// Boolean xor = nand(nand(b1, nand), nand(b2, nand)); +// Boolean and = nand(nand, nand); +// Boolean nand2 = nand(xor, and); +// Boolean nand3 = nand(nand(xor, nand2), nand(and, nand2)); +// return nand(nand3, nand3); + } + + /** + * Return the XNOR of the specified Booleans. + * Boolean#xnor(Boolean) + */ + public static Boolean xnor(Boolean b1, Boolean b2) { + return not(xor(b1, b2)); +// Boolean nand = nand(b1, b2); +// Boolean xor = nand(nand(b1, nand), nand(b2, nand)); +// return nand(xor, xor); + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private BooleanTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ClassName.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ClassName.java new file mode 100644 index 0000000000..aaf4be351c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ClassName.java @@ -0,0 +1,431 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Convenience methods related to Java class names as returned by + * {@link java.lang.Class#getName()}. + */ +public final class ClassName { + + public static final String VOID_CLASS_NAME = ReflectionTools.VOID_CLASS.getName(); + public static final String VOID_WRAPPER_CLASS_NAME = ReflectionTools.VOID_WRAPPER_CLASS.getName(); + + public static final char REFERENCE_CLASS_CODE = 'L'; + public static final char REFERENCE_CLASS_NAME_DELIMITER = ';'; + + /** + * Return whether the specified class is an array type. + * @see java.lang.Class#getName() + */ + public static boolean isArray(String className) { + return className.charAt(0) == '['; + } + + /** + * Return the "element type" of the specified class. + * The element type is the base type held by an array type. + * Non-array types simply return themselves. + * @see java.lang.Class#getName() + */ + public static String getElementTypeName(String className) { + int depth = getArrayDepth(className); + if (depth == 0) { + // the name is in the form: "java.lang.Object" or "int" + return className; + } + return getElementTypeName_(className, depth); + } + + /** + * pre-condition: array depth is not zero + */ + private static String getElementTypeName_(String className, int arrayDepth) { + int last = className.length() - 1; + if (className.charAt(arrayDepth) == REFERENCE_CLASS_CODE) { + // the name is in the form: "[[[Ljava.lang.Object;" + return className.substring(arrayDepth + 1, last); // drop the trailing ';' + } + // the name is in the form: "[[[I" + return forCode(className.charAt(last)); + } + + /** + * Return the "array depth" of the specified class. + * The depth is the number of dimensions for an array type. + * Non-array types have a depth of zero. + * @see java.lang.Class#getName() + */ + public static int getArrayDepth(String className) { + int depth = 0; + while (className.charAt(depth) == '[') { + depth++; + } + return depth; + } + + /** + * Return the specified class's component type. + * Return null if the specified class is not an array type. + * @see java.lang.Class#getName() + */ + public static String getComponentTypeName(String className) { + switch (getArrayDepth(className)) { + case 0: + return null; + case 1: + return getElementTypeName_(className, 1); + default: + return className.substring(1); + } + } + + /** + * Return the specified class's simple name. + * Return an empty string if the specified class is anonymous. + *

+ * The simple name of an array type is the simple name of the + * component type with "[]" appended. In particular, + * the simple name of an array type whose component type is + * anonymous is simply "[]". + * @see java.lang.Class#getSimpleName() + */ + public static String getSimpleName(String className) { + return isArray(className) ? + getSimpleName(getComponentTypeName(className)) + "[]" : //$NON-NLS-1$ + getSimpleName_(className); + } + + /** + * pre-condition: specified class is not an array type + */ + private static String getSimpleName_(String className) { + int index = className.lastIndexOf('$'); + if (index == -1) { // "top-level" class - strip package name + return className.substring(className.lastIndexOf('.') + 1); + } + + int len = className.length(); + for (int i = ++index; i < len; i++) { + if ( ! charIsAsciiDigit(className.charAt(i))) { + return className.substring(i); // "member" or "local" class + } + } + // all the characters past the '$' are ASCII digits ("anonymous" class) + return ""; //$NON-NLS-1$ + } + + /** + * Return the specified class's package name (e.g. + * "java.lang.Object" returns + * "java.lang"). + * Return an empty string if the specified class is:

    + *
  • in the "default" package + *
  • an array class + *
  • a primtive class + *
+ * @see java.lang.Class#getPackage() + * @see java.lang.Package#getName() + */ + public static String getPackageName(String className) { + if (isArray(className)) { + return ""; //$NON-NLS-1$ + } + int lastPeriod = className.lastIndexOf('.'); + return (lastPeriod == -1) ? "" : className.substring(0, lastPeriod); //$NON-NLS-1$ + } + + /** + * Return whether the specified class is a "top-level" class, + * as opposed to a "member", "local", or "anonymous" class, + * using the standard JDK naming conventions (i.e. the class + * name does NOT contain a '$'). + * A "top-level" class can be either the "primary" (public) class defined + * in a file/compilation unit (i.e. the class with the same name as its + * file's simple base name) or a "non-primary" (package visible) class + * (i.e. the other top-level classes defined in a file). + * A "top-level" class can contain any of the other types of classes. + * @see java.lang.Class#getName() + */ + public static boolean isTopLevel(String className) { + if (isArray(className)) { + return false; + } + return className.lastIndexOf('$') == -1; + } + + /** + * Return whether the specified class is a "member" class, + * as opposed to a "top-level", "local", or "anonymous" class, + * using the standard JDK naming convention (i.e. the class + * name ends with a '$' followed by a legal class name; e.g. + * "TopLevelClass$1LocalClass$MemberClass"). + * A "member" class can be either "nested" (static) or "inner"; + * but there is no way to determine which from the class's name. + * A "member" class can contain "local", "anonymous", or other + * "member" classes; and vice-versa. + * @see java.lang.Class#getName() + */ + public static boolean isMember(String className) { + if (isArray(className)) { + return false; + } + int index = className.lastIndexOf('$'); + if (index == -1) { + return false; // "top-level" class + } + // the character immediately after the dollar sign cannot be an ASCII digit + return ! charIsAsciiDigit(className.charAt(++index)); + } + + /** + * Return whether the specified class is a "local" class, + * as opposed to a "top-level", "member", or "anonymous" class, + * using the standard JDK naming convention (i.e. the class name + * ends with "$nnnXXX", + * where the '$' is + * followed by a series of numeric digits which are followed by the + * local class name; e.g. "TopLevelClass$1LocalClass"). + * A "local" class can contain "member", "anonymous", or other + * "local" classes; and vice-versa. + * @see java.lang.Class#getName() + */ + public static boolean isLocal(String className) { + if (isArray(className)) { + return false; + } + int index = className.lastIndexOf('$'); + if (index == -1) { + return false; // "top-level" class + } + if ( ! charIsAsciiDigit(className.charAt(++index))) { + return false; // "member" class + } + int len = className.length(); + for (int i = ++index; i < len; i++) { + if ( ! charIsAsciiDigit(className.charAt(i))) { + return true; + } + } + // all the characters past the '$' are ASCII digits ("anonymous" class) + return false; + } + + /** + * Return whether the specified class is an "anonymous" class, + * as opposed to a "top-level", "member", or "local" class, + * using the standard JDK naming convention (i.e. the class + * name ends with "$nnn" where all the characters past the + * last '$' are ASCII numeric digits; + * e.g. "TopLevelClass$1"). + * An "anonymous" class can contain "member", "local", or other + * "anonymous" classes; and vice-versa. + * @see java.lang.Class#getName() + */ + public static boolean isAnonymous(String className) { + if (isArray(className)) { + return false; + } + int index = className.lastIndexOf('$'); + if (index == -1) { + return false; // "top-level" class + } + if ( ! charIsAsciiDigit(className.charAt(++index))) { + return false; // "member" class + } + int len = className.length(); + for (int i = ++index; i < len; i++) { + if ( ! charIsAsciiDigit(className.charAt(i))) { + return false; // "local" class + } + } + // all the characters past the '$' are ASCII digits ("anonymous" class) + return true; + } + + /** + * {@link Character#isDigit(char)} returns true for some non-ASCII + * digits. This method does not. + */ + private static boolean charIsAsciiDigit(char c) { + return ('0' <= c) && (c <= '9'); + } + + /** + * Return whether the specified class is a "reference" + * class (i.e. neither void nor one of the primitive variable classes, + * boolean, int, float, etc.). + *

+ * NB: void.class.isPrimitive() == true + */ + public static boolean isReference(String className) { + return ! isPrimitive(className); + } + + /** + * Return whether the specified class is a primitive + * class (i.e. void or one of the primitive variable classes, + * boolean, int, float, etc.). + *

+ * NB: void.class.isPrimitive() == true + */ + public static boolean isPrimitive(String className) { + if (isArray(className) || (className.length() > ReflectionTools.MAX_PRIMITIVE_CLASS_NAME_LENGTH)) { + return false; // performance tweak + } + for (ReflectionTools.Primitive primitive : ReflectionTools.PRIMITIVES) { + if (className.equals(primitive.javaClass.getName())) { + return true; + } + } + return false; + } + + /** + * Return whether the specified class is a primitive wrapper + * class (i.e. java.lang.Void or one of the primitive + * variable wrapper classes, java.lang.Boolean, + * java.lang.Integer, java.lang.Float, etc.). + *

+ * NB: void.class.isPrimitive() == true + */ + public static boolean isPrimitiveWrapper(String className) { + if (isArray(className) || (className.length() > ReflectionTools.MAX_PRIMITIVE_WRAPPER_CLASS_NAME_LENGTH)) { + return false; // performance tweak + } + for (ReflectionTools.Primitive primitive : ReflectionTools.PRIMITIVES) { + if (className.equals(primitive.wrapperClass.getName())) { + return true; + } + } + return false; + } + + /** + * Return whether the specified class is a "variable" primitive + * class (i.e. boolean, int, float, etc., + * but not void). + *

+ * NB: void.class.isPrimitive() == true + */ + public static boolean isVariablePrimitive(String className) { + return isPrimitive(className) + && ( ! className.equals(VOID_CLASS_NAME)); + } + + /** + * Return whether the specified class is a "variable" primitive wrapper + * class (i.e. java.lang.Boolean, + * java.lang.Integer, java.lang.Float, etc., + * but not java.lang.Void). + *

+ * NB: void.class.isPrimitive() == true + */ + public static boolean isVariablePrimitiveWrapper(String className) { + return isPrimitiveWrapper(className) + && ( ! className.equals(VOID_WRAPPER_CLASS_NAME)); + } + + /** + * Return the name of the primitive wrapper class corresponding to the specified + * primitive class. Return null if the specified class is not a primitive. + */ + public static String getWrapperClassName(String primitiveClassName) { + for (ReflectionTools.Primitive primitive : ReflectionTools.PRIMITIVES) { + if (primitive.javaClass.getName().equals(primitiveClassName)) { + return primitive.wrapperClass.getName(); + } + } + return null; + } + + /** + * Return the name of the primitive class corresponding to the specified + * primitive wrapper class. Return null if the specified class + * is not a primitive wrapper. + */ + public static String getPrimitiveClassName(String primitiveWrapperClassName) { + for (ReflectionTools.Primitive primitive : ReflectionTools.PRIMITIVES) { + if (primitive.wrapperClass.getName().equals(primitiveWrapperClassName)) { + return primitive.javaClass.getName(); + } + } + return null; + } + + /** + * Return whether the two class names are equivalent, given autoboxing. + * (e.g. "java.lang.Integer" and "int" should be equivalent) + */ + public static boolean areAutoboxEquivalents(String className1, String className2) { + return Tools.valuesAreEqual(className1, className2) + || Tools.valuesAreEqual(getPrimitiveClassName(className1), className2) + || Tools.valuesAreEqual(getWrapperClassName(className1), className2); + } + + + // ********** primitive codes ********** + + /** + * Return the primitive class name for the specified primitive class code. + * Return null if the specified code + * is not a primitive class code. + * @see java.lang.Class#getName() + */ + public static String forCode(int classCode) { + return forCode((char) classCode); + } + + /** + * Return the primitive class name for the specified primitive class code. + * Return null if the specified code + * is not a primitive class code. + * @see java.lang.Class#getName() + */ + public static String forCode(char classCode) { + Class primitiveClass = ReflectionTools.getClassForCode(classCode); + return (primitiveClass == null) ? null : primitiveClass.getName(); + } + + /** + * Return the class code for the specified primitive class. + * Return 0 if the specified class + * is not a primitive class. + * @see java.lang.Class#getName() + */ + public static char getCodeForClassName(String primitiveClassName) { + if (( ! isArray(primitiveClassName)) && (primitiveClassName.length() <= ReflectionTools.MAX_PRIMITIVE_CLASS_NAME_LENGTH)) { + for (ReflectionTools.Primitive primitive : ReflectionTools.PRIMITIVES) { + if (primitive.javaClass.getName().equals(primitiveClassName)) { + return primitive.code; + } + } + } + return 0; + } + + static void append(String className, StringBuilder sb) { + sb.append(REFERENCE_CLASS_CODE); + sb.append(className); + sb.append(REFERENCE_CLASS_NAME_DELIMITER); + } + + + // ********** suppressed constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private ClassName() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Classpath.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Classpath.java new file mode 100644 index 0000000000..dcf03bd2cf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Classpath.java @@ -0,0 +1,939 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.eclipse.jpt.common.utility.Filter; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.iterators.CompositeIterator; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.iterators.FilteringIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; + +/** + * Classpath models a Java classpath, which consists of a list of + * {@link Entry}s, each of which contain Java classes. The classpath can return + * the names of classes found in it etc. There are a number of static + * convenience methods that can be use to construct Classpaths + * corresponding to the Java classpath etc. + */ +public class Classpath + implements Serializable +{ + /** The entries in the classpath */ + private final Entry[] entries; + + private static final long serialVersionUID = 1L; + + + // ********** static methods ********** + + // ***** factory methods for "standard" classpaths ***** + + /** + * Return the Java "boot" classpath. This includes rt.jar. + */ + public static Classpath bootClasspath() { + return new Classpath(System.getProperty("sun.boot.class.path")); //$NON-NLS-1$ + } + + /** + * Return a "virtual classpath" that contains all the jars + * that would be used by the Java Extension Mechanism. + */ + public static Classpath javaExtensionClasspath() { + File[] dirs = javaExtensionDirectories(); + List jarFileNames = new ArrayList(); + for (File dir : dirs) { + if (dir.isDirectory()) { + addJarFileNamesTo(dir, jarFileNames); + } + } + return new Classpath(jarFileNames); + } + + /** + * Return the Java "system" classpath. + */ + public static Classpath javaClasspath() { + return new Classpath(System.getProperty("java.class.path")); //$NON-NLS-1$ + } + + /** + * Return the unretouched "complete" classpath. + * This includes the boot classpath, the Java Extension + * Mechanism classpath, and the normal "system" classpath. + */ + public static Classpath completeClasspath() { + return new Classpath(new Classpath[] { + bootClasspath(), + javaExtensionClasspath(), + javaClasspath() + }); + } + + /** + * Return a classpath that contains the location of the specified class. + */ + public static Classpath classpathFor(Class javaClass) { + return new Classpath(locationFor(javaClass)); + } + + + // ***** file => class ***** + + /** + * Convert a relative file name to a class name; this will work for + * any file that has a single extension beyond the base + * class name:

    + *
  • "java/lang/String.class" is converted to "java.lang.String" + *
  • "java/lang/String.java" is converted to "java.lang.String" + *
+ */ + public static String convertToClassName(String classFileName) { + String className = FileTools.stripExtension(classFileName); + // do this for archive entry names + className = className.replace('/', '.'); + // do this for O/S-specific file names + if (File.separatorChar != '/') { + className = className.replace(File.separatorChar, '.'); + } + return className; + } + + /** + * Convert a file to a class name; + * e.g. File(java/lang/String.class) is converted to + * "java.lang.String". + */ + public static String convertToClassName(File classFile) { + return convertToClassName(classFile.getPath()); + } + + /** + * Convert a relative file name to a class; + * e.g. "java/lang/String.class" is converted to + * java.lang.String.class. + */ + public static Class convertToClass(String classFileName) throws ClassNotFoundException { + return Class.forName(convertToClassName(classFileName)); + } + + /** + * Convert a relative file to a class; + * e.g. File(java/lang/String.class) is converted to + * java.lang.String.class. + */ + public static Class convertToClass(File classFile) throws ClassNotFoundException { + return convertToClass(classFile.getPath()); + } + + + // ***** class => JAR entry ***** + + /** + * Convert a class name to an archive entry name base; + * e.g. "java.lang.String" is converted to + * "java/lang/String". + */ + public static String convertToArchiveEntryNameBase(String className) { + return className.replace('.', '/'); + } + + /** + * Convert a class to an archive entry name base; + * e.g. java.lang.String.class is converted to + * "java/lang/String". + */ + public static String convertToArchiveEntryNameBase(Class javaClass) { + return convertToArchiveEntryNameBase(javaClass.getName()); + } + + /** + * Convert a class name to an archive class file entry name; + * e.g. "java.lang.String" is converted to + * "java/lang/String.class". + */ + public static String convertToArchiveClassFileEntryName(String className) { + return convertToArchiveEntryNameBase(className) + ".class"; //$NON-NLS-1$ + } + + /** + * Convert a class to an archive class file entry name; + * e.g. java.lang.String.class is converted to + * "java/lang/String.class". + */ + public static String convertToArchiveClassFileEntryName(Class javaClass) { + return convertToArchiveClassFileEntryName(javaClass.getName()); + } + + + // ***** class => file (.class or .java) ***** + + /** + * Convert a class name to a file name base for the current O/S; + * e.g. "java.lang.String" is converted to + * "java/lang/String" on Unix and + * "java\\lang\\String" on Windows. + */ + public static String convertToFileNameBase(String className) { + return className.replace('.', File.separatorChar); + } + + /** + * Convert a class to a file name base for the current O/S; + * e.g. java.lang.String.class is converted to + * "java/lang/String" on Unix and + * "java\\lang\\String" on Windows. + */ + public static String convertToFileNameBase(Class javaClass) { + return convertToFileNameBase(javaClass.getName()); + } + + /** + * Convert a class name to a class file name for the current O/S; + * e.g. "java.lang.String" is converted to + * "java/lang/String.class" on Unix and + * "java\\lang\\String.class" on Windows. + */ + public static String convertToClassFileName(String className) { + return convertToFileNameBase(className) + ".class"; //$NON-NLS-1$ + } + + /** + * Convert a class to a class file name for the current O/S; + * e.g. java.lang.String.class is converted to + * "java/lang/String.class" on Unix and + * "java\\lang\\String.class" on Windows. + */ + public static String convertToClassFileName(Class javaClass) { + return convertToClassFileName(javaClass.getName()); + } + + /** + * Convert a class name to a class file for the current O/S; + * e.g. "java.lang.String" is converted to + * File(java/lang/String.class). + */ + public static File convertToClassFile(String className) { + return new File(convertToClassFileName(className)); + } + + /** + * Convert a class to a class file for the current O/S; + * e.g. java.lang.String.class is converted to + * File(java/lang/String.class). + */ + public static File convertToClassFile(Class javaClass) { + return convertToClassFile(javaClass.getName()); + } + + /** + * Convert a class name to a java file name for the current O/S; + * e.g. "java.lang.String" is converted to + * "java/lang/String.java" on Unixl and + * "java\\lang\\String.java" on Windows. + */ + public static String convertToJavaFileName(String className) { + return convertToFileNameBase(className) + ".java"; //$NON-NLS-1$ + } + + /** + * Convert a class to a java file name for the current O/S; + * e.g. java.lang.String.class is converted to + * "java/lang/String.java" on Unix and + * "java\\lang\\String.java" on Windows. + */ + public static String convertToJavaFileName(Class javaClass) { + return convertToJavaFileName(javaClass.getName()); + } + + /** + * Convert a class name to a java file for the current O/S; + * e.g. "java.lang.String" is converted to + * File(java/lang/String.java). + */ + public static File convertToJavaFile(String className) { + return new File(convertToJavaFileName(className)); + } + + /** + * Convert a class to a java file for the current O/S; + * e.g. java.lang.String.class is converted to + * File(java/lang/String.java). + */ + public static File convertToJavaFile(Class javaClass) { + return convertToJavaFile(javaClass.getName()); + } + + + // ***** class => resource ***** + + /** + * Convert a class to a resource name; + * e.g. java.lang.String.class is converted to + * "/java/lang/String.class". + */ + public static String convertToResourceName(Class javaClass) { + return '/' + convertToArchiveClassFileEntryName(javaClass); + } + + /** + * Convert a class to a resource; + * e.g. java.lang.String.class is converted to + * URL(jar:file:/C:/jdk/1.4.2_04/jre/lib/rt.jar!/java/lang/String.class). + */ + public static URL convertToResource(Class javaClass) { + return javaClass.getResource(convertToResourceName(javaClass)); + } + + + // ***** utilities ***** + + /** + * Return whether the specified file is an archive file; + * i.e. its name ends with ".zip" or ".jar". + */ + public static boolean fileNameIsArchive(String fileName) { + String ext = FileTools.extension(fileName).toLowerCase(); + return ext.equals(".jar") || ext.equals(".zip"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Return whether the specified file is an archive file; + * i.e. its name ends with ".zip" or ".jar". + */ + public static boolean fileIsArchive(File file) { + return fileNameIsArchive(file.getName()); + } + + /** + * Return what should be the fully-qualified file name + * for the JRE runtime JAR; + * e.g. "C:\jdk1.4.2_04\jre\lib\rt.jar". + */ + public static String rtJarName() { + return locationFor(java.lang.Object.class); + } + + /** + * Return the location from where the specified class was loaded. + */ + public static String locationFor(Class javaClass) { + URL url = convertToResource(javaClass); + String path; + try { + path = FileTools.buildFile(url).getPath(); + } catch (URISyntaxException ex) { + throw new RuntimeException(ex); + } + String protocol = url.getProtocol().toLowerCase(); + if (protocol.equals("jar")) { //$NON-NLS-1$ + // if the class is in a JAR, the URL will look something like this: + // jar:file:/C:/jdk/1.4.2_04/jre/lib/rt.jar!/java/lang/String.class + return path.substring(0, path.indexOf('!')); + } else if (protocol.equals("file")) { //$NON-NLS-1$ + // if the class is in a directory, the URL will look something like this: + // file:/C:/dev/main/mwdev/class/org/eclipse/dali/utility/Classpath.class + return path.substring(0, path.length() - convertToClassFileName(javaClass).length() - 1); + } else if (protocol.equals("bundleresource")) { //$NON-NLS-1$ + // if the class is in a bundle resource (Eclipse?), the URL will look something like this: + // bundleresource://43/org/eclipse/dali/utility/Classpath.class + return path.substring(0, path.length() - convertToClassFileName(javaClass).length() - 1); + } + + throw new IllegalStateException(url.toString()); + } + + /** + * Return the directories used by the Java Extension Mechanism. + */ + public static File[] javaExtensionDirectories() { + return convertToFiles(javaExtensionDirectoryNames()); + } + + /** + * Return the directory names used by the Java Extension Mechanism. + */ + public static String[] javaExtensionDirectoryNames() { + return System.getProperty("java.ext.dirs").split(File.pathSeparator); //$NON-NLS-1$ + } + + + // ***** internal ***** + + private static File[] convertToFiles(String[] fileNames) { + File[] files = new File[fileNames.length]; + for (int i = fileNames.length; i-- > 0; ) { + files[i] = new File(fileNames[i]); + } + return files; + } + + private static void addJarFileNamesTo(File dir, List jarFileNames) { + File[] jarFiles = jarFilesIn(dir); + for (File jarFile : jarFiles) { + jarFileNames.add(FileTools.canonicalFile(jarFile).getPath()); + } + } + + private static File[] jarFilesIn(File directory) { + return directory.listFiles(jarFileFilter()); + } + + private static FileFilter jarFileFilter() { + return new FileFilter() { + public boolean accept(File file) { + return FileTools.extension(file.getName()).toLowerCase().equals(".jar"); //$NON-NLS-1$ + } + }; + } + + + // ********** constructors ********** + + /** + * Construct a classpath with the specified entries. + */ + private Classpath(Entry[] entries) { + super(); + this.entries = entries; + } + + /** + * Construct a classpath with the specified entries. + */ + public Classpath(String... fileNames) { + this(buildEntries(fileNames)); + } + + /** + * Skip empty file names because they will end up expanding to the current + * working directory, which is not what we want. Empty file names actually + * occur with some frequency; such as when the classpath has been built up + * dynamically with too many separators. For example:
+	 *     "C:\dev\foo.jar;;C:\dev\bar.jar"
+	 * 
will be parsed into three file names:
+	 *     { "C:\dev\foo.jar", "", "C:\dev\bar.jar" }
+	 * 
+ */ + private static Entry[] buildEntries(String[] fileNames) { + List entries = new ArrayList(); + for (String fileName : fileNames) { + if ((fileName != null) && (fileName.length() != 0)) { + entries.add(new Entry(fileName)); + } + } + return entries.toArray(new Entry[entries.size()]); + } + + /** + * Construct a classpath with the specified path. + */ + public Classpath(String path) { + this(path.split(File.pathSeparator)); + } + + /** + * Construct a classpath with the specified entries. + */ + public Classpath(Iterable fileNames) { + this(ArrayTools.array(fileNames, StringTools.EMPTY_STRING_ARRAY)); + } + + /** + * Consolidate the specified classpaths into a single classpath. + */ + public Classpath(Classpath... classpaths) { + this(consolidateEntries(classpaths)); + } + + private static Entry[] consolidateEntries(Classpath[] classpaths) { + List entries = new ArrayList(); + for (Classpath classpath : classpaths) { + CollectionTools.addAll(entries, classpath.getEntries()); + } + return entries.toArray(new Entry[entries.size()]); + } + + + // ********** public API ********** + + /** + * Return the classpath's entries. + */ + public Iterable getEntries() { + return new ArrayIterable(this.entries); + } + + /** + * Return the classpath's path. + */ + public String getPath() { + int max = this.entries.length - 1; + if (max == -1) { + return ""; //$NON-NLS-1$ + } + StringBuilder sb = new StringBuilder(2000); + // stop one short of the end of the array + for (int i = 0; i < max; i++) { + sb.append(this.entries[i].getFileName()); + sb.append(File.pathSeparatorChar); + } + sb.append(this.entries[max].getFileName()); + return sb.toString(); + } + + /** + * Search the classpath for the specified (unqualified) file + * and return its entry. Return null if an entry is not found. + * For example, you could use this method to find the entry + * for "rt.jar" or "toplink.jar". + */ + public Entry getEntryForFileNamed(String shortFileName) { + for (Entry entry : this.entries) { + if (entry.getFile().getName().equals(shortFileName)) { + return entry; + } + } + return null; + } + + /** + * Return the first entry file in the classpath + * that contains the specified class. + * Return null if an entry is not found. + */ + public Entry getEntryForClassNamed(String className) { + String relativeClassFileName = convertToClassFileName(className); + String archiveEntryName = convertToArchiveClassFileEntryName(className); + for (Entry entry : this.entries) { + if (entry.contains(relativeClassFileName, archiveEntryName)) { + return entry; + } + } + return null; + } + + /** + * Return the names of all the classes discovered on the classpath, + * with duplicates removed. + * @see #classNames() + */ + public Iterable getClassNames() { + return this.getClassNames(Filter.Null.instance()); + } + + /** + * Return the names of all the classes discovered on the classpath + * and accepted by the specified filter, with duplicates removed. + * @see #classNames(Filter) + */ + public Iterable getClassNames(Filter filter) { + Collection classNames = new HashSet(10000); + this.addClassNamesTo(classNames, filter); + return classNames; + } + + /** + * Add the names of all the classes discovered on the classpath + * to the specified collection. + */ + public void addClassNamesTo(Collection classNames) { + this.addClassNamesTo(classNames, Filter.Null.instance()); + } + + /** + * Add the names of all the classes discovered on the classpath + * and accepted by the specified filter to the specified collection. + */ + public void addClassNamesTo(Collection classNames, Filter filter) { + for (Entry entry : this.entries) { + entry.addClassNamesTo(classNames, filter); + } + } + + /** + * Return the names of all the classes discovered on the classpath. + * Just a bit more performant than {@link #getClassNames()}. + */ + public Iterator classNames() { + return this.classNames(Filter.Null.instance()); + } + + /** + * Return the names of all the classes discovered on the classpath + * that are accepted by the specified filter. + * Just a bit more performant than {@link #getClassNames(Filter)}. + */ + public Iterator classNames(Filter filter) { + return new CompositeIterator(this.entryClassNamesIterators(filter)); + } + + private Iterator> entryClassNamesIterators(final Filter filter) { + return new TransformationIterator>(new ArrayIterator(this.entries)) { + @Override + protected Iterator transform(Entry entry) { + return entry.classNames(filter); + } + }; + } + + /** + * Return a "compressed" version of the classpath with its + * duplicate entries eliminated. + */ + public Classpath compressed() { + return new Classpath(ArrayTools.removeDuplicateElements(this.entries)); + } + + /** + * Convert the classpath to an array of URLs + * (that can be used to instantiate a {@link java.net.URLClassLoader}). + */ + public Iterable getURLs() { + int len = this.entries.length; + URL[] urls = new URL[len]; + for (int i = 0; i < len; i++) { + urls[i] = this.entries[i].getURL(); + } + return new ArrayIterable(urls); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.getPath()); + } + + + // ********** inner class ********** + + /** + * Entry models a Java classpath entry, which can be either a + * directory containing .class files or a JAR file (or, + * similarly, a .zip file). The entry can return the names of + * classes found in it etc. + */ + public static class Entry implements Serializable { + private final String fileName; + private final File file; + private final File canonicalFile; + + private static final long serialVersionUID = 1L; + + Entry(String fileName) { + super(); + if ((fileName == null) || (fileName.length() == 0)) { + throw new IllegalArgumentException("'fileName' must be non-empty"); //$NON-NLS-1$ + } + this.fileName = fileName; + this.file = new File(fileName); + this.canonicalFile = FileTools.canonicalFile(this.file); + } + + public String getFileName() { + return this.fileName; + } + + public File getFile() { + return this.file; + } + + public File getCanonicalFile() { + return this.canonicalFile; + } + + public String getCanonicalFileName() { + return this.canonicalFile.getAbsolutePath(); + } + + @Override + public boolean equals(Object o) { + if ( ! (o instanceof Entry)) { + return false; + } + return ((Entry) o).canonicalFile.equals(this.canonicalFile); + } + + @Override + public int hashCode() { + return this.canonicalFile.hashCode(); + } + + /** + * Return the entry's "canonical" URL. + */ + public URL getURL() { + try { + return this.canonicalFile.toURI().toURL(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Return whether the entry contains the specified class. + */ + public boolean contains(Class javaClass) { + return this.contains(javaClass.getName()); + } + + /** + * Return whether the entry contains the specified class. + */ + public boolean contains(String className) { + return this.contains(convertToClassFileName(className), convertToArchiveClassFileEntryName(className)); + } + + /** + * Return whether the entry contains either the specified relative + * class file or the specified archive entry. + * Not the prettiest signature, but it's internal.... + */ + boolean contains(String relativeClassFileName, String archiveEntryName) { + if ( ! this.canonicalFile.exists()) { + return false; + } + if (this.canonicalFile.isDirectory() && (new File(this.canonicalFile, relativeClassFileName)).exists()) { + return true; + } + return (fileIsArchive(this.canonicalFile) && this.archiveContainsEntry(archiveEntryName)); + } + + /** + * Return whether the entry's archive contains the specified entry. + */ + private boolean archiveContainsEntry(String zipEntryName) { + ZipFile zipFile = null; + ZipEntry zipEntry = null; + try { + zipFile = new ZipFile(this.canonicalFile); + zipEntry = zipFile.getEntry(zipEntryName); + } catch (IOException ex) { + // something is wrong, leave the entry null + } finally { + try { + if (zipFile != null) { + zipFile.close(); + } + } catch (IOException ex) { + zipEntry = null; // something is wrong, clear out the entry + } + } + return zipEntry != null; + } + + /** + * Return the names of all the classes discovered in the entry. + * @see #classNames() + */ + public Iterable getClassNames() { + return this.getClassNames(Filter.Null.instance()); + } + + /** + * Return the names of all the classes discovered in the entry + * and accepted by the specified filter. + * @see #classNames(Filter) + */ + public Iterable getClassNames(Filter filter) { + Collection classNames = new ArrayList(2000); + this.addClassNamesTo(classNames, filter); + return classNames; + } + + /** + * Add the names of all the classes discovered in the entry + * to the specified collection. + */ + public void addClassNamesTo(Collection classNames) { + this.addClassNamesTo(classNames, Filter.Null.instance()); + } + + /** + * Add the names of all the classes discovered in the entry + * and accepted by the specified filter to the specified collection. + */ + public void addClassNamesTo(Collection classNames, Filter filter) { + if (this.canonicalFile.exists()) { + if (this.canonicalFile.isDirectory()) { + this.addClassNamesForDirectoryTo(classNames, filter); + } else if (fileIsArchive(this.canonicalFile)) { + this.addClassNamesForArchiveTo(classNames, filter); + } + } + } + + /** + * Add the names of all the classes discovered + * under the entry's directory and accepted by + * the specified filter to the specified collection. + */ + private void addClassNamesForDirectoryTo(Collection classNames, Filter filter) { + int start = this.canonicalFile.getAbsolutePath().length() + 1; + for (Iterator stream = this.classFilesForDirectory(); stream.hasNext(); ) { + String className = convertToClassName(stream.next().getAbsolutePath().substring(start)); + if (filter.accept(className)) { + classNames.add(className); + } + } + } + + /** + * Return an iterator on all the class files discovered + * under the entry's directory. + */ + private Iterator classFilesForDirectory() { + return new FilteringIterator(FileTools.filesInTree(this.canonicalFile)) { + @Override + protected boolean accept(File next) { + return Entry.this.fileNameMightBeForClassFile(next.getName()); + } + }; + } + + /** + * Add the names of all the classes discovered + * in the entry's archive file and accepted by the + * specified filter to the specified collection. + */ + private void addClassNamesForArchiveTo(Collection classNames, Filter filter) { + ZipFile zipFile = null; + try { + zipFile = new ZipFile(this.canonicalFile); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + for (Enumeration stream = zipFile.entries(); stream.hasMoreElements(); ) { + ZipEntry zipEntry = stream.nextElement(); + String zipEntryName = zipEntry.getName(); + if (this.fileNameMightBeForClassFile(zipEntryName)) { + String className = convertToClassName(zipEntryName); + if (filter.accept(className)) { + classNames.add(className); + } + } + } + try { + zipFile.close(); + } catch (IOException ex) { + return; + } + } + + /** + * Return whether the specified file might be a Java class file. + * The file name must at least end with ".class" and contain no spaces. + * (Neither class names nor package names may contain spaces.) + * Whether it actually is a class file will need to be determined by + * a class loader. + */ + boolean fileNameMightBeForClassFile(String name) { + return FileTools.extension(name).toLowerCase().equals(".class") //$NON-NLS-1$ + && (name.indexOf(' ') == -1); + } + + /** + * Return the names of all the classes discovered on the classpath. + * Just a bit more performant than {@link #getClassNames()}. + */ + public Iterator classNames() { + return this.classNames(Filter.Null.instance()); + } + + /** + * Return the names of all the classes discovered on the classpath + * that are accepted by the specified filter. + * Just a bit more performant than {@link #getClassNames(Filter)}. + */ + public Iterator classNames(Filter filter) { + if (this.canonicalFile.exists()) { + if (this.canonicalFile.isDirectory()) { + return this.classNamesForDirectory(filter); + } + if (fileIsArchive(this.canonicalFile)) { + return this.classNamesForArchive(filter); + } + } + return EmptyIterator.instance(); + } + + /** + * Return the names of all the classes discovered + * under the entry's directory and accepted by + * the specified filter. + */ + private Iterator classNamesForDirectory(Filter filter) { + return new FilteringIterator(this.classNamesForDirectory(), filter); + } + + /** + * Transform the class files to class names. + */ + private Iterator classNamesForDirectory() { + final int start = this.canonicalFile.getAbsolutePath().length() + 1; + return new TransformationIterator(this.classFilesForDirectory()) { + @Override + protected String transform(File f) { + return convertToClassName(f.getAbsolutePath().substring(start)); + } + }; + } + + /** + * Return the names of all the classes discovered + * in the entry's archive file and accepted by the + * specified filter. + */ + private Iterator classNamesForArchive(Filter filter) { + // we can't simply wrap iterators here because we need to close the archive file... + ZipFile zipFile = null; + try { + zipFile = new ZipFile(this.canonicalFile); + } catch (IOException ex) { + return EmptyIterator.instance(); + } + Collection classNames = new HashSet(zipFile.size()); + for (Enumeration stream = zipFile.entries(); stream.hasMoreElements(); ) { + ZipEntry zipEntry = stream.nextElement(); + String zipEntryName = zipEntry.getName(); + if (this.fileNameMightBeForClassFile(zipEntryName)) { + String className = convertToClassName(zipEntryName); + if (filter.accept(className)) { + classNames.add(className); + } + } + } + try { + zipFile.close(); + } catch (IOException ex) { + return EmptyIterator.instance(); + } + return classNames.iterator(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CollectionTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CollectionTools.java new file mode 100644 index 0000000000..aca8d2c354 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CollectionTools.java @@ -0,0 +1,1957 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Random; +import java.util.RandomAccess; +import java.util.TreeSet; +import java.util.Vector; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementIterator; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.SuperIteratorWrapper; + +/** + * {@link Collection}-related utility methods. + */ +public final class CollectionTools { + + // ********** add all ********** + + /** + * Add all the elements returned by the specified iterable + * to the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.addAll(Iterable iterable) + */ + public static boolean addAll(Collection collection, Iterable iterable) { + return addAll(collection, iterable.iterator()); + } + + /** + * Add all the elements returned by the specified iterable + * to the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.addAll(Iterable iterable) + */ + public static boolean addAll(Collection collection, Iterable iterable, int size) { + return addAll(collection, iterable.iterator(), size); + } + + /** + * Add all the elements returned by the specified iterator + * to the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.addAll(Iterator iterator) + */ + public static boolean addAll(Collection collection, Iterator iterator) { + return iterator.hasNext() ? addAll_(collection, iterator) : false; + } + + /** + * assume the iterator is not empty + */ + private static boolean addAll_(Collection collection, Iterator iterator) { + boolean modified = false; + while (iterator.hasNext()) { + modified |= collection.add(iterator.next()); + } + return modified; + } + + /** + * Add all the elements returned by the specified iterator + * to the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.addAll(Iterator iterator) + */ + public static boolean addAll(Collection collection, Iterator iterator, int size) { + return iterator.hasNext() ? collection.addAll(list(iterator, size)) : false; + } + + /** + * Add all the elements in the specified array + * to the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.addAll(Object[] array) + */ + public static boolean addAll(Collection collection, E... array) { + return (array.length == 0) ? false : addAll_(collection, array); + } + + /** + * assume the array is not empty + */ + private static boolean addAll_(Collection collection, E... array) { + boolean modified = false; + for (E element : array) { + modified |= collection.add(element); + } + return modified; + } + + /** + * Add all the elements returned by the specified iterable + * to the specified list at the specified index. + * Return whether the list changed as a result. + *

+ * List.addAll(Iterable iterable) + */ + public static boolean addAll(List list, int index, Iterable iterable) { + return addAll(list, index, iterable.iterator()); + } + + /** + * Add all the elements returned by the specified iterable + * to the specified list at the specified index. + * Return whether the list changed as a result. + *

+ * List.addAll(Iterable iterable) + */ + public static boolean addAll(List list, int index, Iterable iterable, int size) { + return addAll(list, index, iterable.iterator(), size); + } + + /** + * Add all the elements returned by the specified iterator + * to the specified list at the specified index. + * Return whether the list changed as a result. + *

+ * List.addAll(Iterator iterator) + */ + public static boolean addAll(List list, int index, Iterator iterator) { + return iterator.hasNext() ? list.addAll(index, list(iterator)) : false; + } + + /** + * Add all the elements returned by the specified iterator + * to the specified list at the specified index. + * Return whether the list changed as a result. + *

+ * List.addAll(Iterator iterator) + */ + public static boolean addAll(List list, int index, Iterator iterator, int size) { + return iterator.hasNext() ? list.addAll(index, list(iterator, size)) : false; + } + + /** + * Add all the elements in the specified array + * to the specified list at the specified index. + * Return whether the list changed as a result. + *

+ * List.addAll(Object[] array) + */ + public static boolean addAll(List list, int index, E... array) { + return (array.length == 0) ? false : list.addAll(index, Arrays.asList(array)); + } + + + // ********** bag ********** + + /** + * Return a bag corresponding to the specified enumeration. + *

+ * HashBag(Enumeration enumeration) + */ + public static HashBag bag(Enumeration enumeration) { + return bag(enumeration, new HashBag()); + } + + /** + * Return a bag corresponding to the specified enumeration. + * The specified enumeration size is a performance hint. + *

+ * HashBag(Enumeration enumeration) + */ + public static HashBag bag(Enumeration enumeration, int enumerationSize) { + return bag(enumeration, new HashBag(enumerationSize)); + } + + private static HashBag bag(Enumeration enumeration, HashBag bag) { + while (enumeration.hasMoreElements()) { + bag.add(enumeration.nextElement()); + } + return bag; + } + + /** + * Return a bag corresponding to the specified iterable. + *

+ * HashBag(Iterable iterable) + */ + public static HashBag bag(Iterable iterable) { + return bag(iterable.iterator()); + } + + /** + * Return a bag corresponding to the specified iterable. + * The specified iterable size is a performance hint. + *

+ * HashBag(Iterable iterable) + */ + public static HashBag bag(Iterable iterable, int iterableSize) { + return bag(iterable.iterator(), iterableSize); + } + + /** + * Return a bag corresponding to the specified iterator. + *

+ * HashBag(Iterator iterator) + */ + public static HashBag bag(Iterator iterator) { + return bag(iterator, new HashBag()); + } + + /** + * Return a bag corresponding to the specified iterator. + * The specified iterator size is a performance hint. + *

+ * HashBag(Iterator iterator) + */ + public static HashBag bag(Iterator iterator, int iteratorSize) { + return bag(iterator, new HashBag(iteratorSize)); + } + + private static HashBag bag(Iterator iterator, HashBag bag) { + while (iterator.hasNext()) { + bag.add(iterator.next()); + } + return bag; + } + + /** + * Return a bag corresponding to the specified array. + *

+ * HashBag(Object[] array) + */ + public static HashBag bag(E... array) { + int len = array.length; + HashBag bag = new HashBag(len); + for (E item : array) { + bag.add(item); + } + return bag; + } + + + // ********** collection ********** + + /** + * Return a collection corresponding to the specified enumeration. + */ + public static HashBag collection(Enumeration enumeration) { + return bag(enumeration); + } + + /** + * Return a collection corresponding to the specified enumeration. + * The specified enumeration size is a performance hint. + */ + public static HashBag collection(Enumeration enumeration, int enumerationSize) { + return bag(enumeration, enumerationSize); + } + + /** + * Return a collection corresponding to the specified iterable. + */ + public static HashBag collection(Iterable iterable) { + return collection(iterable.iterator()); + } + + /** + * Return a collection corresponding to the specified iterable. + * The specified iterable size is a performance hint. + */ + public static HashBag collection(Iterable iterable, int iterableSize) { + return collection(iterable.iterator(), iterableSize); + } + + /** + * Return a collection corresponding to the specified iterator. + */ + public static HashBag collection(Iterator iterator) { + return bag(iterator); + } + + /** + * Return a collection corresponding to the specified iterator. + * The specified iterator size is a performance hint. + */ + public static HashBag collection(Iterator iterator, int iteratorSize) { + return bag(iterator, iteratorSize); + } + + /** + * Return a collection corresponding to the specified array. + */ + public static HashBag collection(E... array) { + return bag(array); + } + + + // ********** contains ********** + + /** + * Return whether the specified enumeration contains the + * specified element. + *

+ * Enumeration.contains(Object o) + */ + public static boolean contains(Enumeration enumeration, Object value) { + if (value == null) { + while (enumeration.hasMoreElements()) { + if (enumeration.nextElement() == null) { + return true; + } + } + } else { + while (enumeration.hasMoreElements()) { + if (value.equals(enumeration.nextElement())) { + return true; + } + } + } + return false; + } + + /** + * Return whether the specified iterable contains the + * specified element. + *

+ * Iterable.contains(Object o) + */ + public static boolean contains(Iterable iterable, Object value) { + return contains(iterable.iterator(), value); + } + + /** + * Return whether the specified iterator contains the + * specified element. + *

+ * Iterator.contains(Object o) + */ + public static boolean contains(Iterator iterator, Object value) { + if (value == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + if (value.equals(iterator.next())) { + return true; + } + } + } + return false; + } + + + // ********** contains all ********** + + /** + * Return whether the specified collection contains all of the + * elements in the specified iterable. + *

+ * Collection.containsAll(Iterable iterable) + */ + public static boolean containsAll(Collection collection, Iterable iterable) { + return containsAll(collection, iterable.iterator()); + } + + /** + * Return whether the specified collection contains all of the + * elements in the specified iterator. + *

+ * Collection.containsAll(Iterator iterator) + */ + public static boolean containsAll(Collection collection, Iterator iterator) { + while (iterator.hasNext()) { + if ( ! collection.contains(iterator.next())) { + return false; + } + } + return true; + } + + /** + * Return whether the specified collection contains all of the + * elements in the specified array. + *

+ * Collection.containsAll(Object[] array) + */ + public static boolean containsAll(Collection collection, Object... array) { + for (int i = array.length; i-- > 0; ) { + if ( ! collection.contains(array[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified collection. + *

+ * Iterable.containsAll(Collection collection) + */ + public static boolean containsAll(Iterable iterable, Collection collection) { + return containsAll(iterable.iterator(), collection); + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified collection. + * The specified iterable size is a performance hint. + *

+ * Iterable.containsAll(Collection collection) + */ + public static boolean containsAll(Iterable iterable, int iterableSize, Collection collection) { + return containsAll(iterable.iterator(), iterableSize, collection); + } + + /** + * Return whether the specified iterable 1 contains all of the + * elements in the specified iterable 2. + *

+ * Iterable.containsAll(Iterable iterable) + */ + public static boolean containsAll(Iterable iterable1, Iterable iterable2) { + return containsAll(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Return whether the specified iterable 1 contains all of the + * elements in the specified iterable 2. + * The specified iterable 1 size is a performance hint. + *

+ * Iterable.containsAll(Iterable iterable) + */ + public static boolean containsAll(Iterable iterable1, int iterable1Size, Iterable iterable2) { + return containsAll(iterable1.iterator(), iterable1Size, iterable2.iterator()); + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified iterator. + *

+ * Iterable.containsAll(Iterator iterator) + */ + public static boolean containsAll(Iterable iterable, Iterator iterator) { + return containsAll(iterable.iterator(), iterator); + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified iterator. + * The specified iterable size is a performance hint. + *

+ * Iterable.containsAll(Iterator iterator) + */ + public static boolean containsAll(Iterable iterable, int iterableSize, Iterator iterator) { + return containsAll(iterable.iterator(), iterableSize, iterator); + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified array. + *

+ * Iterable.containsAll(Object[] array) + */ + public static boolean containsAll(Iterable iterable, Object... array) { + return containsAll(iterable.iterator(), array); + } + + /** + * Return whether the specified iterable contains all of the + * elements in the specified array. + * The specified iterable size is a performance hint. + *

+ * Iterable.containsAll(Object[] array) + */ + public static boolean containsAll(Iterable iterable, int iterableSize, Object... array) { + return containsAll(iterable.iterator(), iterableSize, array); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified collection. + *

+ * Iterator.containsAll(Collection collection) + */ + public static boolean containsAll(Iterator iterator, Collection collection) { + return set(iterator).containsAll(collection); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified collection. + * The specified iterator size is a performance hint. + *

+ * Iterator.containsAll(Collection collection) + */ + public static boolean containsAll(Iterator iterator, int iteratorSize, Collection collection) { + return set(iterator, iteratorSize).containsAll(collection); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified iterable. + *

+ * Iterator.containsAll(Iterable iterable) + */ + public static boolean containsAll(Iterator iterator, Iterable iterable) { + return containsAll(set(iterator), iterable); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified iterable. + * The specified iterator size is a performance hint. + *

+ * Iterator.containsAll(Iterable iterable) + */ + public static boolean containsAll(Iterator iterator, int iteratorSize, Iterable iterable) { + return containsAll(set(iterator, iteratorSize), iterable); + } + + /** + * Return whether the specified iterator 1 contains all of the + * elements in the specified iterator 2. + *

+ * Iterator.containsAll(Iterator iterator) + */ + public static boolean containsAll(Iterator iterator1, Iterator iterator2) { + return containsAll(set(iterator1), iterator2); + } + + /** + * Return whether the specified iterator 1 contains all of the + * elements in the specified iterator 2. + * The specified iterator 1 size is a performance hint. + *

+ * Iterator.containsAll(Iterator iterator) + */ + public static boolean containsAll(Iterator iterator1, int iterator1Size, Iterator iterator2) { + return containsAll(set(iterator1, iterator1Size), iterator2); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified array. + *

+ * Iterator.containsAll(Object[] array) + */ + public static boolean containsAll(Iterator iterator, Object... array) { + return containsAll(set(iterator), array); + } + + /** + * Return whether the specified iterator contains all of the + * elements in the specified array. + * The specified iterator size is a performance hint. + *

+ * Iterator.containsAll(Object[] array) + */ + public static boolean containsAll(Iterator iterator, int iteratorSize, Object... array) { + return containsAll(set(iterator, iteratorSize), array); + } + + + // ********** diff ********** + + /** + * Return the index of the first elements in the specified + * lists that are different, beginning at the end. + * If the lists are identical, return -1. + * If the lists are different sizes, return the index of the + * last element in the longer list. + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + *

+ * Collections.diffEnd(List list1, List list2) + */ + public static int diffEnd(List list1, List list2) { + return ArrayTools.diffEnd(list1.toArray(), list2.toArray()); + } + + /** + * Return the range of elements in the specified + * arrays that are different. + * If the arrays are identical, return [size, -1]. + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + *

+ * Collections.diffRange(List list1, List list2) + * @see #diffStart(List, List) + * @see #diffEnd(List, List) + */ + public static Range diffRange(List list1, List list2) { + return ArrayTools.diffRange(list1.toArray(), list2.toArray()); + } + + /** + * Return the index of the first elements in the specified + * lists that are different. If the lists are identical, return + * the size of the two lists (i.e. one past the last index). + * If the lists are different sizes and all the elements in + * the shorter list match their corresponding elements in + * the longer list, return the size of the shorter list + * (i.e. one past the last index of the shorter list). + * Use the elements' {@link Object#equals(Object)} method to compare the + * elements. + *

+ * Collections.diffStart(List list1, List list2) + */ + public static int diffStart(List list1, List list2) { + return ArrayTools.diffStart(list1.toArray(), list2.toArray()); + } + + + // ********** identity diff ********** + + /** + * Return the index of the first elements in the specified + * lists that are different, beginning at the end. + * If the lists are identical, return -1. + * If the lists are different sizes, return the index of the + * last element in the longer list. + * Use object identity to compare the elements. + *

+ * Collections.identityDiffEnd(List list1, List list2) + */ + public static int identityDiffEnd(List list1, List list2) { + return ArrayTools.identityDiffEnd(list1.toArray(), list2.toArray()); + } + + /** + * Return the range of elements in the specified + * arrays that are different. + * If the arrays are identical, return [size, -1]. + * Use object identity to compare the elements. + *

+ * Collections.identityDiffStart(List list1, List list2) + * @see #identityDiffStart(List, List) + * @see #identityDiffEnd(List, List) + */ + public static Range identityDiffRange(List list1, List list2) { + return ArrayTools.identityDiffRange(list1.toArray(), list2.toArray()); + } + + /** + * Return the index of the first elements in the specified + * lists that are different. If the lists are identical, return + * the size of the two lists (i.e. one past the last index). + * If the lists are different sizes and all the elements in + * the shorter list match their corresponding elements in + * the longer list, return the size of the shorter list + * (i.e. one past the last index of the shorter list). + * Use object identity to compare the elements. + *

+ * Collections.identityDiffStart(List list1, List list2) + */ + public static int identityDiffStart(List list1, List list2) { + return ArrayTools.identityDiffStart(list1.toArray(), list2.toArray()); + } + + + // ********** elements are equal ********** + + /** + * Return whether the specified iterables do not return the same elements + * in the same order. + */ + public static boolean elementsAreDifferent(Iterable iterable1, Iterable iterable2) { + return elementsAreDifferent(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Return whether the specified iterators do not return the same elements + * in the same order. + */ + public static boolean elementsAreDifferent(Iterator iterator1, Iterator iterator2) { + return ! elementsAreEqual(iterator1, iterator2); + } + + /** + * Return whether the specified iterables return equal elements + * in the same order. + *

+ * Iterable.elementsAreEqual(Iterable iterable) + */ + public static boolean elementsAreEqual(Iterable iterable1, Iterable iterable2) { + return elementsAreEqual(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Return whether the specified iterators return equal elements + * in the same order. + *

+ * Iterator.elementsAreEqual(Iterator iterator) + */ + public static boolean elementsAreEqual(Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext() && iterator2.hasNext()) { + if (Tools.valuesAreDifferent(iterator1.next(), iterator2.next())) { + return false; + } + } + return ! (iterator1.hasNext() || iterator2.hasNext()); + } + + + // ********** elements are identical ********** + + /** + * Return whether the specified iterables return the same elements. + *

+ * Iterable.identical(Iterable iterable) + */ + public static boolean elementsAreIdentical(Iterable iterable1, Iterable iterable2) { + return elementsAreIdentical(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Return whether the specified iterators return the same elements. + *

+ * Iterator.identical(Iterator iterator) + */ + public static boolean elementsAreIdentical(Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext() && iterator2.hasNext()) { + if (iterator1.next() != iterator2.next()) { + return false; + } + } + return ! (iterator1.hasNext() || iterator2.hasNext()); + } + + + // ********** get ********** + + /** + * Return the element corresponding to the specified index + * in the specified iterable. + *

+ * Iterable.get(int index) + */ + public static E get(Iterable iterable, int index) { + return get(iterable.iterator(), index); + } + + /** + * Return the element corresponding to the specified index + * in the specified iterator. + *

+ * Iterator.get(int index) + */ + public static E get(Iterator iterator, int index) { + int i = 0; + while (iterator.hasNext()) { + E next = iterator.next(); + if (i++ == index) { + return next; + } + } + throw new IndexOutOfBoundsException(String.valueOf(index) + ':' + String.valueOf(i)); + } + + + // ********** hash code ********** + + public static int hashCode(Iterable iterable) { + if (iterable == null) { + return 0; + } + int hash = 1; + for (Object element : iterable) { + hash = 31 * hash + (element == null ? 0 : element.hashCode()); + } + return hash; + } + + + // ********** index of ********** + + /** + * Return the index of the first occurrence of the + * specified element in the specified iterable; + * return -1 if there is no such index. + *

+ * Iterable.indexOf(Object o) + */ + public static int indexOf(Iterable iterable, Object value) { + return indexOf(iterable.iterator(), value); + } + + /** + * Return the index of the first occurrence of the + * specified element in the specified iterator; + * return -1 if there is no such index. + *

+ * Iterator.indexOf(Object o) + */ + public static int indexOf(Iterator iterator, Object value) { + if (value == null) { + for (int i = 0; iterator.hasNext(); i++) { + if (iterator.next() == null) { + return i; + } + } + } else { + for (int i = 0; iterator.hasNext(); i++) { + if (value.equals(iterator.next())) { + return i; + } + } + } + return -1; + } + + + // ********** insertion index of ********** + + /** + * Return an index of where the specified comparable object + * can be inserted into the specified sorted list and still keep + * the list sorted. If the specified sorted list is an instance of + * {@link RandomAccess} return the maximum insertion index; + * otherwise return the minimum insertion index. + */ + public static > int insertionIndexOf(List sortedList, Comparable value) { + if (sortedList instanceof RandomAccess) { + for (int i = sortedList.size(); i-- > 0; ) { + if (value.compareTo(sortedList.get(i)) >= 0) { + return i + 1; + } + } + return 0; + } + int i = 0; + for (E element : sortedList) { + if (value.compareTo(element) <= 0) { + return i; + } + i++; + } + return i; + } + + /** + * Return an index of where the specified comparable object + * can be inserted into the specified sorted list and still keep + * the list sorted. If the specified sorted list is an instance of + * {@link RandomAccess} return the maximum insertion index; + * otherwise return the minimum insertion index. + */ + public static int insertionIndexOf(List sortedList, E value, Comparator comparator) { + if (sortedList instanceof RandomAccess) { + for (int i = sortedList.size(); i-- > 0; ) { + if (comparator.compare(value, sortedList.get(i)) >= 0) { + return i + 1; + } + } + return 0; + } + int i = 0; + for (E element : sortedList) { + if (comparator.compare(value, element) <= 0) { + return i; + } + i++; + } + return i; + } + + + // ********** iterable/iterator ********** + + /** + * Return an iterable on the elements in the specified array. + *

+ * Arrays.iterable(Object[] array) + */ + public static Iterable iterable(E... array) { + return new ArrayIterable(array); + } + + /** + * Return an iterator on the elements in the specified array. + *

+ * Arrays.iterator(Object[] array) + */ + public static Iterator iterator(E... array) { + return new ArrayIterator(array); + } + + + // ********** last ********** + + /** + * Return the specified iterable's last element. + *

+ * Iterable.last() + * + * @exception java.util.NoSuchElementException iterable is empty. + */ + public static E last(Iterable iterable) { + return last(iterable.iterator()); + } + + /** + * Return the specified iterator's last element. + *

+ * Iterator.last() + * + * @exception java.util.NoSuchElementException iterator is empty. + */ + public static E last(Iterator iterator) { + E last; + do { + last = iterator.next(); + } while (iterator.hasNext()); + return last; + } + + + // ********** last index of ********** + + /** + * Return the index of the last occurrence of the + * specified element in the specified iterable; + * return -1 if there is no such index. + *

+ * Iterable.lastIndexOf(Object o) + */ + public static int lastIndexOf(Iterable iterable, Object value) { + return lastIndexOf(iterable.iterator(), value); + } + + /** + * Return the index of the last occurrence of the + * specified element in the specified iterable; + * return -1 if there is no such index. + * The specified iterable size is a performance hint. + *

+ * Iterable.lastIndexOf(Object o) + */ + public static int lastIndexOf(Iterable iterable, int iterableSize, Object value) { + return lastIndexOf(iterable.iterator(), iterableSize, value); + } + + /** + * Return the index of the last occurrence of the + * specified element in the specified iterator; + * return -1 if there is no such index. + *

+ * Iterator.lastIndexOf(Object o) + */ + public static int lastIndexOf(Iterator iterator, Object value) { + return iterator.hasNext() ? list(iterator).lastIndexOf(value) : -1; + } + + /** + * Return the index of the last occurrence of the + * specified element in the specified iterator; + * return -1 if there is no such index. + * The specified iterator size is a performance hint. + *

+ * Iterator.lastIndexOf(Object o) + */ + public static int lastIndexOf(Iterator iterator, int iteratorSize, Object value) { + return iterator.hasNext() ? list(iterator, iteratorSize).lastIndexOf(value) : -1; + } + + + // ********** list ********** + + /** + * Return a list corresponding to the specified iterable. + *

+ * Iterable.toList() + */ + public static ArrayList list(Iterable iterable) { + return list(iterable.iterator()); + } + + /** + * Return a list corresponding to the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.toList() + */ + public static ArrayList list(Iterable iterable, int iterableSize) { + return list(iterable.iterator(), iterableSize); + } + + /** + * Return a list corresponding to the specified iterator. + *

+ * Iterator.toList() + */ + public static ArrayList list(Iterator iterator) { + return list(iterator, new ArrayList()); + } + + /** + * Return a list corresponding to the specified iterator. + * The specified iterator size is a performance hint. + *

+ * Iterator.toList() + */ + public static ArrayList list(Iterator iterator, int iteratorSize) { + return list(iterator, new ArrayList(iteratorSize)); + } + + private static ArrayList list(Iterator iterator, ArrayList list) { + while (iterator.hasNext()) { + list.add(iterator.next()); + } + return list; + } + + /** + * Return a list corresponding to the specified array. + * Unlike {@link Arrays#asList(Object[])}, the list + * is modifiable and is not backed by the array. + */ + public static ArrayList list(E... array) { + return new ArrayList(Arrays.asList(array)); + } + + /** + * Return a list iterator for the specified array. + *

+ * Arrays.listIterator(Object[] array) + */ + public static ListIterator listIterator(E... array) { + return listIterator(array, 0); + } + + /** + * Return a list iterator for the specified array + * starting at the specified position in the array. + *

+ * Arrays.listIterator(Object[] array, int index) + */ + public static ListIterator listIterator(E[] array, int start) { + return listIterator(array, start, array.length - start); + } + + /** + * Return a list iterator for the specified array + * starting at the specified position in the array. + *

+ * Arrays.listIterator(Object[] array, int index, int length) + */ + public static ListIterator listIterator(E[] array, int start, int length) { + return new ArrayListIterator(array, start, length); + } + + + // ********** move ********** + + /** + * Move an element from the specified source index to the specified target + * index. Return the altered list. + *

+ * List.move(int targetIndex, int sourceIndex) + */ + public static List move(List list, int targetIndex, int sourceIndex) { + return (targetIndex == sourceIndex) ? list : move_(list, targetIndex, sourceIndex); + } + + /** + * assume targetIndex != sourceIndex + */ + private static List move_(List list, int targetIndex, int sourceIndex) { + if (list instanceof RandomAccess) { + // move elements, leaving the list in place + E temp = list.get(sourceIndex); + if (targetIndex < sourceIndex) { + for (int i = sourceIndex; i-- > targetIndex; ) { + list.set(i + 1, list.get(i)); + } + } else { + for (int i = sourceIndex; i < targetIndex; i++) { + list.set(i, list.get(i + 1)); + } + } + list.set(targetIndex, temp); + } else { + // remove the element and re-add it at the target index + list.add(targetIndex, list.remove(sourceIndex)); + } + return list; + } + + /** + * Move elements from the specified source index to the specified target + * index. Return the altered list. + *

+ * List.move(int targetIndex, int sourceIndex, int length) + */ + public static List move(List list, int targetIndex, int sourceIndex, int length) { + if ((targetIndex == sourceIndex) || (length == 0)) { + return list; + } + if (length == 1) { + return move_(list, targetIndex, sourceIndex); + } + if (list instanceof RandomAccess) { + // move elements, leaving the list in place + ArrayList temp = new ArrayList(list.subList(sourceIndex, sourceIndex + length)); + if (targetIndex < sourceIndex) { + for (int i = sourceIndex; i-- > targetIndex; ) { + list.set(i + length, list.get(i)); + } + } else { + for (int i = sourceIndex; i < targetIndex; i++) { + list.set(i, list.get(i + length)); + } + } + for (int i = 0; i < length; i++) { + list.set(targetIndex + i, temp.get(i)); + } + } else { + // remove the elements and re-add them at the target index + list.addAll(targetIndex, removeElementsAtIndex(list, sourceIndex, length)); + } + return list; + } + + + // ********** remove all ********** + + /** + * Remove all the elements returned by the specified iterable + * from the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.removeAll(Iterable iterable) + */ + public static boolean removeAll(Collection collection, Iterable iterable) { + return removeAll(collection, iterable.iterator()); + } + + /** + * Remove all the elements returned by the specified iterable + * from the specified collection. + * Return whether the collection changed as a result. + * The specified iterable size is a performance hint. + *

+ * Collection.removeAll(Iterable iterable) + */ + public static boolean removeAll(Collection collection, Iterable iterable, int iterableSize) { + return removeAll(collection, iterable.iterator(), iterableSize); + } + + /** + * Remove all the elements returned by the specified iterator + * from the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.removeAll(Iterator iterator) + */ + public static boolean removeAll(Collection collection, Iterator iterator) { + return iterator.hasNext() ? collection.removeAll(set(iterator)) : false; + } + + /** + * Remove all the elements returned by the specified iterator + * from the specified collection. + * Return whether the collection changed as a result. + * The specified iterator size is a performance hint. + *

+ * Collection.removeAll(Iterator iterator) + */ + public static boolean removeAll(Collection collection, Iterator iterator, int iteratorSize) { + return iterator.hasNext() ? collection.removeAll(set(iterator, iteratorSize)) : false; + } + + /** + * Remove all the elements in the specified array + * from the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.removeAll(Object[] array) + */ + public static boolean removeAll(Collection collection, Object... array) { + return (array.length == 0) ? false : collection.removeAll(set(array)); + } + + + // ********** remove all occurrences ********** + + /** + * Remove all occurrences of the specified element + * from the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.removeAllOccurrences(Object value) + */ + public static boolean removeAllOccurrences(Collection collection, Object value) { + boolean modified = false; + Iterator stream = collection.iterator(); + if (value == null) { + while (stream.hasNext()) { + if (stream.next() == null) { + stream.remove(); + modified = true; + } + } + } else { + while (stream.hasNext()) { + if (value.equals(stream.next())) { + stream.remove(); + modified = true; + } + } + } + return modified; + } + + + // ********** remove elements at index ********** + + /** + * Remove the elements at the specified index. + * Return the removed elements. + *

+ * List.remove(int index, int length) + */ + public static ArrayList removeElementsAtIndex(List list, int index, int length) { + List subList = list.subList(index, index + length); + ArrayList result = new ArrayList(subList); + subList.clear(); + return result; + } + + + // ********** remove duplicate elements ********** + + /** + * Remove any duplicate elements from the specified list, + * while maintaining the order. + * Return whether the list changed as a result. + */ + public static boolean removeDuplicateElements(List list) { + int size = list.size(); + if ((size == 0) || (size == 1)) { + return false; + } + return removeDuplicateElements(list, size); + } + + /** + * assume list is non-empty + */ + static boolean removeDuplicateElements(List list, int size) { + LinkedHashSet temp = new LinkedHashSet(size); // take advantage of hashed look-up + boolean modified = false; + for (E item : list) { + if ( ! temp.add(item)) { + modified = true; // duplicate item + } + } + if (modified) { + int i = 0; + for (E e : temp) { + list.set(i, e); + i++; + } + int tempSize = temp.size(); + for (i = list.size(); i-- > tempSize; ) { + list.remove(i); // pull off the end + } + } + return modified; + } + + + // ********** retain all ********** + + /** + * Retain only the elements in the specified iterable + * in the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.retainAll(Iterable iterable) + */ + public static boolean retainAll(Collection collection, Iterable iterable) { + return retainAll(collection, iterable.iterator()); + } + + /** + * Retain only the elements in the specified iterable + * in the specified collection. + * Return whether the collection changed as a result. + * The specified iterable size is a performance hint. + *

+ * Collection.retainAll(Iterable iterable) + */ + public static boolean retainAll(Collection collection, Iterable iterable, int iterableSize) { + return retainAll(collection, iterable.iterator(), iterableSize); + } + + /** + * Retain only the elements in the specified iterator + * in the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.retainAll(Iterator iterator) + */ + public static boolean retainAll(Collection collection, Iterator iterator) { + if (iterator.hasNext()) { + return collection.retainAll(set(iterator)); + } + if (collection.isEmpty()) { + return false; + } + collection.clear(); + return true; + } + + /** + * Retain only the elements in the specified iterator + * in the specified collection. + * Return whether the collection changed as a result. + * The specified iterator size is a performance hint. + *

+ * Collection.retainAll(Iterator iterator) + */ + public static boolean retainAll(Collection collection, Iterator iterator, int iteratorSize) { + if (iterator.hasNext()) { + return collection.retainAll(set(iterator, iteratorSize)); + } + if (collection.isEmpty()) { + return false; + } + collection.clear(); + return true; + } + + /** + * Retain only the elements in the specified array + * in the specified collection. + * Return whether the collection changed as a result. + *

+ * Collection.retainAll(Object[] array) + */ + public static boolean retainAll(Collection collection, Object... array) { + if (array.length > 0) { + return collection.retainAll(set(array)); + } + if (collection.isEmpty()) { + return false; + } + collection.clear(); + return true; + } + + + // ********** reverse list ********** + + /** + * Return a list with entries in reverse order from those + * returned by the specified iterable. + *

+ * Iterable.reverseList() + */ + public static ArrayList reverseList(Iterable iterable) { + return reverseList(iterable.iterator()); + } + + /** + * Return a list with entries in reverse order from those + * returned by the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.reverseList() + */ + public static ArrayList reverseList(Iterable iterable, int iterableSize) { + return reverseList(iterable.iterator(), iterableSize); + } + + /** + * Return a list with entries in reverse order from those + * returned by the specified iterator. + *

+ * Iterator.reverseList() + */ + public static ArrayList reverseList(Iterator iterator) { + return (ArrayList) reverse(list(iterator)); + } + + /** + * Return a list with entries in reverse order from those + * returned by the specified iterator. + * The specified iterator size is a performance hint. + *

+ * Iterator.reverseList() + */ + public static ArrayList reverseList(Iterator iterator, int size) { + return (ArrayList) reverse(list(iterator, size)); + } + + + // ********** rotate ********** + + /** + * Return the list after it has been "rotated" by one position. + *

+ * List.rotate() + */ + public static List rotate(List list) { + return rotate(list, 1); + } + + + // ********** set ********** + + /** + * Return a set corresponding to the specified iterable. + *

+ * HashSet(Iterable iterable) + */ + public static HashSet set(Iterable iterable) { + return set(iterable.iterator()); + } + + /** + * Return a set corresponding to the specified iterable. + * The specified iterable size is a performance hint. + *

+ * HashSet(Iterable iterable) + */ + public static HashSet set(Iterable iterable, int iterableSize) { + return set(iterable.iterator(), iterableSize); + } + + /** + * Return a set corresponding to the specified iterator. + *

+ * HashSet(Iterator iterator) + */ + public static HashSet set(Iterator iterator) { + return set(iterator, new HashSet()); + } + + /** + * Return a set corresponding to the specified iterator. + * The specified iterator size is a performance hint. + *

+ * HashSet(Iterator iterator) + */ + public static HashSet set(Iterator iterator, int iteratorSize) { + return set(iterator, new HashSet(iteratorSize)); + } + + private static HashSet set(Iterator iterator, HashSet set) { + while (iterator.hasNext()) { + set.add(iterator.next()); + } + return set; + } + + /** + * Return a set corresponding to the specified array. + *

+ * HashSet(Object[] array) + */ + public static HashSet set(E... array) { + HashSet set = new HashSet(array.length); + for (int i = array.length; i-- > 0;) { + set.add(array[i]); + } + return set; + } + + + // ********** singleton iterator ********** + + /** + * Return an iterator that returns only the single, + * specified object. + *

+ * Object.toIterator() + */ + public static Iterator singletonIterator(E value) { + return new SingleElementIterator(value); + } + + /** + * Return a list iterator that returns only the single, + * specified object. + *

+ * Object.toListIterator() + */ + public static ListIterator singletonListIterator(E value) { + return new SingleElementListIterator(value); + } + + + // ********** size ********** + + /** + * Return the number of elements returned by the specified iterable. + *

+ * Iterable.size() + */ + public static int size(Iterable iterable) { + return size(iterable.iterator()); + } + + /** + * Return the number of elements returned by the specified iterator. + *

+ * Iterator.size() + */ + public static int size(Iterator iterator) { + int size = 0; + while (iterator.hasNext()) { + iterator.next(); + size++; + } + return size; + } + + /** + * Return whether the specified iterable is empty + * (Shortcuts the iterator rather than calculating the entire size) + */ + public static boolean isEmpty(Iterable iterable) { + return isEmpty(iterable.iterator()); + } + + /** + * Return whether the specified iterator is empty + * (Shortcuts the iterator rather than calculating the entire size) + */ + public static boolean isEmpty(Iterator iterator) { + return ! iterator.hasNext(); + } + + + // ********** sort ********** + + /** + * Return an iterable containing the sorted elements of the specified iterable. + *

+ * Iterable.sort() + */ + public static > Iterable sort(Iterable iterable) { + return sort(iterable, null); + } + + /** + * Return an iterable containing the sorted elements of the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.sort() + */ + public static > Iterable sort(Iterable iterable, int iterableSize) { + return sort(iterable, null, iterableSize); + } + + /** + * Return an iterable containing the sorted elements of the specified iterable. + *

+ * Iterable.sort(Comparator comparator) + */ + public static Iterable sort(Iterable iterable, Comparator comparator) { + return sort(list(iterable), comparator); + } + + /** + * Return an iterable containing the sorted elements of the specified iterable. + * The specified iterable size is a performance hint. + *

+ * Iterable.sort(Comparator comparator) + */ + public static Iterable sort(Iterable iterable, Comparator comparator, int iterableSize) { + return sort(list(iterable, iterableSize), comparator); + } + + /** + * Return the iterator after it has been "sorted". + *

+ * Iterator.sort() + */ + public static > ListIterator sort(Iterator iterator) { + return sort(iterator, null); + } + + /** + * Return the iterator after it has been "sorted". + * The specified iterator size is a performance hint. + *

+ * Iterator.sort() + */ + public static > ListIterator sort(Iterator iterator, int iteratorSize) { + return sort(iterator, null, iteratorSize); + } + + /** + * Return the iterator after it has been "sorted". + *

+ * Iterator.sort(Comparator comparator) + */ + public static ListIterator sort(Iterator iterator, Comparator comparator) { + return sort(list(iterator), comparator).listIterator(); + } + + /** + * Return the iterator after it has been "sorted". + * The specified iterator size is a performance hint. + *

+ * Iterator.sort(Comparator comparator) + */ + public static ListIterator sort(Iterator iterator, Comparator comparator, int iteratorSize) { + return sort(list(iterator, iteratorSize), comparator).listIterator(); + } + + + // ********** sorted set ********** + + /** + * Return a sorted set corresponding to the specified iterable. + *

+ * TreeSet(Iterable iterable) + */ + public static > TreeSet sortedSet(Iterable iterable) { + return sortedSet(iterable.iterator()); + } + + /** + * Return a sorted set corresponding to the specified iterable. + * The specified iterable size is a performance hint. + *

+ * TreeSet(Iterable iterable) + */ + public static > TreeSet sortedSet(Iterable iterable, int iterableSize) { + return sortedSet(iterable.iterator(), iterableSize); + } + + /** + * Return a sorted set corresponding to the specified iterable + * and comparator. + *

+ * TreeSet(Iterable iterable, Comparator c) + */ + public static TreeSet sortedSet(Iterable iterable, Comparator comparator) { + return sortedSet(iterable.iterator(), comparator); + } + + /** + * Return a sorted set corresponding to the specified iterable + * and comparator. + * The specified iterable size is a performance hint. + *

+ * TreeSet(Iterable iterable, Comparator c) + */ + public static TreeSet sortedSet(Iterable iterable, Comparator comparator, int iterableSize) { + return sortedSet(iterable.iterator(), comparator, iterableSize); + } + + /** + * Return a sorted set corresponding to the specified iterator. + *

+ * TreeSet(Iterator iterator) + */ + public static > TreeSet sortedSet(Iterator iterator) { + return sortedSet(iterator, null); + } + + /** + * Return a sorted set corresponding to the specified iterator. + * The specified iterator size is a performance hint. + *

+ * TreeSet(Iterator iterator) + */ + public static > TreeSet sortedSet(Iterator iterator, int iteratorSize) { + return sortedSet(iterator, null, iteratorSize); + } + + /** + * Return a sorted set corresponding to the specified iterator + * and comparator. + *

+ * TreeSet(Iterator iterator, Comparator c) + */ + public static TreeSet sortedSet(Iterator iterator, Comparator comparator) { + return sortedSet(list(iterator), comparator); + } + + /** + * Return a sorted set corresponding to the specified iterator + * and comparator. + * The specified iterator size is a performance hint. + *

+ * TreeSet(Iterator iterator, Comparator c) + */ + public static TreeSet sortedSet(Iterator iterator, Comparator comparator, int iteratorSize) { + return sortedSet(list(iterator, iteratorSize), comparator); + } + + private static TreeSet sortedSet(List list, Comparator comparator) { + TreeSet sortedSet = new TreeSet(comparator); + sortedSet.addAll(list); + return sortedSet; + } + + /** + * Return a sorted set corresponding to the specified array. + *

+ * TreeSet(Object[] array) + */ + public static > TreeSet sortedSet(E... array) { + return sortedSet(array, null); + } + + /** + * Return a sorted set corresponding to the specified array + * and comparator. + *

+ * TreeSet(Object[] array, Comparator c) + */ + public static TreeSet sortedSet(E[] array, Comparator comparator) { + TreeSet sortedSet = new TreeSet(comparator); + sortedSet.addAll(Arrays.asList(array)); + return sortedSet; + } + + + // ********** Old School Vector ********** + + /** + * Return a vector corresponding to the specified iterable. + * This is useful for legacy code that requires a {@link Vector}. + *

+ * Vector(Iterable iterable) + */ + public static Vector vector(Iterable iterable) { + return vector(iterable.iterator()); + } + + /** + * Return a vector corresponding to the specified iterable. + * This is useful for legacy code that requires a {@link Vector}. + *

+ * Vector(Iterable iterable, int size) + */ + public static Vector vector(Iterable iterable, int size) { + return vector(iterable.iterator(), size); + } + + /** + * Return a vector corresponding to the specified iterator. + * This is useful for legacy code that requires a {@link Vector}. + *

+ * Vector(Iterator iterator) + */ + public static Vector vector(Iterator iterator) { + return vector(iterator, new Vector()); + } + + /** + * Return a vector corresponding to the specified iterator. + * This is useful for legacy code that requires a {@link Vector}. + *

+ * Vector(Iterator iterator, int size) + */ + public static Vector vector(Iterator iterator, int size) { + return vector(iterator, new Vector(size)); + } + + private static Vector vector(Iterator iterator, Vector v) { + while (iterator.hasNext()) { + v.addElement(iterator.next()); + } + return v; + } + + /** + * Return a vector corresponding to the specified array. + * This is useful for legacy code that requires a {@link Vector}. + *

+ * Vector(Object... array) + */ + public static Vector vector(E... array) { + Vector v = new Vector(array.length); + for (E item : array) { + v.addElement(item); + } + return v; + } + + + // ********** single-use Iterable ********** + + /** + * Return a one-use {@link Iterable} for the specified {@link Iterator}. + * Throw an {@link IllegalStateException} if {@link Iterable#iterator()} + * is called more than once. + * As such, this utility should only be used in one-use situations, such as + * a foreach loop. + */ + public static Iterable iterable(Iterator iterator) { + return new SingleUseIterable(iterator); + } + + /** + * This is a one-time use iterable that can return a single iterator. + * Once the iterator is returned the iterable is no longer valid. + * As such, this utility should only be used in one-time use situations, + * such as a 'for-each' loop. + */ + public static class SingleUseIterable implements Iterable { + private Iterator iterator; + + public SingleUseIterable(Iterator iterator) { + super(); + if (iterator == null) { + throw new NullPointerException(); + } + this.iterator = new SuperIteratorWrapper(iterator); + } + + public Iterator iterator() { + if (this.iterator == null) { + throw new IllegalStateException("This method has already been called."); //$NON-NLS-1$ + } + Iterator result = this.iterator; + this.iterator = null; + return result; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + + } + + + // ********** java.util.Collections enhancements ********** + + /** + * Return the destination list after the source list has been copied into it. + * @see Collections#copy(List, List) + */ + public static List copy(List dest, List src) { + Collections.copy(dest, src); + return dest; + } + + /** + * Return the list after it has been "filled". + * @see Collections#fill(List, Object) + */ + public static List fill(List list, E value) { + Collections.fill(list, value); + return list; + } + + /** + * Return the list after it has been "reversed". + * @see Collections#reverse(List) + */ + public static List reverse(List list) { + Collections.reverse(list); + return list; + } + + /** + * Return the list after it has been "rotated". + * @see Collections#rotate(List, int) + */ + public static List rotate(List list, int distance) { + Collections.rotate(list, distance); + return list; + } + + /** + * Return the list after it has been "shuffled". + * @see Collections#shuffle(List) + */ + public static List shuffle(List list) { + Collections.shuffle(list); + return list; + } + + /** + * Return the list after it has been "shuffled". + * @see Collections#shuffle(List, Random) + */ + public static List shuffle(List list, Random random) { + Collections.shuffle(list, random); + return list; + } + + /** + * Return the list after it has been "sorted". + * NB: The list is sorted in place as a side-effect. + * @see Collections#sort(List) + */ + public static > List sort(List list) { + Collections.sort(list); + return list; + } + + /** + * Return the list after it has been "sorted". + * NB: The list is sorted in place as a side-effect. + * @see Collections#sort(List, Comparator) + */ + public static List sort(List list, Comparator comparator) { + Collections.sort(list, comparator); + return list; + } + + /** + * Return the list after the specified elements have been "swapped". + * @see Collections#swap(List, int, int) + */ + public static List swap(List list, int i, int j) { + Collections.swap(list, i, j); + return list; + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private CollectionTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CommandRunnable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CommandRunnable.java new file mode 100644 index 0000000000..8f66ef255c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CommandRunnable.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Wrap a Command so it can be used as a Runnable. + */ +public class CommandRunnable implements Runnable { + protected final Command command; + + public CommandRunnable(Command command) { + super(); + if (command == null) { + throw new NullPointerException(); + } + this.command = command; + } + + public void run() { + this.command.execute(); + } + + @Override + public String toString() { + return "Runnable[" + this.command.toString() +']'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeCommand.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeCommand.java new file mode 100644 index 0000000000..fce6610235 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeCommand.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; + +/** + * CompositeCommand provides support for treating a collection of + * {@link Command}s as a single command. + */ +public class CompositeCommand + implements Command +{ + private final Iterable commands; + + public CompositeCommand(Command... commands) { + this(new ArrayIterable(commands)); + } + + public CompositeCommand(Iterable commands) { + super(); + this.commands = commands; + } + + public void execute() { + for (Command command : this.commands) { + command.execute(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.commands); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeException.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeException.java new file mode 100644 index 0000000000..dcb51283cd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeException.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Collection; + +/** + * Provide a way for multiple exceptions to be packaged and reported. + */ +public class CompositeException + extends RuntimeException +{ + private final Throwable[] exceptions; + private static final long serialVersionUID = 1L; + + + /** + * The specified exceptions list must not be empty. + */ + public CompositeException(Collection exceptions) { + this(exceptions.toArray(new Throwable[exceptions.size()])); + } + + /** + * The specified exceptions list must not be empty. + */ + public CompositeException(Throwable[] exceptions) { + // provide a list of the nested exceptions and + // grab the first exception and make it the "cause" + super(buildMessage(exceptions)); + this.exceptions = exceptions; + } + + public Throwable[] getExceptions() { + return this.exceptions; + } + + private static String buildMessage(Throwable[] exceptions) { + StringBuilder sb = new StringBuilder(); + sb.append(exceptions.length); + sb.append(" exceptions: "); //$NON-NLS-1$ + sb.append('['); + for (Throwable ex : exceptions) { + sb.append(ex.getClass().getSimpleName()); + sb.append(", "); //$NON-NLS-1$ + } + sb.setLength(sb.length() - 2); // chop off trailing comma + sb.append(']'); + return sb.toString(); + } + + @Override + public void printStackTrace(PrintStream s) { + synchronized (s) { + s.println(this); + for (StackTraceElement element : this.getStackTrace()) { + s.print("\tat "); //$NON-NLS-1$ + s.println(element); + } + int i = 1; + for (Throwable ex : this.exceptions) { + s.print("Nested exception "); //$NON-NLS-1$ + s.print(i++); + s.print(": "); //$NON-NLS-1$ + ex.printStackTrace(s); + } + } + } + + @Override + public void printStackTrace(PrintWriter s) { + synchronized (s) { + s.println(this); + for (StackTraceElement element : this.getStackTrace()) { + s.print("\tat "); //$NON-NLS-1$ + s.println(element); + } + int i = 1; + for (Throwable ex : this.exceptions) { + s.print("Nested exception "); //$NON-NLS-1$ + s.print(i++); + s.print(": "); //$NON-NLS-1$ + ex.printStackTrace(s); + } + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ConsumerThreadCoordinator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ConsumerThreadCoordinator.java new file mode 100644 index 0000000000..8dc34e968d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ConsumerThreadCoordinator.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.util.Vector; +import java.util.concurrent.ThreadFactory; + +/** + * A ConsumerThreadCoordinator controls the creation, + * starting, and stopping of a general purpose "consumer" thread. Construct + * the coordinator with a {@link Consumer} that both waits for the producer + * to "produce" something to "consume" and, once the wait is over, + * "consumes" whatever is available. + *

+ * NB: The client-supplied consumer should handle any + * exception appropriately (e.g. log the exception and return gracefully) so + * the thread can continue executing. + */ +public class ConsumerThreadCoordinator { + /** + * The runnable passed to the consumer thread each time it is built. + */ + private final Runnable runnable; + + /** + * Client-supplied thread factory. Defaults to a straightforward + * implementation. + */ + private final ThreadFactory threadFactory; + + /** + * Optional, client-supplied name for the consumer thread. + * If null, the JDK assigns a name. + */ + private final String threadName; + + /** + * The consumer is executed on this thread. A new thread is built + * for every start/stop cycle (since a thread cannot be started more than + * once). + */ + private volatile Thread thread; + + /** + * A list of the uncaught exceptions thrown by the consumer + * during the current start/stop cycle. + */ + final Vector exceptions = new Vector(); + + + // ********** construction ********** + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Use simple JDK thread(s) for the consumer thread(s). + * Allow the consumer thread(s) to be assigned JDK-generated names. + */ + public ConsumerThreadCoordinator(Consumer consumer) { + this(consumer, SimpleThreadFactory.instance()); + } + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Use the specified thread factory to construct the consumer thread(s). + * Allow the consumer thread(s) to be assigned JDK-generated names. + */ + public ConsumerThreadCoordinator(Consumer consumer, ThreadFactory threadFactory) { + this(consumer, threadFactory, null); + } + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Assign the consumer thread(s) the specified name. + * Use simple JDK thread(s) for the consumer thread(s). + */ + public ConsumerThreadCoordinator(Consumer consumer, String threadName) { + this(consumer, SimpleThreadFactory.instance(), threadName); + } + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Use the specified thread factory to construct the consumer thread(s). + * Assign the consumer thread(s) the specified name. + */ + public ConsumerThreadCoordinator(Consumer consumer, ThreadFactory threadFactory, String threadName) { + super(); + this.runnable = this.buildRunnable(consumer); + this.threadFactory = threadFactory; + this.threadName = threadName; + } + + private Runnable buildRunnable(Consumer consumer) { + return new RunnableConsumer(consumer); + } + + + // ********** Lifecycle support ********** + + /** + * Build and start the consumer thread. + */ + public synchronized void start() { + if (this.thread != null) { + throw new IllegalStateException("Not stopped."); //$NON-NLS-1$ + } + this.thread = this.buildThread(); + this.thread.start(); + } + + private Thread buildThread() { + Thread t = this.threadFactory.newThread(this.runnable); + if (this.threadName != null) { + t.setName(this.threadName); + } + return t; + } + + /** + * Interrupt the consumer thread so that it stops executing at the + * end of its current iteration. Suspend the current thread until + * the consumer thread is finished executing. If any uncaught + * exceptions were thrown while the consumer thread was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public synchronized void stop() { + if (this.thread == null) { + throw new IllegalStateException("Not started."); //$NON-NLS-1$ + } + this.thread.interrupt(); + try { + this.thread.join(); + } catch (InterruptedException ex) { + // the thread that called #stop() was interrupted while waiting to + // join the consumer thread - ignore; + // 'thread' is still "interrupted", so its #run() loop will still stop + // after its current execution - we just won't wait around for it... + } + this.thread = null; + + if (this.exceptions.size() > 0) { + Throwable[] temp = this.exceptions.toArray(new Throwable[this.exceptions.size()]); + this.exceptions.clear(); + throw new CompositeException(temp); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.thread); + } + + + // ********** consumer thread runnable ********** + + /** + * This implementation of {@link Runnable} is a long-running consumer that + * will repeatedly execute the consumer {@link Consumer#execute()} method. + * With each iteration, the consumer thread will wait + * until the other consumer method, {@link Consumer#waitForProducer()}, allows the + * consumer thread to proceed (i.e. there is something for the consumer to + * consume). Once {@link Consumer#execute()} is finished, the thread will quiesce + * until {@link Consumer#waitForProducer()} returns again. + * Stop the thread by calling {@link Thread#interrupt()}. + */ + private class RunnableConsumer + implements Runnable + { + /** + * Client-supplied consumer that controls waiting for something to consume + * and the consuming itself. + */ + private final Consumer consumer; + + RunnableConsumer(Consumer consumer) { + super(); + this.consumer = consumer; + } + + /** + * Loop while this thread has not been interrupted by another thread. + * In each loop: Pause execution until {@link Consumer#waitForProducer()} + * allows us to proceed. + *

+ * If this thread is interrupted during {@link Consumer#execute()}, + * the call to {@link Thread#interrupted()} will stop the loop. If this thread is + * interrupted during the call to {@link Consumer#waitForProducer()}, + * we will catch the {@link InterruptedException} and stop the loop also. + */ + public void run() { + while ( ! Thread.interrupted()) { + try { + this.consumer.waitForProducer(); + } catch (InterruptedException ex) { + // we were interrupted while waiting, must be Quittin' Time + return; + } + this.execute(); + } + } + + /** + * Execute the consumer {@link Consumer#execute()} method. + * Do not allow any unhandled exceptions to kill the thread. + * Store them up for later pain. + * @see ConsumerThreadCoordinator#stop() + */ + private void execute() { + try { + this.execute_(); + } catch (Throwable ex) { + ConsumerThreadCoordinator.this.exceptions.add(ex); + } + } + + /** + * Subclass-implemented behavior: consume stuff. + */ + private void execute_() { + this.consumer.execute(); + } + + } + + + // ********** consumer interface ********** + + /** + * Interface implemented by clients that controls:

    + *
  • when the consumer thread suspends, waiting for something to consume + *
  • the consuming of whatever is being produced + *
+ */ + public interface Consumer { + /** + * Wait for something to consume. + * Throw an {@link InterruptedException} if the thread is interrupted. + */ + void waitForProducer() throws InterruptedException; + + /** + * Consume whatever is currently available. + */ + void execute(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ExceptionHandler.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ExceptionHandler.java new file mode 100644 index 0000000000..9c32f555a9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ExceptionHandler.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Simple interface for allowing clients to pass an exception handler to a + * service (e.g. to log the exception). This is particularly helpful if the + * service executes on another, possibly inaccessible, thread. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ExceptionHandler { + + /** + * The specified exception was thrown. Handle it appropriately. + */ + void handleException(Throwable t); + + /** + * Singleton implementation of the exception handler interface that does + * nothing with the exception. + */ + final class Null implements ExceptionHandler, Serializable { + public static final ExceptionHandler INSTANCE = new Null(); + public static ExceptionHandler instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void handleException(Throwable t) { + // do nothing + } + @Override + public String toString() { + return "ExceptionHandler.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * Singleton implementation of the exception handler interface that + * wraps the exception in a runtime exception and throws it. + */ + final class Runtime implements ExceptionHandler, Serializable { + public static final ExceptionHandler INSTANCE = new Runtime(); + public static ExceptionHandler instance() { + return INSTANCE; + } + // ensure single instance + private Runtime() { + super(); + } + public void handleException(Throwable t) { + // re-throw the exception unchecked + throw new RuntimeException(t); + } + @Override + public String toString() { + return "ExceptionHandler.Runtime"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FileTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FileTools.java new file mode 100644 index 0000000000..cfe93a64b7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FileTools.java @@ -0,0 +1,1002 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.iterators.CompositeIterator; +import org.eclipse.jpt.common.utility.internal.iterators.FilteringIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; + +/** + * Assorted file tools: + * - delete entire trees of directories and files + * - build iterators on entire trees of directories and files + * - build a temporary directory + * - "canonize" files + */ +public final class FileTools { + + public static final String USER_HOME_DIRECTORY_NAME = System.getProperty("user.home"); //$NON-NLS-1$ + public static final String USER_TEMPORARY_DIRECTORY_NAME = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$ + public static String DEFAULT_TEMPORARY_DIRECTORY_NAME = "tmpdir"; //$NON-NLS-1$ + public static final String CURRENT_WORKING_DIRECTORY_NAME = System.getProperty("user.dir"); //$NON-NLS-1$ + + /** A list of some invalid file name characters. + : is the filename separator in MacOS and the drive indicator in DOS + * is a DOS wildcard character + | is a DOS redirection character + & is our own escape character + / is the filename separator in Unix and the command option tag in DOS + \ is the filename separator in DOS/Windows and the escape character in Unix + ; is ??? + ? is a DOS wildcard character + [ is ??? + ] is ??? + = is ??? + + is ??? + < is a DOS redirection character + > is a DOS redirection character + " is used by DOS to delimit file names with spaces + , is ??? + */ + public static final char[] INVALID_FILENAME_CHARACTERS = { ':', '*', '|', '&', '/', '\\', ';', '?', '[', ']', '=', '+', '<', '>', '"', ',' }; + + /** This encoder will convert strings into valid file names. */ + public static final XMLStringEncoder FILE_NAME_ENCODER = new XMLStringEncoder(INVALID_FILENAME_CHARACTERS); + + /** Windows files that are redirected to devices etc. */ + @SuppressWarnings("nls") + private static final String[] WINDOWS_RESERVED_FILE_NAMES = { + "con", + "aux", + "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", + "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", + "prn", + "nul" + }; + + /** The default length of a shortened file name. */ + public static final int MAXIMUM_SHORTENED_FILE_NAME_LENGTH = 60; + + + // ********** deleting directories ********** + + /** + * Delete the specified directory and all of its contents. + * USE WITH CARE. + * File#deleteAll()? + */ + public static void deleteDirectory(String directoryName) { + deleteDirectory(new File(directoryName)); + } + + /** + * Delete the specified directory and all of its contents. + * USE WITH CARE. + * File#deleteAll()? + */ + public static void deleteDirectory(File directory) { + deleteDirectoryContents(directory); + if ( ! directory.delete()) { + throw new RuntimeException("unable to delete directory: " + directory.getAbsolutePath()); //$NON-NLS-1$ + } + } + + /** + * Delete the contents of the specified directory + * (but not the directory itself). + * USE WITH CARE. + * File#deleteFiles() + */ + public static void deleteDirectoryContents(String directoryName) { + deleteDirectoryContents(new File(directoryName)); + } + + /** + * Delete the contents of the specified directory + * (but not the directory itself). + * USE WITH CARE. + * File#deleteFiles() + */ + public static void deleteDirectoryContents(File directory) { + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + deleteDirectory(file); // recurse through subdirectories + } else { + if ( ! file.delete()) { + throw new RuntimeException("unable to delete file: " + file.getAbsolutePath()); //$NON-NLS-1$ + } + } + } + } + + + // ********** copying files ********** + + /** + * Copies the content of the source file to the destination file. + * File#copy(File destinationFile) + */ + public static void copyToFile(File sourceFile, File destinationFile) + throws IOException + { + FileChannel sourceChannel = new FileInputStream(sourceFile).getChannel(); + FileChannel destinationChannel = new FileOutputStream(destinationFile).getChannel(); + try { + destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); + } finally { + sourceChannel.close(); + destinationChannel.close(); + } + } + + /** + * Copies the content of the source file to a file by + * the same name in the destination directory. + * File#copyToDirectory(File destinationDirectory) + */ + public static void copyToDirectory(File sourceFile, File destinationDirectory) + throws IOException + { + File destinationFile = new File(destinationDirectory, sourceFile.getName()); + if ( ! destinationFile.exists() && ! destinationFile.createNewFile()) { + throw new RuntimeException("createNewFile() failed: " + destinationFile); //$NON-NLS-1$ + } + copyToFile(sourceFile, destinationFile); + } + + + // ********** iteratoring over files and directories ********** + + /** + * Return an iterator on all the files in the specified directory. + * The iterator will skip over subdirectories. + * File#files() + */ + public static Iterator filesIn(String directoryName) { + return filesIn(new File(directoryName)); + } + + /** + * Return an iterator on all the files in the specified directory. + * The iterator will skip over subdirectories. + * File#files() + */ + public static Iterator filesIn(File directory) { + return filesIn(directory.listFiles()); + } + + private static Iterator filesIn(File[] files) { + return new FilteringIterator(new ArrayIterator(files)) { + @Override + protected boolean accept(File next) { + return next.isFile(); + } + }; + } + + /** + * Return an iterator on all the subdirectories + * in the specified directory. + * File#subDirectories() + */ + public static Iterator directoriesIn(String directoryName) { + return directoriesIn(new File(directoryName)); + } + + /** + * Return an iterator on all the subdirectories + * in the specified directory. + * File#subDirectories() + */ + public static Iterator directoriesIn(File directory) { + return directoriesIn(directory.listFiles()); + } + + private static Iterator directoriesIn(File[] files) { + return new FilteringIterator(new ArrayIterator(files)) { + @Override + protected boolean accept(File next) { + return next.isDirectory(); + } + }; + } + + /** + * Return an iterator on all the files under the specified + * directory, recursing into subdirectories. + * The iterator will skip over the subdirectories themselves. + * File#filesRecurse() + */ + public static Iterator filesInTree(String directoryName) { + return filesInTree(new File(directoryName)); + } + + /** + * Return an iterator on all the files under the specified + * directory, recursing into subdirectories. + * The iterator will skip over the subdirectories themselves. + * File#filesRecurse() + */ + public static Iterator filesInTree(File directory) { + return filesInTreeAsSet(directory).iterator(); + } + + private static Set filesInTreeAsSet(File directory) { + Set files = new HashSet(10000); + addFilesInTreeTo(directory, files); + return files; + } + + private static void addFilesInTreeTo(File directory, Collection allFiles) { + for (File file : directory.listFiles()) { + if (file.isFile()) { + allFiles.add(file); + } else if (file.isDirectory()) { + addFilesInTreeTo(file, allFiles); + } + } + } + + /** + * Return an iterator on all the directories under the specified + * directory, recursing into subdirectories. + * File#subDirectoriesRecurse() + */ + public static Iterator directoriesInTree(String directoryName) { + return directoriesInTree(new File(directoryName)); + } + + /** + * Return an iterator on all the directories under the specified + * directory, recursing into subdirectories. + * File#subDirectoriesRecurse() + */ + @SuppressWarnings("unchecked") + public static Iterator directoriesInTree(File directory) { + File[] files = directory.listFiles(); + return new CompositeIterator(directoriesIn(files), directoriesInTrees(directoriesIn(files))); + } + + private static Iterator directoriesInTrees(Iterator directories) { + return new CompositeIterator( + new TransformationIterator>(directories) { + @Override + protected Iterator transform(File next) { + return FileTools.directoriesInTree(next); + } + } + ); + } + + + // ********** short file name manipulation ********** + + /** + * Strip the extension from the specified file name + * and return the result. If the file name has no + * extension, it is returned unchanged + * File#basePath() + */ + public static String stripExtension(String fileName) { + int index = fileName.lastIndexOf('.'); + if (index == -1) { + return fileName; + } + return fileName.substring(0, index); + } + + /** + * Strip the extension from the specified file's name + * and return the result. If the file's name has no + * extension, it is returned unchanged + * File#basePath() + */ + public static String stripExtension(File file) { + return stripExtension(file.getPath()); + } + + /** + * Return the extension, including the dot, of the specified file name. + * If the file name has no extension, return an empty string. + * File#extension() + */ + public static String extension(String fileName) { + int index = fileName.lastIndexOf('.'); + if (index == -1) { + return ""; //$NON-NLS-1$ + } + return fileName.substring(index); + } + + /** + * Return the extension, including the dot, of the specified file's name. + * If the file's name has no extension, return an empty string. + * File#extension() + */ + public static String extension(File file) { + return extension(file.getPath()); + } + + + // ********** temporary directories ********** + + /** + * Build and return an empty temporary directory with the specified + * name. If the directory already exists, it will be cleared out. + * This directory will be a subdirectory of the Java temporary directory, + * as indicated by the System property "java.io.tmpdir". + */ + public static File emptyTemporaryDirectory(String name) { + File dir = new File(userTemporaryDirectory(), name); + if (dir.exists()) { + deleteDirectoryContents(dir); + } else { + mkdirs(dir); + } + return dir; + } + + private static void mkdirs(File dir) { + if ( ! dir.mkdirs()) { + throw new RuntimeException("mkdirs() failed: " + dir); //$NON-NLS-1$ + } + } + + /** + * Build and return an empty temporary directory with a + * name of "tmpdir". If the directory already exists, it will be cleared out. + * This directory will be a subdirectory of the Java temporary directory, + * as indicated by the System property "java.io.tmpdir". + */ + public static File emptyTemporaryDirectory() { + return emptyTemporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME); + } + + /** + * Build and return a temporary directory with the specified + * name. If the directory already exists, it will be left unchanged; + * if it does not already exist, it will be created. + * This directory will be a subdirectory of the Java temporary directory, + * as indicated by the System property "java.io.tmpdir". + */ + public static File temporaryDirectory(String name) { + File dir = new File(userTemporaryDirectory(), name); + if ( ! dir.exists()) { + mkdirs(dir); + } + return dir; + } + + /** + * Build and return a temporary directory with a name of + * "tmpdir". If the directory already exists, it will be left unchanged; + * if it does not already exist, it will be created. + * This directory will be a subdirectory of the Java temporary directory, + * as indicated by the System property "java.io.tmpdir". + */ + public static File temporaryDirectory() { + return temporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME); + } + + /** + * Build and return a *new* temporary directory with the specified + * prefix. The prefix will be appended with a number that + * is incremented, starting with 1, until a non-pre-existing directory + * is found and successfully created. This directory will be a + * subdirectory of the Java temporary directory, as indicated by + * the System property "java.io.tmpdir". + */ + public static File newTemporaryDirectory(String prefix) { + if ( ! prefix.endsWith(".")) { //$NON-NLS-1$ + prefix = prefix + '.'; + } + File dir; + int i = 0; + do { + i++; + dir = new File(userTemporaryDirectory(), prefix + i); + } while ( ! dir.mkdirs()); + return dir; + } + + /** + * Build and return a *new* temporary directory with a + * prefix of "tmpdir". This prefix will be appended with a number that + * is incremented, starting with 1, until a non-pre-existing directory + * is found and successfully created. This directory will be a + * subdirectory of the Java temporary directory, as indicated by + * the System property "java.io.tmpdir". + */ + public static File newTemporaryDirectory() { + return newTemporaryDirectory(DEFAULT_TEMPORARY_DIRECTORY_NAME); + } + + + // ********** resource files ********** + + /** + * Build and return a file for the specified resource. + * The resource name must be fully-qualified, i.e. it cannot be relative + * to the package name/directory. + * NB: There is a bug in jdk1.4.x the prevents us from getting + * a resource that has spaces (or other special characters) in + * its name.... (see Sun's Java bug 4466485) + */ + public static File resourceFile(String resourceName) throws URISyntaxException { + if ( ! resourceName.startsWith("/")) { //$NON-NLS-1$ + throw new IllegalArgumentException(resourceName); + } + return resourceFile(resourceName, FileTools.class); + } + + /** + * Build and return a file for the specified resource. + * NB: There is a bug in jdk1.4.x the prevents us from getting + * a resource that has spaces (or other special characters) in + * its name.... (see Sun's Java bug 4466485) + */ + public static File resourceFile(String resourceName, Class javaClass) throws URISyntaxException { + URL url = javaClass.getResource(resourceName); + return buildFile(url); + } + + /** + * Build and return a file for the specified URL. + * NB: There is a bug in jdk1.4.x the prevents us from getting + * a resource that has spaces (or other special characters) in + * its name.... (see Sun's Java bug 4466485) + */ + public static File buildFile(URL url) throws URISyntaxException { + return buildFile(url.getFile()); + } + + /** + * Build and return a file for the specified file name. + * NB: There is a bug in jdk1.4.x the prevents us from getting + * a resource that has spaces (or other special characters) in + * its name.... (see Sun's Java bug 4466485) + */ + public static File buildFile(String fileName) throws URISyntaxException { + URI uri = new URI(fileName); + File file = new File(uri.getPath()); + return file; + } + + + // ********** "canonical" files ********** + + /** + * Convert the specified file into a "canonical" file. + */ + public static File canonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException ioexception) { + // settle for the absolute file + return file.getAbsoluteFile(); + } + } + + /** + * Build an iterator that will convert the specified files + * into "canonical" files. + */ + public static Iterator canonicalFiles(Iterator files) { + return new TransformationIterator(files) { + @Override + protected File transform(File next) { + return canonicalFile(next); + } + }; + } + + /** + * Build an iterator that will convert the specified files + * into "canonical" files. + */ + public static Iterator canonicalFiles(Collection files) { + return canonicalFiles(files.iterator()); + } + + /** + * Convert the specified file name into a "canonical" file name. + */ + public static String canonicalFileName(String fileName) { + return canonicalFile(new File(fileName)).getAbsolutePath(); + } + + /** + * Build an iterator that will convert the specified file names + * into "canonical" file names. + */ + public static Iterator canonicalFileNames(Iterator fileNames) { + return new TransformationIterator(fileNames) { + @Override + protected String transform(String next) { + return canonicalFileName(next); + } + }; + } + + /** + * Build an iterator that will convert the specified file names + * into "canonical" file names. + */ + public static Iterator canonicalFileNames(Collection fileNames) { + return canonicalFileNames(fileNames.iterator()); + } + + + // ********** file name validation ********** + + /** + * Return whether the specified file name is invalid. + */ + public static boolean fileNameIsInvalid(String filename) { + return ! fileNameIsValid(filename); + } + + /** + * Return whether the specified file name is valid. + */ + public static boolean fileNameIsValid(String filename) { + int len = filename.length(); + for (int i = 0; i < len; i++) { + char filenameChar = filename.charAt(i); + if (ArrayTools.contains(INVALID_FILENAME_CHARACTERS, filenameChar)) { + return false; + } + } + return true; + } + + /** + * Convert the illegal characters in the specified file name to + * the specified character and return the result. + */ + public static String convertToValidFileName(String filename, char replacementChar) { + int len = filename.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char filenameChar = filename.charAt(i); + if (ArrayTools.contains(INVALID_FILENAME_CHARACTERS, filenameChar)) { + sb.append(replacementChar); + } else { + sb.append(filenameChar); + } + } + return sb.toString(); + } + + /** + * Convert the illegal characters in the specified file name to + * periods ('.') and return the result. + */ + public static String convertToValidFileName(String filename) { + return convertToValidFileName(filename, '.'); + } + + /** + * Return whether the specified file name is "reserved" + * (i.e. it cannot be used for "user" files). Windows reserves + * a number of file names (e.g. CON, AUX, PRN). + */ + public static boolean fileNameIsReserved(String fileName) { + // Unix/Linux does not have any "reserved" file names (I think...) + return Tools.osIsWindows() && ArrayTools.contains(WINDOWS_RESERVED_FILE_NAMES, fileName.toLowerCase()); + } + + /** + * Return whether the specified file contains any "reserved" + * components. + * Windows reserves a number of file names (e.g. CON, AUX, PRN); + * and these file names cannot be used for either the names of + * files or directories. + */ + public static boolean fileHasAnyReservedComponents(File file) { + File temp = file; + while (temp != null) { + if (fileNameIsReserved(temp.getName())) { + return true; + } + temp = temp.getParentFile(); + } + return false; + } + + + // ********** shortened file names ********** + + /** + * Return a shorter version of the absolute file name for the specified file. + * The shorter version will not be longer than the maximum length. + * The first directory (usually the drive letter) and the file name or the + * last directory will always be added to the generated string regardless of + * the maximum length allowed. + */ + public static String shortenFileName(URL url) { + return shortenFileName(url, MAXIMUM_SHORTENED_FILE_NAME_LENGTH); + } + + /** + * Return a shorter version of the absolute file name for the specified file. + * The shorter version will not be longer than the maximum length. + * The first directory (usually the drive letter) and the file name or the + * last directory will always be added to the generated string regardless of + * the maximum length allowed. + */ + public static String shortenFileName(URL url, int maxLength) { + File file; + try { + file = buildFile(url); + } catch (URISyntaxException e) { + file = new File(url.getFile()); + } + return shortenFileName(file, maxLength); + } + + /** + * Return a shorter version of the absolute file name for the specified file. + * The shorter version will not be longer than the maximum length. + * The first directory (usually the drive letter) and the file name or the + * last directory will always be added to the generated string regardless of + * the maximum length allowed. + */ + public static String shortenFileName(File file) { + return shortenFileName(file, MAXIMUM_SHORTENED_FILE_NAME_LENGTH); + } + + /** + * Return a shorter version of the absolute file name for the specified file. + * The shorter version will not be longer than the maximum length. + * The first directory (usually the drive letter) and the file name or the + * last directory will always be added to the generated string regardless of + * the maximum length allowed. + */ + public static String shortenFileName(File file, int maxLength) { + String absoluteFileName = canonicalFile(file).getAbsolutePath(); + if (absoluteFileName.length() <= maxLength) { + // no need to shorten + return absoluteFileName; + } + + // break down the path into its components + String fs = File.separator; + String[] paths = absoluteFileName.split('\\' + fs); + + if (paths.length <= 1) { + // e.g. "C:\" + return paths[0]; + } + + if (paths.length == 2) { + // e.g. "C:\MyReallyLongFileName.ext" or "C:\MyReallyLongDirectoryName" + // return the complete file name since this is a minimum requirement, + // regardless of the maximum length allowed + return absoluteFileName; + } + + StringBuilder sb = new StringBuilder(); + sb.append(paths[0]); // always add the first directory, which is usually the drive letter + + // Keep the index of insertion into the string buffer + int insertIndex = sb.length(); + + sb.append(fs); + sb.append(paths[paths.length - 1]); // append the file name or the last directory + + maxLength -= 4; // -4 for "/..." + + int currentLength = sb.length() - 4; // -4 for "/..." + int leftIndex = 1; // 1 to skip the root directory + int rightIndex = paths.length - 2; // -1 for the file name or the last directory + + boolean canAddFromLeft = true; + boolean canAddFromRight = true; + + // Add each directory, the insertion is going in both direction: left and + // right, once a side can't be added, the other side is still continuing + // until both can't add anymore + while (true) { + if (!canAddFromLeft && !canAddFromRight) + break; + + if (canAddFromRight) { + String rightDirectory = paths[rightIndex]; + int rightLength = rightDirectory.length(); + + // Add the directory on the right side of the loop + if (currentLength + rightLength + 1 <= maxLength) { + sb.insert(insertIndex, fs); + sb.insert(insertIndex + 1, rightDirectory); + + currentLength += rightLength + 1; + rightIndex--; + + // The right side is now overlapping the left side, that means + // we can't add from the right side anymore + if (leftIndex >= rightIndex) { + canAddFromRight = false; + } + } else { + canAddFromRight = false; + } + } + + if (canAddFromLeft) { + String leftDirectory = paths[leftIndex]; + int leftLength = leftDirectory.length(); + + // Add the directory on the left side of the loop + if (currentLength + leftLength + 1 <= maxLength) { + sb.insert(insertIndex, fs); + sb.insert(insertIndex + 1, leftDirectory); + + insertIndex += leftLength + 1; + currentLength += leftLength + 1; + leftIndex++; + + // The left side is now overlapping the right side, that means + // we can't add from the left side anymore + if (leftIndex >= rightIndex) { + canAddFromLeft = false; + } + } else { + canAddFromLeft = false; + } + } + } + + if (leftIndex <= rightIndex) { + sb.insert(insertIndex, fs); + sb.insert(insertIndex + 1, "..."); //$NON-NLS-1$ + } + + return sb.toString(); + } + + + // ********** system properties ********** + + /** + * Return a file representing the user's home directory. + */ + public static File userHomeDirectory() { + return new File(USER_HOME_DIRECTORY_NAME); + } + + /** + * Return a file representing the user's temporary directory. + */ + public static File userTemporaryDirectory() { + return new File(USER_TEMPORARY_DIRECTORY_NAME); + } + + /** + * Return a file representing the current working directory. + */ + public static File currentWorkingDirectory() { + return new File(CURRENT_WORKING_DIRECTORY_NAME); + } + + + // ********** miscellaneous ********** + + /** + * Return only the files that fit the filter. + * File#files(FileFilter fileFilter) + */ + public static Iterator filter(Iterator files, final FileFilter fileFilter) { + return new FilteringIterator(files) { + @Override + protected boolean accept(File next) { + return fileFilter.accept(next); + } + }; + } + + /** + * Return a file that is a re-specification of the specified + * file, relative to the specified directory. + * Linux/Unix/Mac: + * convertToRelativeFile(/foo/bar/baz.java, /foo) + * => bar/baz.java + * Windows: + * convertToRelativeFile(C:\foo\bar\baz.java, C:\foo) + * => bar/baz.java + * The file can be either a file or a directory; the directory + * *should* be a directory. + * If the file is already relative or it cannot be made relative + * to the directory, it will be returned unchanged. + * + * NB: This method has been tested on Windows and Linux, + * but not Mac (but the Mac is Unix-based these days, so + * it shouldn't be a problem...). + */ + public static File convertToRelativeFile(final File file, final File dir) { + // check whether the file is already relative + if ( ! file.isAbsolute()) { + return file; // return unchanged + } + + File cFile = canonicalFile(file); + File cDir = canonicalFile(dir); + + // the two are the same directory + if (cFile.equals(cDir)) { + return new File("."); //$NON-NLS-1$ + } + + File[] filePathFiles = pathFiles(cFile); + File[] dirPathFiles = pathFiles(cDir); + + // Windows only (?): the roots are different - e.g. D:\ vs. C:\ + if ( ! dirPathFiles[0].equals(filePathFiles[0])) { + return file; // return unchanged + } + + // at this point we know the root is the same, now find how much is in common + int i = 0; // this will point at the first miscompare + while ((i < dirPathFiles.length) && (i < filePathFiles.length)) { + if (dirPathFiles[i].equals(filePathFiles[i])) { + i++; + } else { + break; + } + } + // save our current position + int firstMismatch = i; + + // check whether the file is ABOVE the directory: ../.. + if (firstMismatch == filePathFiles.length) { + return relativeParentFile(dirPathFiles.length - firstMismatch); + } + + // build a new file from the path beyond the matching portions + File diff = new File(filePathFiles[i].getName()); + while (++i < filePathFiles.length) { + diff = new File(diff, filePathFiles[i].getName()); + } + + // check whether the file is BELOW the directory: subdir1/subdir2/file.ext + if (firstMismatch == dirPathFiles.length) { + return diff; + } + + // the file must be a PEER of the directory: ../../subdir1/subdir2/file.ext + return new File(relativeParentFile(dirPathFiles.length - firstMismatch), diff.getPath()); + } + + /** + * Return a file that is a re-specification of the specified + * file, relative to the current working directory. + * Linux/Unix/Mac (CWD = /foo): + * convertToRelativeFile(/foo/bar/baz.java) + * => bar/baz.java + * Windows (CWD = C:\foo): + * convertToRelativeFile(C:\foo\bar\baz.java) + * => bar/baz.java + * The file can be either a file or a directory. + * If the file is already relative or it cannot be made relative + * to the directory, it will be returned unchanged. + * + * NB: This method has been tested on Windows and Linux, + * but not Mac (but the Mac is Unix-based these days, so + * it shouldn't be a problem...). + */ + public static File convertToRelativeFile(final File file) { + return convertToRelativeFile(file, currentWorkingDirectory()); + } + + /** + * Return an array of files representing the path to the specified + * file. For example: + * C:/foo/bar/baz.txt => + * { C:/, C:/foo, C:/foo/bar, C:/foo/bar/baz.txt } + */ + private static File[] pathFiles(File file) { + List path = new ArrayList(); + for (File f = file; f != null; f = f.getParentFile()) { + path.add(f); + } + Collections.reverse(path); + return path.toArray(new File[path.size()]); + } + + /** + * Return a file with the specified (non-zero) number of relative + * file names, e.g. xxx(3) => ../../.. + */ + private static File relativeParentFile(int len) { + if (len <= 0) { + throw new IllegalArgumentException("length must be greater than zero: " + len); //$NON-NLS-1$ + } + File result = new File(".."); //$NON-NLS-1$ + for (int i = len - 1; i-- > 0; ) { + result = new File(result, ".."); //$NON-NLS-1$ + } + return result; + } + + /** + * Return a file that is a re-specification of the specified + * file, absolute to the specified directory. + * Linux/Unix/Mac: + * convertToAbsoluteFile(bar/baz.java, /foo) + * => /foo/bar/baz.java + * Windows: + * convertToAbsoluteFile(bar/baz.java, C:\foo) + * => C:\foo\bar\baz.java + * The file can be either a file or a directory; the directory + * *should* be a directory. + * If the file is already absolute, it will be returned unchanged. + * + * NB: This method has been tested on Windows and Linux, + * but not Mac (but the Mac is Unix-based these days, so + * it shouldn't be a problem...). + */ + public static File convertToAbsoluteFile(final File file, final File dir) { + // check whether the file is already absolute + if (file.isAbsolute()) { + return file; // return unchanged + } + return canonicalFile(new File(dir, file.getPath())); + } + + /** + * Return a file that is a re-specification of the specified + * file, absolute to the current working directory. + * Linux/Unix/Mac (CWD = /foo): + * convertToAbsoluteFile(bar/baz.java) + * => /foo/bar/baz.java + * Windows (CWD = C:\foo): + * convertToAbsoluteFile(bar/baz.java) + * => C:\foo\bar\baz.java + * The file can be either a file or a directory. + * If the file is already absolute, it will be returned unchanged. + * + * NB: This method has been tested on Windows and Linux, + * but not Mac (but the Mac is Unix-based these days, so + * it shouldn't be a problem...). + */ + public static File convertToAbsoluteFile(final File file) { + return convertToAbsoluteFile(file, currentWorkingDirectory()); + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private FileTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FlaggedObjectReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FlaggedObjectReference.java new file mode 100644 index 0000000000..84d06c6552 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FlaggedObjectReference.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Provide a container for passing an object that can be changed by the + * recipient. If the value is set at any time after construction of the + * reference, the reference is marked "set". This allows the client to + * detect whether the server/recipient ever set the value, even if it remains + * unchanged. This is particularly useful when the value can be set to + * null. + *

+ * The reference can be set multiple times, but it can + * never be "unset" once it is "set". + */ +public class FlaggedObjectReference + extends SimpleObjectReference +{ + private volatile boolean set = false; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create an object reference with the specified initial value. + */ + public FlaggedObjectReference(V value) { + super(value); + } + + /** + * Create an object reference with an initial value of + * null. + */ + public FlaggedObjectReference() { + super(); + } + + + // ********** set ********** + + public boolean isSet() { + return this.set; + } + + + // ********** overrides ********** + + @Override + public V setValue(V value) { + this.set = true; + return super.setValue(value); + } + + @Override + public String toString() { + String s = super.toString(); + return (this.set) ? '*' + s : s; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/HashBag.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/HashBag.java new file mode 100644 index 0000000000..d394bbb286 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/HashBag.java @@ -0,0 +1,877 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class implements the {@link Bag} interface, backed by a + * hash table. It makes no guarantees as to the iteration order of + * the bag's elements; in particular, it does not guarantee the order + * will remain constant over time. This class permits the null + * element. + *

+ * This class offers constant time performance for the basic operations + * (add, remove, contains and + * size), assuming the hash function disperses the elements + * properly among the buckets. Iterating over this bag requires time + * proportional to the sum of the bag's size (the number of elements) plus the + * "capacity" of the backing hash table (the number of buckets). Thus, it is + * important not to set the initial capacity too high (or the load factor too + * low) if iteration performance is important. + *

+ * Note that this implementation is not synchronized. If multiple + * threads access a bag concurrently, and at least one of the threads modifies + * the bag, it must be synchronized externally. This is typically + * accomplished by synchronizing on some object that naturally encapsulates + * the bag. If no such object exists, the bag should be "wrapped" using the + * Collections.synchronizedCollection method. This is + * best done at creation time, to prevent accidental unsynchronized access + * to the bag: + *

+ * Collection c = Collections.synchronizedCollection(new HashBag(...));
+ * 
+ *

+ * The iterators returned by this class's iterator method are + * fail-fast: if the bag is modified at any time after the iterator is + * created, in any way except through the iterator's own remove + * method, the iterator throws a {@link ConcurrentModificationException}. + * Thus, in the face of concurrent modification, the iterator fails quickly + * and cleanly, rather than risking arbitrary, non-deterministic behavior at + * an undetermined time in the future. + *

+ * Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + * @param the type of elements maintained by the bag + * + * @see Collection + * @see Bag + * @see SynchronizedBag + * @see Collections#synchronizedCollection(Collection) + * @see IdentityHashBag + */ +public class HashBag + extends AbstractCollection + implements Bag, Cloneable, Serializable +{ + /** The hash table. Resized as necessary. Length MUST Always be a power of two. */ + transient Entry[] table; + + /** The total number of entries in the bag. */ + transient int size = 0; + + /** The number of UNIQUE entries in the bag. */ + transient int uniqueCount = 0; + + /** + * The hash table is rehashed when its size exceeds this threshold. (The + * value of this field is (int) (capacity * loadFactor).) + * + * @serial + */ + private int threshold; + + /** + * The load factor for the hash table. + * + * @serial + */ + private final float loadFactor; + + /** + * The number of times this bag has been structurally modified. + * Structural modifications are those that change the number of entries in + * the bag or otherwise modify its internal structure (e.g. rehash). + * This field is used to make iterators on this bag fail-fast. + * + * @see java.util.ConcurrentModificationException + */ + transient int modCount = 0; + + /** + * The default initial capacity - MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= (1 << 30). + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * Construct a new, empty bag with the + * default capacity, which is 16, and load factor, which is 0.75. + */ + public HashBag() { + this(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Construct a new, empty bag with the specified initial capacity + * and the default load factor, which is 0.75. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is less + * than zero + */ + public HashBag(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, false); // false = do not validate parms + } + + /** + * Construct a new, empty bag with + * the specified initial capacity and load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is less + * than zero or if the load factor is non-positive + */ + public HashBag(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, true); // true = validate parms + } + + private HashBag(int initialCapacity, float loadFactor, boolean validateParms) { + super(); + int capacity = initialCapacity; + if (validateParms) { + if (capacity < 0) { + throw new IllegalArgumentException("Illegal Initial Capacity: " + capacity); //$NON-NLS-1$ + } + if (capacity > MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } + if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Illegal Load factor: " + loadFactor); //$NON-NLS-1$ + } + + // find a power of 2 >= 'initialCapacity' + capacity = 1; + while (capacity < initialCapacity) { + capacity <<= 1; + } + } + + this.loadFactor = loadFactor; + this.table = this.buildTable(capacity); + this.threshold = (int) (capacity * loadFactor); + } + + /** + * Construct a new bag containing the elements in the specified + * collection. The bag's load factor will be the default, which is 0.75, + * and its initial capacity will be sufficient to hold all the elements in + * the specified collection. + * + * @param c the collection whose elements are to be placed into this bag. + */ + public HashBag(Collection c) { + this(Math.max((int) (c.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); + this.addAll_(c); + } + + /** + * Return a hash for the specified object. + */ + private int hash(Object o) { + return (o == null) ? 0 : this.tweakHash(o.hashCode()); + } + + /** + * Tweak the specified hash, to defend against poor implementations + * of {@link Object#hashCode()}. + */ + private int tweakHash(int h) { + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + /** + * Return the index for the specified hash. + */ + private int index(int hash) { + return this.index(hash, this.table.length); + } + + /** + * Return the index for the specified hash + * within a table with the specified length. + */ + int index(int hash, int length) { + return hash & (length - 1); + } + + /** + * Internal {@link #addAll(Collection)} for construction and cloning. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void addAll_(Iterable c) { + for (E e : c) { + this.add_(e); + } + } + + /** + * Internal {@link #add(Object)} for construction and cloning. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void add_(E o) { + this.add_(o, 1); + } + + /** + * Internal {@link #add(Object, int)} for construction, cloning, and serialization. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void add_(E o, int cnt) { + int hash = this.hash(o); + int index = this.index(hash); + for (Entry e = this.table[index]; e != null; e = e.next) { + Object eo; + if ((e.hash == hash) && (((eo = e.object) == o) || ((o != null) && o.equals(eo)))) { + e.count += cnt; + this.size += cnt; + return; + } + } + + // create the new entry and put it in the table + Entry e = this.buildEntry(hash, o, cnt, this.table[index]); + this.table[index] = e; + this.size += cnt; + this.uniqueCount++; + } + + /** + * This implementation simply returns the maintained size. + */ + @Override + public int size() { + return this.size; + } + + /** + * This implementation simply compares the maintained size to zero. + */ + @Override + public boolean isEmpty() { + return this.size == 0; + } + + /** + * Search for the object's entry in the hash table by calculating + * the object's hash code and examining the entries in the corresponding hash + * table bucket. + */ + private Entry getEntry(Object o) { + int hash = this.hash(o); + for (Entry e = this.table[this.index(hash)]; e != null; e = e.next) { + Object eo; + if ((e.hash == hash) && (((eo = e.object) == o) || ((o != null) && o.equals(eo)))) { + return e; + } + } + return null; + } + + @Override + public boolean contains(Object o) { + return this.getEntry(o) != null; + } + + public int count(Object o) { + Entry e = this.getEntry(o); + return (e == null) ? 0 : e.count; + } + + /** + * Rehash the contents of the bag into a new hash table + * with a larger capacity. This method is called when the + * number of different elements in the bag exceeds its + * capacity and load factor. + */ + private void rehash() { + Entry[] oldTable = this.table; + int oldCapacity = oldTable.length; + + if (oldCapacity == MAXIMUM_CAPACITY) { + this.threshold = Integer.MAX_VALUE; + return; + } + + int newCapacity = 2 * oldCapacity; + Entry[] newTable = this.buildTable(newCapacity); + + for (int i = oldCapacity; i-- > 0; ) { + for (Entry old = oldTable[i]; old != null; ) { + Entry e = old; + old = old.next; + + int index = this.index(e.hash, newCapacity); + e.next = newTable[index]; + newTable[index] = e; + } + } + + this.table = newTable; + this.threshold = (int) (newCapacity * this.loadFactor); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + private Entry[] buildTable(int capacity) { + return new Entry[capacity]; + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's hash code and examining the entries in the corresponding hash + * table bucket. + */ + @Override + public boolean add(E o) { + return this.add(o, 1); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's hash code and examining the entries in the corresponding hash + * table bucket. + */ + public boolean add(E o, int cnt) { + if (cnt <= 0) { + return false; + } + this.modCount++; + int hash = this.hash(o); + int index = this.index(hash); + + // if the object is already in the bag, simply bump its count + for (Entry e = this.table[index]; e != null; e = e.next) { + Object eo; + if ((e.hash == hash) && (((eo = e.object) == o) || ((o != null) && o.equals(eo)))) { + e.count += cnt; + this.size += cnt; + return true; + } + } + + // rehash the table if we are going to exceed the threshold + if (this.uniqueCount >= this.threshold) { + this.rehash(); + index = this.index(hash); // need to re-calculate the index + } + + // create the new entry and put it in the table + Entry e = this.buildEntry(hash, o, cnt, this.table[index]); + this.table[index] = e; + this.size += cnt; + this.uniqueCount++; + return true; + } + + // minimize scope of suppressed warnings + @SuppressWarnings({ "rawtypes", "unchecked" } ) + private Entry buildEntry(int hash, Object o, int cnt, Entry next) { + return new Entry(hash, (E) o, cnt, (Entry) next); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's hash code and examining the entries in the corresponding hash + * table bucket. + */ + @Override + public boolean remove(Object o) { + return this.remove(o, 1); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's hash code and examining the entries in the corresponding hash + * table bucket. + */ + public boolean remove(Object o, int cnt) { + if (cnt <= 0) { + return false; + } + int hash = this.hash(o); + int index = this.index(hash); + + for (Entry e = this.table[index], prev = null; e != null; prev = e, e = e.next) { + Object eo; + if ((e.hash == hash) && (((eo = e.object) == o) || ((o != null) && o.equals(eo)))) { + this.modCount++; + cnt = (cnt < e.count) ? cnt : e.count; + e.count -= cnt; + // if we are removing the last element(s), remove the entry from the table + if (e.count == 0) { + if (prev == null) { + this.table[index] = e.next; + } else { + prev.next = e.next; + } + this.uniqueCount--; + } + this.size -= cnt; + return true; + } + } + + return false; + } + + /** + * This implementation simply clears out all of the hash table buckets. + */ + @Override + public void clear() { + Entry[] tab = this.table; + this.modCount++; + for (int i = tab.length; i-- > 0; ) { + tab[i] = null; + } + this.size = 0; + this.uniqueCount = 0; + } + + /** + * Returns a shallow copy of this bag: the elements + * themselves are not cloned. + * + * @return a shallow copy of this bag. + */ + @Override + public HashBag clone() { + try { + @SuppressWarnings("unchecked") + HashBag clone = (HashBag) super.clone(); + clone.table = this.buildTable(this.table.length); + clone.size = 0; + clone.uniqueCount = 0; + clone.modCount = 0; + clone.addAll_(this); + return clone; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + + /** + * Hash table collision list entry. + */ + private static class Entry implements Bag.Entry { + final int hash; + final E object; + int count; + Entry next; + + Entry(int hash, E object, int count, Entry next) { + this.hash = hash; + this.object = object; + this.count = count; + this.next = next; + } + + // ***** Bag.Entry implementation + public E getElement() { + return this.object; + } + + public int getCount() { + return this.count; + } + + public int setCount(int count) { + if (count <= 0) { + throw new IllegalArgumentException("count must be greater than zero: " + count); //$NON-NLS-1$ + } + int old = this.count; + this.count = count; + return old; + } + + @Override + public boolean equals(Object o) { + if ( ! (o instanceof Bag.Entry)) { + return false; + } + @SuppressWarnings("rawtypes") + Bag.Entry e = (Bag.Entry) o; + return (this.count == e.getCount()) && + Tools.valuesAreEqual(this.object, e.getElement()); + } + + @Override + public int hashCode() { + E o = this.object; + return (o == null) ? 0 : (this.count * o.hashCode()); + } + + @Override + public String toString() { + return this.object + "=>" + this.count; //$NON-NLS-1$ + } + } + + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + return (this.size == 0) ? EMPTY_ITERATOR : new HashIterator(); + } + + @SuppressWarnings("unchecked") + public Iterator uniqueIterator() { + return (this.size == 0) ? EMPTY_ITERATOR : new UniqueIterator(); + } + + public int uniqueCount() { + return this.uniqueCount; + } + + @SuppressWarnings("unchecked") + public Iterator> entries() { + return (this.size == 0) ? EMPTY_ITERATOR : new EntryIterator(); + } + + + /** + * Empty iterator that does just about nothing. + */ + @SuppressWarnings("rawtypes") + private static final Iterator EMPTY_ITERATOR = new EmptyIterator(); + + @SuppressWarnings("rawtypes") + private static class EmptyIterator implements Iterator { + + EmptyIterator() { + super(); + } + + public boolean hasNext() { + return false; + } + + public Object next() { + throw new NoSuchElementException(); + } + + public void remove() { + throw new IllegalStateException(); + } + } + + + private class HashIterator implements Iterator { + private int index = HashBag.this.table.length; // start at the end of the table + private Entry nextEntry = null; + private int nextEntryCount = 0; + private Entry lastReturnedEntry = null; + + /** + * The modCount value that the iterator believes that the backing + * bag should have. If this expectation is violated, the iterator + * has detected a concurrent modification. + */ + private int expectedModCount = HashBag.this.modCount; + + HashIterator() { + super(); + } + + public boolean hasNext() { + Entry e = this.nextEntry; + int i = this.index; + Entry[] tab = HashBag.this.table; + // Use locals for faster loop iteration + while ((e == null) && (i > 0)) { + e = tab[--i]; // move backwards through the table + } + this.nextEntry = e; + this.index = i; + return e != null; + } + + public E next() { + if (HashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + Entry et = this.nextEntry; + int i = this.index; + Entry[] tab = HashBag.this.table; + // Use locals for faster loop iteration + while ((et == null) && (i > 0)) { + et = tab[--i]; // move backwards through the table + } + this.nextEntry = et; + this.index = i; + if (et == null) { + throw new NoSuchElementException(); + } + Entry e = this.lastReturnedEntry = this.nextEntry; + this.nextEntryCount++; + if (this.nextEntryCount == e.count) { + this.nextEntry = e.next; + this.nextEntryCount = 0; + } + return e.object; + } + + public void remove() { + if (this.lastReturnedEntry == null) { + throw new IllegalStateException(); + } + if (HashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + int slot = HashBag.this.index(this.lastReturnedEntry.hash, HashBag.this.table.length); + for (Entry e = HashBag.this.table[slot], prev = null; e != null; prev = e, e = e.next) { + if (e == this.lastReturnedEntry) { + HashBag.this.modCount++; + this.expectedModCount++; + e.count--; + if (e.count == 0) { + // if we are removing the last one, remove the entry from the table + if (prev == null) { + HashBag.this.table[slot] = e.next; + } else { + prev.next = e.next; + } + HashBag.this.uniqueCount--; + } else { + // slide back the count to account for the just-removed element + this.nextEntryCount--; + } + HashBag.this.size--; + this.lastReturnedEntry = null; // it cannot be removed again + return; + } + } + throw new ConcurrentModificationException(); + } + + } + + + private class EntryIterator implements Iterator> { + private int index = HashBag.this.table.length; // start at the end of the table + private Entry nextEntry = null; + private Entry lastReturnedEntry = null; + + /** + * The modCount value that the iterator believes that the backing + * bag should have. If this expectation is violated, the iterator + * has detected a concurrent modification. + */ + private int expectedModCount = HashBag.this.modCount; + + EntryIterator() { + super(); + } + + public boolean hasNext() { + Entry e = this.nextEntry; + int i = this.index; + Entry[] tab = HashBag.this.table; + // Use locals for faster loop iteration + while ((e == null) && (i > 0)) { + e = tab[--i]; // move backwards through the table + } + this.nextEntry = e; + this.index = i; + return e != null; + } + + public Entry next() { + if (HashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + Entry et = this.nextEntry; + int i = this.index; + Entry[] tab = HashBag.this.table; + // Use locals for faster loop iteration + while ((et == null) && (i > 0)) { + et = tab[--i]; // move backwards through the table + } + this.nextEntry = et; + this.index = i; + if (et == null) { + throw new NoSuchElementException(); + } + Entry e = this.lastReturnedEntry = this.nextEntry; + this.nextEntry = e.next; + return e; + } + + public void remove() { + if (this.lastReturnedEntry == null) { + throw new IllegalStateException(); + } + if (HashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + int slot = HashBag.this.index(this.lastReturnedEntry.hash, HashBag.this.table.length); + for (Entry e = HashBag.this.table[slot], prev = null; e != null; prev = e, e = e.next) { + if (e == this.lastReturnedEntry) { + HashBag.this.modCount++; + this.expectedModCount++; + // remove the entry from the table + if (prev == null) { + HashBag.this.table[slot] = e.next; + } else { + prev.next = e.next; + } + HashBag.this.uniqueCount--; + HashBag.this.size -= this.lastReturnedEntry.count; + this.lastReturnedEntry = null; // it cannot be removed again + return; + } + } + throw new ConcurrentModificationException(); + } + + } + + + private class UniqueIterator implements Iterator { + private EntryIterator entryIterator = new EntryIterator(); + + UniqueIterator() { + super(); + } + + public boolean hasNext() { + return this.entryIterator.hasNext(); + } + + public E next() { + return this.entryIterator.next().object; + } + + public void remove() { + this.entryIterator.remove(); + } + + } + + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if ( ! (o instanceof Bag)) { + return false; + } + @SuppressWarnings("unchecked") + Bag b = (Bag) o; + if (b.size() != this.size()) { + return false; + } + if (b.uniqueCount() != this.uniqueCount()) { + return false; + } + for (Iterator> stream = b.entries(); stream.hasNext(); ) { + Bag.Entry entry = stream.next(); + if (entry.getCount() != this.count(entry.getElement())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int h = 0; + for (E o : this) { + if (o != null) { + h += o.hashCode(); + } + } + return h; + } + + /** + * Save the state of this bag to a stream (i.e. serialize it). + * + * @serialData Emit the capacity of the bag (int), + * followed by the number of unique elements in the bag (int), + * followed by all of the bag's elements (each an Object) and + * their counts (each an int), in no particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // write out the threshold, load factor, and any hidden stuff + s.defaultWriteObject(); + + // write out number of buckets + s.writeInt(this.table.length); + + // write out number of unique elements + s.writeInt(this.uniqueCount); + + // write out elements and counts (alternating) + if (this.uniqueCount > 0) { + for (Entry entry : this.table) { + while (entry != null) { + s.writeObject(entry.object); + s.writeInt(entry.count); + entry = entry.next; + } + } + } + } + + private static final long serialVersionUID = 1L; + + /** + * Reconstitute the bag from a stream (i.e. deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // read in the threshold, loadfactor, and any hidden stuff + s.defaultReadObject(); + + // read in number of buckets and allocate the bucket array + this.table = this.buildTable(s.readInt()); + + // read in number of unique elements + int unique = s.readInt(); + + // read the elements and counts, and put the elements in the bag + for (int i = 0; i < unique; i++) { + @SuppressWarnings("unchecked") + E element = (E) s.readObject(); + int elementCount = s.readInt(); + this.add_(element, elementCount); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IdentityHashBag.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IdentityHashBag.java new file mode 100644 index 0000000000..7c2fbc4a5c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IdentityHashBag.java @@ -0,0 +1,924 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class implements the {@link Bag} interface with a + * hash table, using object-identity in place of object-equality when + * comparing elements. In other words, in an IdentityHashBag, + * two objects o1 and o2 are considered + * equal if and only if (o1 == o2). (In normal {@link Bag} + * implementations (like {@link HashBag}) two objects o1 + * and o2 are considered equal if and only if + * (o1 == null ? o2 == null : o1.equals(o2)).) + *

+ * + * This class is not a general-purpose {@link Bag} + * implementation! While this class implements the {@link Bag} interface, it + * intentionally violates {@link Bag}'s general contract, which mandates the + * use of the equals method when comparing objects. This class is + * designed for use only in the rare cases wherein object-identity + * semantics are required. + * + *

+ * This class makes no guarantees as to the iteration order of + * the bag's elements; in particular, it does not guarantee that the order + * will remain constant over time. This class permits the null + * element. + *

+ * This class offers constant time performance for the basic operations + * (add, remove, contains and + * size), assuming the system identity hash function + * ({@link System#identityHashCode(Object)}) disperses elements properly + * among the buckets. Iterating over this bag requires time + * proportional to the sum of the bag's size (the number of elements) plus the + * "capacity" of the backing hash table (the number of buckets). Thus, it is + * important not to set the initial capacity too high (or the load factor too + * low) if iteration performance is important. + *

+ * Note that this implementation is not synchronized. If multiple + * threads access a bag concurrently, and at least one of the threads modifies + * the bag, it must be synchronized externally. This is typically + * accomplished by synchronizing on some object that naturally encapsulates + * the bag. If no such object exists, the bag should be "wrapped" using the + * Collections.synchronizedCollection method. This is + * best done at creation time, to prevent accidental unsynchronized access + * to the bag: + *

+ * Collection c = Collections.synchronizedCollection(new IdentityHashBag(...));
+ * 
+ *

+ * The iterators returned by this class's iterator method are + * fail-fast: if the bag is modified at any time after the iterator is + * created, in any way except through the iterator's own remove + * method, the iterator throws a {@link ConcurrentModificationException}. + * Thus, in the face of concurrent modification, the iterator fails quickly + * and cleanly, rather than risking arbitrary, non-deterministic behavior at + * an undetermined time in the future. + *

+ * Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + * @param the type of elements maintained by the bag + * + * @see Collection + * @see Bag + * @see SynchronizedBag + * @see Collections#synchronizedCollection(Collection) + * @see HashBag + */ +public class IdentityHashBag + extends AbstractCollection + implements Bag, Cloneable, Serializable +{ + /** The hash table. Resized as necessary. Length MUST Always be a power of two. */ + transient Entry[] table; + + /** The total number of entries in the bag. */ + transient int size = 0; + + /** The number of UNIQUE entries in the bag. */ + transient int uniqueCount = 0; + + /** + * The hash table is rehashed when its size exceeds this threshold. (The + * value of this field is (int) (capacity * loadFactor).) + * + * @serial + */ + private int threshold; + + /** + * The load factor for the hash table. + * + * @serial + */ + private final float loadFactor; + + /** + * The number of times this bag has been structurally modified. + * Structural modifications are those that change the number of entries in + * the bag or otherwise modify its internal structure (e.g. rehash). + * This field is used to make iterators on this bag fail-fast. + * + * @see java.util.ConcurrentModificationException + */ + transient int modCount = 0; + + /** + * The default initial capacity - MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= (1 << 30). + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * Construct a new, empty bag with the + * default capacity, which is 16, and load factor, which is 0.75. + */ + public IdentityHashBag() { + this(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Construct a new, empty bag with the specified initial capacity + * and the default load factor, which is 0.75. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is less + * than zero + */ + public IdentityHashBag(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, false); // false = do not validate parms + } + + /** + * Construct a new, empty bag with + * the specified initial capacity and load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is less + * than zero or if the load factor is non-positive + */ + public IdentityHashBag(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, true); // true = validate parms + } + + private IdentityHashBag(int initialCapacity, float loadFactor, boolean validateParms) { + super(); + int capacity = initialCapacity; + if (validateParms) { + if (capacity < 0) { + throw new IllegalArgumentException("Illegal Initial Capacity: " + capacity); //$NON-NLS-1$ + } + if (capacity > MAXIMUM_CAPACITY) { + capacity = MAXIMUM_CAPACITY; + } + if (loadFactor <= 0 || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Illegal Load factor: " + loadFactor); //$NON-NLS-1$ + } + + // find a power of 2 >= 'initialCapacity' + capacity = 1; + while (capacity < initialCapacity) { + capacity <<= 1; + } + } + + this.loadFactor = loadFactor; + this.table = this.buildTable(capacity); + this.threshold = (int) (capacity * loadFactor); + } + + /** + * Construct a new bag containing the elements in the specified + * collection. The bag's load factor will be the default, which is 0.75, + * and its initial capacity will be sufficient to hold all the elements in + * the specified collection. + * + * @param c the collection whose elements are to be placed into this bag. + */ + public IdentityHashBag(Collection c) { + this(Math.max((int) (c.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); + this.addAll_(c); + } + + /** + * Return a index for the specified object. + */ + private int index(Object o) { + return this.index(this.hash(o)); + } + + /** + * Return a hash for the specified object. + */ + private int hash(Object o) { + return (o == null) ? 0 : this.tweakHash(System.identityHashCode(o)); + } + + /** + * Tweak the specified hash. + */ + private int tweakHash(int h) { + return h; +// h ^= (h >>> 20) ^ (h >>> 12); +// return h ^ (h >>> 7) ^ (h >>> 4); + } + + /** + * Return the index for the specified hash. + */ + private int index(int hash) { + return this.index(hash, this.table.length); + } + + /** + * Return the index for the specified hash + * within a table with the specified length. + */ + int index(int hash, int length) { + return hash & (length - 1); + } + + /** + * Internal {@link #addAll(Collection)} for construction and cloning. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void addAll_(Iterable c) { + for (E e : c) { + this.add_(e); + } + } + + /** + * Internal {@link #add(Object)} for construction and cloning. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void add_(E o) { + this.add_(o, 1); + } + + /** + * Internal {@link #add(Object, int)} for construction, cloning, and serialization. + * (No check for re-hash; no change to mod count; no return value.) + */ + private void add_(E o, int cnt) { + int hash = this.hash(o); + int index = this.index(hash); + for (Entry e = this.table[index]; e != null; e = e.next) { + if (e.object == o) { + e.count += cnt; + this.size += cnt; + return; + } + } + + // create the new entry and put it in the table + Entry e = this.buildEntry(hash, o, cnt, this.table[index]); + this.table[index] = e; + this.size += cnt; + this.uniqueCount++; + } + + /** + * This implementation simply returns the maintained size. + */ + @Override + public int size() { + return this.size; + } + + /** + * This implementation simply compares the maintained size to zero. + */ + @Override + public boolean isEmpty() { + return this.size == 0; + } + + /** + * Search for the object's entry in the hash table by calculating + * the object's identity hash code and examining the entries in the corresponding hash + * table bucket. + */ + private Entry getEntry(Object o) { + for (Entry e = this.table[this.index(o)]; e != null; e = e.next) { + if (e.object == o) { + return e; + } + } + return null; + } + + @Override + public boolean contains(Object o) { + return this.getEntry(o) != null; + } + + public int count(Object o) { + Entry e = this.getEntry(o); + return (e == null) ? 0 : e.count; + } + + /** + * Rehash the contents of the bag into a new hash table + * with a larger capacity. This method is called when the + * number of different elements in the bag exceeds its + * capacity and load factor. + */ + private void rehash() { + Entry[] oldTable = this.table; + int oldCapacity = oldTable.length; + + if (oldCapacity == MAXIMUM_CAPACITY) { + this.threshold = Integer.MAX_VALUE; + return; + } + + int newCapacity = 2 * oldCapacity; + Entry[] newTable = this.buildTable(newCapacity); + + for (int i = oldCapacity; i-- > 0; ) { + for (Entry old = oldTable[i]; old != null; ) { + Entry e = old; + old = old.next; + + int index = this.index(e.hash, newCapacity); + e.next = newTable[index]; + newTable[index] = e; + } + } + + this.table = newTable; + this.threshold = (int) (newCapacity * this.loadFactor); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + private Entry[] buildTable(int capacity) { + return new Entry[capacity]; + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's identity hash code and examining the entries in the corresponding hash + * table bucket. + */ + @Override + public boolean add(E o) { + return this.add(o, 1); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's identity hash code and examining the entries in the corresponding hash + * table bucket. + */ + public boolean add(E o, int cnt) { + if (cnt <= 0) { + return false; + } + this.modCount++; + int hash = this.hash(o); + int index = this.index(hash); + + // if the object is already in the bag, simply bump its count + for (Entry e = this.table[index]; e != null; e = e.next) { + if (e.object == o) { + e.count += cnt; + this.size += cnt; + return true; + } + } + + // rehash the table if we are going to exceed the threshold + if (this.uniqueCount >= this.threshold) { + this.rehash(); + index = this.index(hash); // need to re-calculate the index + } + + // create the new entry and put it in the table + Entry e = this.buildEntry(hash, o, cnt, this.table[index]); + this.table[index] = e; + this.size += cnt; + this.uniqueCount++; + return true; + } + + // minimize scope of suppressed warnings + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Entry buildEntry(int hash, Object o, int cnt, Entry next) { + return new Entry(hash, (E) o, cnt, (Entry) next); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's identity hash code and examining the entries in the corresponding hash + * table bucket. + */ + @Override + public boolean remove(Object o) { + return this.remove(o, 1); + } + + /** + * This implementation searches for the object in the hash table by calculating + * the object's identity hash code and examining the entries in the corresponding hash + * table bucket. + */ + public boolean remove(Object o, int cnt) { + if (cnt <= 0) { + return false; + } + int index = this.index(o); + + for (Entry e = this.table[index], prev = null; e != null; prev = e, e = e.next) { + if (e.object == o) { + this.modCount++; + cnt = (cnt < e.count) ? cnt : e.count; + e.count -= cnt; + // if we are removing the last element(s), remove the entry from the table + if (e.count == 0) { + if (prev == null) { + this.table[index] = e.next; + } else { + prev.next = e.next; + } + this.uniqueCount--; + } + this.size -= cnt; + return true; + } + } + + return false; + } + + /** + * This implementation uses object-identity to determine whether the + * specified collection contains a particular element. + */ + @Override + public boolean removeAll(Collection c) { + return super.removeAll(new IdentityHashBag(c)); + } + + /** + * This implementation uses object-identity to determine whether the + * specified collection contains a particular element. + */ + @Override + public boolean retainAll(Collection c) { + return super.retainAll(new IdentityHashBag(c)); + } + + /** + * This implementation simply clears out all of the hash table buckets. + */ + @Override + public void clear() { + Entry[] tab = this.table; + this.modCount++; + for (int i = tab.length; i-- > 0; ) { + tab[i] = null; + } + this.size = 0; + this.uniqueCount = 0; + } + + /** + * Returns a shallow copy of this bag: the elements + * themselves are not cloned. + * + * @return a shallow copy of this bag. + */ + @Override + public IdentityHashBag clone() { + try { + @SuppressWarnings("unchecked") + IdentityHashBag clone = (IdentityHashBag) super.clone(); + clone.table = this.buildTable(this.table.length); + clone.size = 0; + clone.uniqueCount = 0; + clone.modCount = 0; + clone.addAll_(this); + return clone; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + + /** + * Hash table collision list entry. + */ + private static class Entry implements Bag.Entry { + final int hash; // cache the hash for re-hashes + final E object; + int count; + Entry next; + + Entry(int hash, E object, int count, Entry next) { + this.hash = hash; + this.object = object; + this.count = count; + this.next = next; + } + + // ***** Bag.Entry implementation + public E getElement() { + return this.object; + } + + public int getCount() { + return this.count; + } + + public int setCount(int count) { + if (count <= 0) { + throw new IllegalArgumentException("count must be greater than zero: " + count); //$NON-NLS-1$ + } + int old = this.count; + this.count = count; + return old; + } + + @Override + public boolean equals(Object o) { + if ( ! (o instanceof Bag.Entry)) { + return false; + } + @SuppressWarnings("rawtypes") + Bag.Entry e = (Bag.Entry) o; + return (this.object == e.getElement()) + && (this.count == e.getCount()); + } + + @Override + public int hashCode() { + E o = this.object; + return (o == null) ? 0 : (this.count * o.hashCode()); + } + + @Override + public String toString() { + return this.object + "=>" + this.count; //$NON-NLS-1$ + } + } + + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + return (this.size == 0) ? EMPTY_ITERATOR : new HashIterator(); + } + + @SuppressWarnings("unchecked") + public Iterator uniqueIterator() { + return (this.size == 0) ? EMPTY_ITERATOR : new UniqueIterator(); + } + + public int uniqueCount() { + return this.uniqueCount; + } + + @SuppressWarnings("unchecked") + public Iterator> entries() { + return (this.size == 0) ? EMPTY_ITERATOR : new EntryIterator(); + } + + + /** + * Empty iterator that does just about nothing. + */ + @SuppressWarnings("rawtypes") + private static final Iterator EMPTY_ITERATOR = new EmptyIterator(); + + @SuppressWarnings("rawtypes") + private static class EmptyIterator implements Iterator { + + EmptyIterator() { + super(); + } + + public boolean hasNext() { + return false; + } + + public Object next() { + throw new NoSuchElementException(); + } + + public void remove() { + throw new IllegalStateException(); + } + } + + + private class HashIterator implements Iterator { + private int index = IdentityHashBag.this.table.length; // start at the end of the table + private Entry nextEntry = null; + private int nextEntryCount = 0; + private Entry lastReturnedEntry = null; + + /** + * The modCount value that the iterator believes that the backing + * bag should have. If this expectation is violated, the iterator + * has detected a concurrent modification. + */ + private int expectedModCount = IdentityHashBag.this.modCount; + + HashIterator() { + super(); + } + + public boolean hasNext() { + Entry e = this.nextEntry; + int i = this.index; + Entry[] tab = IdentityHashBag.this.table; + // Use locals for faster loop iteration + while ((e == null) && (i > 0)) { + e = tab[--i]; // move backwards through the table + } + this.nextEntry = e; + this.index = i; + return e != null; + } + + public E next() { + if (IdentityHashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + Entry et = this.nextEntry; + int i = this.index; + Entry[] tab = IdentityHashBag.this.table; + // Use locals for faster loop iteration + while ((et == null) && (i > 0)) { + et = tab[--i]; // move backwards through the table + } + this.nextEntry = et; + this.index = i; + if (et == null) { + throw new NoSuchElementException(); + } + Entry e = this.lastReturnedEntry = this.nextEntry; + this.nextEntryCount++; + if (this.nextEntryCount == e.count) { + this.nextEntry = e.next; + this.nextEntryCount = 0; + } + return e.object; + } + + public void remove() { + if (this.lastReturnedEntry == null) { + throw new IllegalStateException(); + } + if (IdentityHashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + int slot = IdentityHashBag.this.index(this.lastReturnedEntry.hash, IdentityHashBag.this.table.length); + for (Entry e = IdentityHashBag.this.table[slot], prev = null; e != null; prev = e, e = e.next) { + if (e == this.lastReturnedEntry) { + IdentityHashBag.this.modCount++; + this.expectedModCount++; + e.count--; + if (e.count == 0) { + // if we are removing the last one, remove the entry from the table + if (prev == null) { + IdentityHashBag.this.table[slot] = e.next; + } else { + prev.next = e.next; + } + IdentityHashBag.this.uniqueCount--; + } else { + // slide back the count to account for the just-removed element + this.nextEntryCount--; + } + IdentityHashBag.this.size--; + this.lastReturnedEntry = null; // it cannot be removed again + return; + } + } + throw new ConcurrentModificationException(); + } + + } + + + private class EntryIterator implements Iterator> { + private int index = IdentityHashBag.this.table.length; // start at the end of the table + private Entry nextEntry = null; + private Entry lastReturnedEntry = null; + + /** + * The modCount value that the iterator believes that the backing + * bag should have. If this expectation is violated, the iterator + * has detected a concurrent modification. + */ + private int expectedModCount = IdentityHashBag.this.modCount; + + EntryIterator() { + super(); + } + + public boolean hasNext() { + Entry e = this.nextEntry; + int i = this.index; + Entry[] tab = IdentityHashBag.this.table; + // Use locals for faster loop iteration + while ((e == null) && (i > 0)) { + e = tab[--i]; // move backwards through the table + } + this.nextEntry = e; + this.index = i; + return e != null; + } + + public Entry next() { + if (IdentityHashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + Entry et = this.nextEntry; + int i = this.index; + Entry[] tab = IdentityHashBag.this.table; + // Use locals for faster loop iteration + while ((et == null) && (i > 0)) { + et = tab[--i]; // move backwards through the table + } + this.nextEntry = et; + this.index = i; + if (et == null) { + throw new NoSuchElementException(); + } + Entry e = this.lastReturnedEntry = this.nextEntry; + this.nextEntry = e.next; + return e; + } + + public void remove() { + if (this.lastReturnedEntry == null) { + throw new IllegalStateException(); + } + if (IdentityHashBag.this.modCount != this.expectedModCount) { + throw new ConcurrentModificationException(); + } + int slot = IdentityHashBag.this.index(this.lastReturnedEntry.hash, IdentityHashBag.this.table.length); + for (Entry e = IdentityHashBag.this.table[slot], prev = null; e != null; prev = e, e = e.next) { + if (e == this.lastReturnedEntry) { + IdentityHashBag.this.modCount++; + this.expectedModCount++; + // remove the entry from the table + if (prev == null) { + IdentityHashBag.this.table[slot] = e.next; + } else { + prev.next = e.next; + } + IdentityHashBag.this.uniqueCount--; + IdentityHashBag.this.size -= this.lastReturnedEntry.count; + this.lastReturnedEntry = null; // it cannot be removed again + return; + } + } + throw new ConcurrentModificationException(); + } + + } + + + private class UniqueIterator implements Iterator { + private EntryIterator entryIterator = new EntryIterator(); + + UniqueIterator() { + super(); + } + + public boolean hasNext() { + return this.entryIterator.hasNext(); + } + + public E next() { + return this.entryIterator.next().object; + } + + public void remove() { + this.entryIterator.remove(); + } + + } + + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof IdentityHashBag) { + @SuppressWarnings("unchecked") + IdentityHashBag b = (IdentityHashBag) o; + if (b.size() != this.size()) { + return false; + } + if (b.uniqueCount() != this.uniqueCount()) { + return false; + } + for (Iterator> stream = b.entries(); stream.hasNext(); ) { + Bag.Entry entry = stream.next(); + if (entry.getCount() != this.count(entry.getElement())) { + return false; + } + } + return true; + } else { + return this.equals_(o); + } +// } else if (o instanceof Bag) { +// // hmmm... +// return new HashBag(this).equals(o); +// } else { +// return false; +// } + } + + private boolean equals_(Object o) { + // hmmm... + return (o instanceof Bag) && + new HashBag(this).equals(o); + } + + @Override + public int hashCode() { + int h = 0; + for (E o : this) { + h += System.identityHashCode(o); + } + return h; + } + + /** + * Save the state of this bag to a stream (i.e. serialize it). + * + * @serialData Emit the capacity of the bag (int), + * followed by the number of unique elements in the bag (int), + * followed by all of the bag's elements (each an Object) and + * their counts (each an int), in no particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // write out the threshold, load factor, and any hidden stuff + s.defaultWriteObject(); + + // write out number of buckets + s.writeInt(this.table.length); + + // write out number of unique elements + s.writeInt(this.uniqueCount); + + // write out elements and counts (alternating) + if (this.uniqueCount > 0) { + for (Entry entry : this.table) { + while (entry != null) { + s.writeObject(entry.object); + s.writeInt(entry.count); + entry = entry.next; + } + } + } + } + + private static final long serialVersionUID = 1L; + + /** + * Reconstitute the bag from a stream (i.e. deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // read in the threshold, loadfactor, and any hidden stuff + s.defaultReadObject(); + + // read in number of buckets and allocate the bucket array + this.table = this.buildTable(s.readInt()); + + // read in number of unique elements + int unique = s.readInt(); + + // read the elements and counts, and put the elements in the bag + for (int i = 0; i < unique; i++) { + @SuppressWarnings("unchecked") + E element = (E) s.readObject(); + int elementCount = s.readInt(); + this.add_(element, elementCount); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IntReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IntReference.java new file mode 100644 index 0000000000..ad989b4fda --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IntReference.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Interface for a container for passing an integer that can be changed by + * the recipient. + */ +public interface IntReference + extends ReadOnlyIntReference +{ + /** + * Set the int value. + * Return the previous value. + */ + int setValue(int value); + + /** + * Set the int value to zero. + * Return the previous value. + */ + int setZero(); + + /** + * Increment and return the int value. + */ + int increment(); + + /** + * Decrement and return the int value. + */ + int decrement(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCTools.java new file mode 100644 index 0000000000..1663a8e493 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCTools.java @@ -0,0 +1,349 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.sql.Types; +import java.util.HashMap; +import org.eclipse.jpt.common.utility.JavaType; + +/** + * Helper methods for dealing with the JDBC API. + */ +public final class JDBCTools { + + + /** + * Return the JDBC type corresponding to the specified class. + * @see java.sql.Types + */ + public static JDBCType jdbcTypeForClassNamed(String className) { + JavaToJDBCTypeMapping mapping = javaToJDBCTypeMapping(className); + return (mapping == null) ? DEFAULT_JDBC_TYPE : mapping.getJDBCType(); + } + + /** + * Return the JDBC type corresponding to the specified class. + * @see java.sql.Types + */ + public static JDBCType jdbcTypeFor(Class javaClass) { + return jdbcTypeForClassNamed(javaClass.getName()); + } + + /** + * Return the JDBC type corresponding to the specified class. + * @see java.sql.Types + */ + public static JDBCType jdbcTypeFor(JavaType javaType) { + return jdbcTypeForClassNamed(javaType.getJavaClassName()); + } + + /** + * Return the Java type corresponding to the specified JDBC type. + * @see java.sql.Types + */ + public static JavaType javaTypeForJDBCTypeNamed(String jdbcTypeName) { + JDBCToJavaTypeMapping mapping = jdbcToJavaTypeMapping(jdbcTypeName); + return (mapping == null) ? DEFAULT_JAVA_TYPE : mapping.getJavaType(); + } + + /** + * Return the Java type corresponding to the specified JDBC type. + * @see java.sql.Types + */ + public static JavaType javaTypeFor(JDBCType jdbcType) { + return javaTypeForJDBCTypeNamed(jdbcType.name()); + } + + /** + * Return the Java type corresponding to the specified JDBC type. + * @see java.sql.Types + */ + public static JavaType javaTypeForJDBCTypeCode(int jdbcTypeCode) { + return javaTypeFor(JDBCType.type(jdbcTypeCode)); + } + + + // ********** internal stuff ********** + + + // ********** JDBC => Java ********** + + /** + * JDBC => Java type mappings, keyed by JDBC type name (e.g. "VARCHAR") + */ + private static HashMap JDBC_TO_JAVA_TYPE_MAPPINGS; // pseudo 'final' - lazy-initialized + private static final JavaType DEFAULT_JAVA_TYPE = new SimpleJavaType(java.lang.Object.class); // TODO Object is the default? + + + private static JDBCToJavaTypeMapping jdbcToJavaTypeMapping(String jdbcTypeName) { + return jdbcToJavaTypeMappings().get(jdbcTypeName); + } + + private static synchronized HashMap jdbcToJavaTypeMappings() { + if (JDBC_TO_JAVA_TYPE_MAPPINGS == null) { + JDBC_TO_JAVA_TYPE_MAPPINGS = buildJDBCToJavaTypeMappings(); + } + return JDBC_TO_JAVA_TYPE_MAPPINGS; + } + + private static HashMap buildJDBCToJavaTypeMappings() { + HashMap mappings = new HashMap(); + addJDBCToJavaTypeMappingsTo(mappings); + return mappings; + } + + /** + * hard code the default mappings from the JDBC types to the + * appropriate Java types + * @see java.sql.Types + * see "JDBC 3.0 Specification" Appendix B + */ + private static void addJDBCToJavaTypeMappingsTo(HashMap mappings) { + addJDBCToJavaTypeMappingTo(Types.ARRAY, java.sql.Array.class, mappings); + addJDBCToJavaTypeMappingTo(Types.BIGINT, long.class, mappings); + addJDBCToJavaTypeMappingTo(Types.BINARY, byte[].class, mappings); + addJDBCToJavaTypeMappingTo(Types.BIT, boolean.class, mappings); + addJDBCToJavaTypeMappingTo(Types.BLOB, java.sql.Blob.class, mappings); + addJDBCToJavaTypeMappingTo(Types.BOOLEAN, boolean.class, mappings); + addJDBCToJavaTypeMappingTo(Types.CHAR, java.lang.String.class, mappings); + addJDBCToJavaTypeMappingTo(Types.CLOB, java.sql.Clob.class, mappings); + addJDBCToJavaTypeMappingTo(Types.DATALINK, java.net.URL.class, mappings); + addJDBCToJavaTypeMappingTo(Types.DATE, java.sql.Date.class, mappings); + addJDBCToJavaTypeMappingTo(Types.DECIMAL, java.math.BigDecimal.class, mappings); + addJDBCToJavaTypeMappingTo(Types.DISTINCT, java.lang.Object.class, mappings); // ??? + addJDBCToJavaTypeMappingTo(Types.DOUBLE, double.class, mappings); + addJDBCToJavaTypeMappingTo(Types.FLOAT, double.class, mappings); + addJDBCToJavaTypeMappingTo(Types.INTEGER, int.class, mappings); + addJDBCToJavaTypeMappingTo(Types.JAVA_OBJECT, java.lang.Object.class, mappings); // ??? + addJDBCToJavaTypeMappingTo(Types.LONGVARBINARY, byte[].class, mappings); + addJDBCToJavaTypeMappingTo(Types.LONGVARCHAR, java.lang.String.class, mappings); + // not sure why this is defined in java.sql.Types +// addJDBCToJavaTypeMappingTo(Types.NULL, java.lang.Object.class, mappings); + addJDBCToJavaTypeMappingTo(Types.NUMERIC, java.math.BigDecimal.class, mappings); + addJDBCToJavaTypeMappingTo(Types.OTHER, java.lang.Object.class, mappings); // ??? + addJDBCToJavaTypeMappingTo(Types.REAL, float.class, mappings); + addJDBCToJavaTypeMappingTo(Types.REF, java.sql.Ref.class, mappings); + addJDBCToJavaTypeMappingTo(Types.SMALLINT, short.class, mappings); + addJDBCToJavaTypeMappingTo(Types.STRUCT, java.sql.Struct.class, mappings); + addJDBCToJavaTypeMappingTo(Types.TIME, java.sql.Time.class, mappings); + addJDBCToJavaTypeMappingTo(Types.TIMESTAMP, java.sql.Timestamp.class, mappings); + addJDBCToJavaTypeMappingTo(Types.TINYINT, byte.class, mappings); + addJDBCToJavaTypeMappingTo(Types.VARBINARY, byte[].class, mappings); + addJDBCToJavaTypeMappingTo(Types.VARCHAR, java.lang.String.class, mappings); + } + + private static void addJDBCToJavaTypeMappingTo(int jdbcTypeCode, Class javaClass, HashMap mappings) { + // check for duplicates + JDBCType jdbcType = JDBCType.type(jdbcTypeCode); + Object prev = mappings.put(jdbcType.name(), buildJDBCToJavaTypeMapping(jdbcType, javaClass)); + if (prev != null) { + throw new IllegalArgumentException("duplicate JDBC type: " + jdbcType.name()); //$NON-NLS-1$ + } + } + + private static JDBCToJavaTypeMapping buildJDBCToJavaTypeMapping(JDBCType jdbcType, Class javaClass) { + return new JDBCToJavaTypeMapping(jdbcType, new SimpleJavaType(javaClass)); + } + + + // ********** Java => JDBC ********** + + /** + * Java => JDBC type mappings, keyed by Java class name (e.g. "java.lang.Object") + */ + private static HashMap JAVA_TO_JDBC_TYPE_MAPPINGS; // pseudo 'final' - lazy-initialized + private static final JDBCType DEFAULT_JDBC_TYPE = JDBCType.type(Types.VARCHAR); // TODO VARCHAR is the default? + + + private static JavaToJDBCTypeMapping javaToJDBCTypeMapping(String className) { + return javaToJDBCTypeMappings().get(className); + } + + private static synchronized HashMap javaToJDBCTypeMappings() { + if (JAVA_TO_JDBC_TYPE_MAPPINGS == null) { + JAVA_TO_JDBC_TYPE_MAPPINGS = buildJavaToJDBCTypeMappings(); + } + return JAVA_TO_JDBC_TYPE_MAPPINGS; + } + + private static HashMap buildJavaToJDBCTypeMappings() { + HashMap mappings = new HashMap(); + addJavaToJDBCTypeMappingsTo(mappings); + return mappings; + } + + /** + * hard code the default mappings from the Java types to the + * appropriate JDBC types + * @see java.sql.Types + * see "JDBC 3.0 Specification" Appendix B + */ + private static void addJavaToJDBCTypeMappingsTo(HashMap mappings) { + // primitives + addJavaToJDBCTypeMappingTo(boolean.class, Types.BIT, mappings); + addJavaToJDBCTypeMappingTo(byte.class, Types.TINYINT, mappings); + addJavaToJDBCTypeMappingTo(double.class, Types.DOUBLE, mappings); + addJavaToJDBCTypeMappingTo(float.class, Types.REAL, mappings); + addJavaToJDBCTypeMappingTo(int.class, Types.INTEGER, mappings); + addJavaToJDBCTypeMappingTo(long.class, Types.BIGINT, mappings); + addJavaToJDBCTypeMappingTo(short.class, Types.SMALLINT, mappings); + + // reference classes + addJavaToJDBCTypeMappingTo(java.lang.Boolean.class, Types.BIT, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Byte.class, Types.TINYINT, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Double.class, Types.DOUBLE, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Float.class, Types.REAL, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Integer.class, Types.INTEGER, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Long.class, Types.BIGINT, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Short.class, Types.SMALLINT, mappings); + addJavaToJDBCTypeMappingTo(java.lang.String.class, Types.VARCHAR, mappings); + addJavaToJDBCTypeMappingTo(java.math.BigDecimal.class, Types.NUMERIC, mappings); + addJavaToJDBCTypeMappingTo(java.net.URL.class, Types.DATALINK, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Array.class, Types.ARRAY, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Blob.class, Types.BLOB, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Clob.class, Types.CLOB, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Date.class, Types.DATE, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Ref.class, Types.REF, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Struct.class, Types.STRUCT, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Time.class, Types.TIME, mappings); + addJavaToJDBCTypeMappingTo(java.sql.Timestamp.class, Types.TIMESTAMP, mappings); + + // arrays + addJavaToJDBCTypeMappingTo(byte[].class, Types.VARBINARY, mappings); + addJavaToJDBCTypeMappingTo(java.lang.Byte[].class, Types.VARBINARY, mappings); + } + + private static void addJavaToJDBCTypeMappingTo(Class javaClass, int jdbcTypeCode, HashMap mappings) { + // check for duplicates + Object prev = mappings.put(javaClass.getName(), buildJavaToJDBCTypeMapping(javaClass, jdbcTypeCode)); + if (prev != null) { + throw new IllegalArgumentException("duplicate Java class: " + ((JavaToJDBCTypeMapping) prev).getJavaType().declaration()); //$NON-NLS-1$ + } + } + + private static JavaToJDBCTypeMapping buildJavaToJDBCTypeMapping(Class javaClass, int jdbcTypeCode) { + return new JavaToJDBCTypeMapping(new SimpleJavaType(javaClass), JDBCType.type(jdbcTypeCode)); + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private JDBCTools() { + super(); + throw new UnsupportedOperationException(); + } + + + // ********** member classes ********** + + /** + * JDBC => Java + */ + static class JDBCToJavaTypeMapping { + private final JDBCType jdbcType; + private final JavaType javaType; + + JDBCToJavaTypeMapping(JDBCType jdbcType, JavaType javaType) { + super(); + this.jdbcType = jdbcType; + this.javaType = javaType; + } + + public JDBCType getJDBCType() { + return this.jdbcType; + } + + public JavaType getJavaType() { + return this.javaType; + } + + public boolean maps(int jdbcTypeCode) { + return this.jdbcType.code() == jdbcTypeCode; + } + + public boolean maps(String jdbcTypeName) { + return this.jdbcType.name().equals(jdbcTypeName); + } + + public boolean maps(JDBCType type) { + return this.jdbcType == type; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + this.appendTo(sb); + return sb.toString(); + } + + public void appendTo(StringBuilder sb) { + this.jdbcType.appendTo(sb); + sb.append(" => "); //$NON-NLS-1$ + this.javaType.appendDeclarationTo(sb); + } + + } + + /** + * Java => JDBC + */ + static class JavaToJDBCTypeMapping { + private final JavaType javaType; + private final JDBCType jdbcType; + + JavaToJDBCTypeMapping(JavaType javaType, JDBCType jdbcType) { + super(); + this.javaType = javaType; + this.jdbcType = jdbcType; + } + + public JavaType getJavaType() { + return this.javaType; + } + + public JDBCType getJDBCType() { + return this.jdbcType; + } + + public boolean maps(JavaType jt) { + return this.javaType.equals(jt); + } + + public boolean maps(String elementTypeName, int arrayDepth) { + return this.javaType.equals(elementTypeName, arrayDepth); + } + + public boolean maps(String javaClassName) { + return this.javaType.describes(javaClassName); + } + + public boolean maps(Class javaClass) { + return this.javaType.describes(javaClass); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + this.appendTo(sb); + return sb.toString(); + } + + public void appendTo(StringBuilder sb) { + this.javaType.appendDeclarationTo(sb); + sb.append(" => "); //$NON-NLS-1$ + this.jdbcType.appendTo(sb); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCType.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCType.java new file mode 100644 index 0000000000..96fd283a7d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCType.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.sql.Types; + +/** + * Associate the Java constant and the JDBC type name. + * These are derived from java.sql.Types. + * + * @see java.sql.Types + */ +public final class JDBCType + implements Cloneable, Serializable +{ + + /** + * the constant name (e.g. VARCHAR) + */ + private final String name; + + /** + * the JDBC code used by JDBC drivers + */ + private final int code; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a JDBC type with the specified name and type code. + * This is private because all the possible JDBC types are built and + * stored in the static array TYPES. + * @see #types() + */ + private JDBCType(String name, int code) { + super(); + this.name = name; + this.code = code; + } + + + // ********** accessors ********** + + /** + * Return the name of the type, as defined in java.sql.Types. + */ + public String name() { + return this.name; + } + + + /** + * Return the type code, as defined in java.sql.Types. + */ + public int code() { + return this.code; + } + + + // ********** printing and displaying ********** + + public void appendTo(StringBuilder sb) { + sb.append(this.name); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()); + sb.append('('); + this.appendTo(sb); + sb.append(')'); + return sb.toString(); + } + + @Override + public JDBCType clone() { + try { + return (JDBCType) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + + // ********** static stuff ********** + + /** + * all the JDBC type defined in java.sql.Types + */ + private static JDBCType[] TYPES; // pseudo 'final' - lazy-initialized + + + public synchronized static JDBCType[] types() { + if (TYPES == null) { + TYPES = buildTypes(); + } + return TYPES; + } + + /** + * Return the JDBC type for the specified type code (e.g. Types.VARCHAR). + * @see java.sql.Types + */ + public static JDBCType type(int code) { + JDBCType[] types = types(); + for (int i = types.length; i-- > 0; ) { + if (types[i].code() == code) { + return types[i]; + } + } + throw new IllegalArgumentException("invalid JDBC type code: " + code); //$NON-NLS-1$ + } + + /** + * Return the JDBC type for the specified type name (e.g. "VARCHAR"). + * @see java.sql.Types + */ + public static JDBCType type(String name) { + JDBCType[] types = types(); + for (int i = types.length; i-- > 0; ) { + if (types[i].name().equals(name)) { + return types[i]; + } + } + throw new IllegalArgumentException("invalid JDBC type name: " + name); //$NON-NLS-1$ + } + + /** + * build up the JDBC types via reflection + * @see java.sql.Types + */ + private static JDBCType[] buildTypes() { + Field[] fields = Types.class.getDeclaredFields(); + int len = fields.length; + JDBCType[] types = new JDBCType[len]; + for (int i = len; i-- > 0; ) { + String name = fields[i].getName(); + int code; + try { + code = ((Integer) fields[i].get(null)).intValue(); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); // shouldn't happen... + } + types[i] = new JDBCType(name, code); + } + return types; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/KeyedSet.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/KeyedSet.java new file mode 100644 index 0000000000..cf471f4bb8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/KeyedSet.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Oracle - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * This class maintains a {@link Set} of items, and a {@link Map} of keys to those items. + * An item may have multiple keys, but an item may have no keys and remain in the set. Once an + * item's last key is removed, the item is also removed. + */ +public class KeyedSet { + + private final Set itemSet; + private final Set unmodifiableItemSet; + private final Map map; + + + public KeyedSet() { + this.itemSet = new HashSet(); + this.unmodifiableItemSet = Collections.unmodifiableSet(this.itemSet); + this.map = new HashMap(); + } + + /** + * Return an unmodifiable representation of the set of items. + */ + public Set getItemSet() { + return this.unmodifiableItemSet; + } + + /** + * Return the item stored under the given key. + */ + public V getItem(K key) { + return this.map.get(key); + } + + /** + * Return whether an item is stored under the given key. + */ + public boolean containsKey(K key) { + return this.map.containsKey(key); + } + + /** + * Return whether the item is stored under *any* key. + */ + public boolean containsItem(V item) { + return this.itemSet.contains(item); + } + + /** + * Add an item to be stored under the given key. + * The item must not already be stored. + */ + public void addItem(K key, V item) { + addItem(item); + addKey(key, item); + } + + private void addItem(V item) { + if (item == null) { + throw new IllegalArgumentException(); + } + this.itemSet.add(item); + } + + /** + * Add an additional key to an item already stored under an alternate key. + */ + public void addKey(K key, V item) { + if (key == null || item == null) { + throw new IllegalArgumentException(); + } + if (! this.itemSet.contains(item)) { + throw new IllegalArgumentException(); + } + this.map.put(key, item); + } + + /** + * Remove the given item and remove any key-to-item mapping it may have. + */ + public boolean removeItem(V item) { + if (this.itemSet.remove(item)) { + for (Map.Entry entry : CollectionTools.collection(this.map.entrySet())) { + if (entry.getValue() == item) { + map.remove(entry.getKey()); + } + } + return true; + } + return false; + } + + /** + * Remove the key-to-item mapping for the given key. + * If it is the last key to the item, also remove the item. + */ + public boolean removeKey(K key) { + final V item = this.map.get(key); + if (item != null) { + this.map.remove(key); + boolean otherKey = false; + for (Map.Entry entry : CollectionTools.collection(this.map.entrySet())) { + if (otherKey | entry.getValue() == item) { + otherKey = true; + } + } + if (! otherKey) { + removeItem(item); + } + return true; + } + return false; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/LazyReadOnlyObjectReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/LazyReadOnlyObjectReference.java new file mode 100644 index 0000000000..84d01e34c3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/LazyReadOnlyObjectReference.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import org.eclipse.jpt.common.utility.ReadOnlyObjectReference; + +/** + * Provide a thread-safe, reasonably performing container for holding an + * object that will be "lazy-initialized" upon its first reference. This is + * also useful for preventing direct references (accidental or otherwise) to + * lazy-initialized state. + *

+ * There are some penalties:

    + *
  • The reference's use of generics will require casting (and the requisite + * VM testing) with every access + *
  • If the value calculated during lazy initialization is null, + * access will be synchronized every time. + *
+ * @see SimpleObjectReference + * @see SynchronizedObject + */ +public abstract class LazyReadOnlyObjectReference + implements ReadOnlyObjectReference, Cloneable, Serializable +{ + /** Backing value. */ + private volatile V value = null; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create a lazy object reference. + */ + public LazyReadOnlyObjectReference() { + super(); + } + + + // ********** value ********** + + /** + * In JDK 5 and later, this "double-checked locking" idiom works as long + * as the instance variable is marked volatile. + */ + public V getValue() { + V result = this.value; + if (result == null) { + synchronized (this) { + result = this.value; + if (result == null) { + this.value = result = this.buildValue(); + } + } + } + return result; + } + + protected abstract V buildValue(); + + public boolean valueEquals(Object object) { + return Tools.valuesAreEqual(this.getValue(), object); + } + + public boolean valueNotEqual(Object object) { + return Tools.valuesAreDifferent(this.getValue(), object); + } + + public boolean isNull() { + return this.getValue() == null; + } + + public boolean isNotNull() { + return this.getValue() != null; + } + + + // ********** standard methods ********** + + @Override + public LazyReadOnlyObjectReference clone() { + try { + @SuppressWarnings("unchecked") + LazyReadOnlyObjectReference clone = (LazyReadOnlyObjectReference) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + /** + * This method will not trigger the "lazy-initialization". + */ + @Override + public String toString() { + return '[' + String.valueOf(this.value) + ']'; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ListenerList.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ListenerList.java new file mode 100644 index 0000000000..632b3fd5df --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ListenerList.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; + +/** + * Maintain a thread-safe list of listeners that does not allow adding + * duplicate listeners or removing non-listeners. + */ +public class ListenerList + implements Serializable +{ + /** + * We can mark this volatile and not synchronize the read methods because + * we never change the contents of the array. + */ + private transient volatile L[] listeners; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a listener list for listeners of the specified type. + */ + public ListenerList(Class listenerClass) { + super(); + this.listeners = this.buildListenerArray(listenerClass, 0); + } + + /** + * Construct a listener list for listeners of the specified type. + * Add the specified listener to the list. + */ + public ListenerList(Class listenerClass, L listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listeners = this.buildListenerArray(listenerClass, 1); + this.listeners[0] = listener; + } + + @SuppressWarnings("unchecked") + private L[] buildListenerArray(Class listenerClass, int length) { + return (L[]) Array.newInstance(listenerClass, length); + } + + /** + * Return the listeners. + */ + public Iterable getListeners() { + return new ArrayIterable(this.listeners); + } + + /** + * Return the number of listeners. + */ + public int size() { + return this.listeners.length; + } + + /** + * Return whether the listener list has no listeners. + */ + public boolean isEmpty() { + return this.listeners.length == 0; + } + + /** + * Add the specified listener to the listener list. + * Duplicate listeners are not allowed. + */ + public synchronized void add(L listener) { + if (listener == null) { + throw new NullPointerException(); + } + if (ArrayTools.contains(this.listeners, listener)) { + throw new IllegalArgumentException("duplicate listener: " + listener); //$NON-NLS-1$ + } + this.listeners = ArrayTools.add(this.listeners, listener); + } + + /** + * Remove the specified listener from the listener list. + * Removing a listener that is not on the list is not allowed. + */ + public synchronized void remove(L listener) { + if (listener == null) { + throw new NullPointerException(); + } + int index = ArrayTools.indexOf(this.listeners, listener); + if (index == -1) { + throw new IllegalArgumentException("unregistered listener: " + listener); //$NON-NLS-1$ + } + this.listeners = ArrayTools.removeElementAtIndex(this.listeners, index); + } + + /** + * Clear the listener list. + */ + public synchronized void clear() { + this.listeners = ArrayTools.clear(this.listeners); + } + + /** + * Return the type of listeners held by the listener list. + */ + @SuppressWarnings("unchecked") + public Class getListenerType() { + return (Class) this.listeners.getClass().getComponentType(); + } + + @Override + public String toString() { + return Arrays.toString(this.listeners); + } + + + // ********** serialization ********** + + /** + * Silently drop any non-serializable listeners. + */ + private synchronized void writeObject(ObjectOutputStream s) throws IOException { + // write out any hidden stuff + s.defaultWriteObject(); + + @SuppressWarnings("unchecked") + Class listenerClass = (Class) this.listeners.getClass().getComponentType(); + s.writeObject(listenerClass); + + // only write out serializable listeners + for (L listener : this.listeners) { + if (listener instanceof Serializable) { + s.writeObject(listener); + } + } + + s.writeObject(null); + } + + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { + // read in any hidden stuff + s.defaultReadObject(); + + Class listenerClass = (Class) s.readObject(); + this.listeners = this.buildListenerArray(listenerClass, 0); + Object o; + while ((o = s.readObject()) != null) { + this.listeners = ArrayTools.add(this.listeners, (L) o); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NameTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NameTools.java new file mode 100644 index 0000000000..dd7d65f0bf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NameTools.java @@ -0,0 +1,376 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.beans.Introspector; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.SortedSet; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; + +/** + * Various helper methods for generating names. + */ +public final class NameTools { + + /** + * Given a "root" name and a set of existing names, generate a unique + * name that is either the "root" name or some variation on the "root" + * name (e.g. "root2", "root3",...). The names are case-sensitive + * (i.e. "Root" and "root" are both allowed). + */ + public static String uniqueNameFor(String rootName, Iterator existingNames) { + return uniqueNameFor(rootName, CollectionTools.set(existingNames)); + } + + /** + * Given a "root" name and a set of existing names, generate a unique + * name that is either the "root" name or some variation on the "root" + * name (e.g. "root2", "root3",...). The names are case-sensitive + * (i.e. "Root" and "root" are both allowed). + */ + public static String uniqueNameFor(String rootName, Collection existingNames) { + return uniqueNameFor(rootName, existingNames, rootName); + } + + /** + * Given a "root" name and a set of existing names, generate a unique + * name that is either the "root" name or some variation on the "root" + * name (e.g. "root2", "root3",...). The names are NOT case-sensitive + * (i.e. "Root" and "root" are NOT both allowed). + */ + public static String uniqueNameForIgnoreCase(String rootName, Iterator existingNames) { + return uniqueNameForIgnoreCase(rootName, CollectionTools.set(existingNames)); + } + + /** + * Given a "root" name and a set of existing names, generate a unique + * name that is either the "root" name or some variation on the "root" + * name (e.g. "root2", "root3",...). The names are NOT case-sensitive + * (i.e. "Root" and "root" are NOT both allowed). + */ + public static String uniqueNameForIgnoreCase(String rootName, Collection existingNames) { + return uniqueNameFor(rootName, convertToLowerCase(existingNames), rootName.toLowerCase()); + } + + /** + * use the suffixed "template" name to perform the comparisons, but RETURN + * the suffixed "root" name; this allows case-insensitive comparisons + * (i.e. the "template" name has been morphed to the same case as + * the "existing" names, while the "root" name has not, but the "root" name + * is what the client wants morphed to be unique) + */ + private static String uniqueNameFor(String rootName, Collection existingNames, String templateName) { + if ( ! existingNames.contains(templateName)) { + return rootName; + } + String uniqueName = templateName; + for (int suffix = 2; true; suffix++) { + if ( ! existingNames.contains(uniqueName + suffix)) { + return rootName.concat(String.valueOf(suffix)); + } + } + } + + /** + * Convert the specified collection of strings to a collection of the same + * strings converted to lower case. + */ + private static HashSet convertToLowerCase(Collection strings) { + HashSet result = new HashSet(strings.size()); + for (String string : strings) { + result.add(string.toLowerCase()); + } + return result; + } + + /** + * Build a fully-qualified name for the specified database object. + * Variations: + * catalog.schema.name + * catalog..name + * schema.name + * name + */ + public static String buildQualifiedDatabaseObjectName(String catalog, String schema, String name) { + if (name == null) { + return null; + } + if ((catalog == null) && (schema == null)) { + return name; + } + + StringBuilder sb = new StringBuilder(100); + if (catalog != null) { + sb.append(catalog); + sb.append('.'); + } + if (schema != null) { + sb.append(schema); + } + sb.append('.'); + sb.append(name); + return sb.toString(); + } + + /** + * The set of reserved words in the Java programming language. + * These words cannot be used as identifiers (i.e. names). + * http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html + */ + @SuppressWarnings("nls") + public static final String[] JAVA_RESERVED_WORDS = new String[] { + "abstract", + "assert", // jdk 1.4 + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", // unused + "continue", + "default", + "do", + "double", + "else", + "enum", // jdk 1.5 + "extends", + "false", + "final", + "finally", + "float", + "for", + "goto", // unused + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", // jdk 1.2 + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "void", + "volatile", + "while" + }; + + /** + * The set of reserved words in the Java programming language. + * These words cannot be used as identifiers (i.e. names). + * http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html + */ + public static final SortedSet JAVA_RESERVED_WORDS_SET = + Collections.unmodifiableSortedSet(CollectionTools.sortedSet(JAVA_RESERVED_WORDS)); + + /** + * Return the set of Java programming language reserved words. + * These words cannot be used as identifiers (i.e. names). + * http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html + */ + public static Iterator javaReservedWords() { + return new ArrayIterator(JAVA_RESERVED_WORDS); + } + + /** + * Return whether the specified string consists of Java identifier + * characters (but may be a reserved word). + */ + public static boolean stringConsistsOfJavaIdentifierCharacters(String string) { + if (string.length() == 0) { + return false; + } + return stringConsistsOfJavaIdentifierCharacters_(string.toCharArray()); + } + + /** + * Return whether the specified string consists of Java identifier + * characters (but may be a reserved word). + */ + public static boolean stringConsistsOfJavaIdentifierCharacters(char[] string) { + if (string.length == 0) { + return false; + } + return stringConsistsOfJavaIdentifierCharacters_(string); + } + + /** + * The specified string must not be empty. + */ + private static boolean stringConsistsOfJavaIdentifierCharacters_(char[] string) { + if ( ! Character.isJavaIdentifierStart(string[0])) { + return false; + } + for (int i = string.length; i-- > 1; ) { // NB: end with 1 + if ( ! Character.isJavaIdentifierPart(string[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified string is a valid Java identifier. + */ + public static boolean stringIsLegalJavaIdentifier(String string) { + return stringConsistsOfJavaIdentifierCharacters(string) + && ! JAVA_RESERVED_WORDS_SET.contains(string); + } + + /** + * Return whether the specified string is a valid Java identifier. + */ + public static boolean stringIsLegalJavaIdentifier(char[] string) { + return stringConsistsOfJavaIdentifierCharacters(string) + && ! JAVA_RESERVED_WORDS_SET.contains(new String(string)); + } + + /** + * Convert the specified string to a valid Java identifier + * by substituting an underscore '_' for any invalid characters + * in the string and appending an underscore '_' to the string if + * it is a Java reserved word. + */ + public static String convertToJavaIdentifier(String string) { + return convertToJavaIdentifier(string, '_'); + } + + /** + * Convert the specified string to a valid Java identifier + * by substituting the specified character for any invalid characters + * in the string and, if necessary, appending the specified character + * to the string until it is not a Java reserved word. + */ + public static String convertToJavaIdentifier(String string, char c) { + if (string.length() == 0) { + return string; + } + if (JAVA_RESERVED_WORDS_SET.contains(string)) { + // a reserved word is a valid identifier, we just need to tweak it a bit + checkCharIsJavaIdentifierPart(c); + return convertToJavaIdentifier(string + c, c); + } + char[] array = string.toCharArray(); + return convertToJavaIdentifier_(array, c) ? new String(array) : string; + } + + /** + * Convert the specified string to a valid Java identifier + * by substituting an underscore '_' for any invalid characters + * in the string and appending an underscore '_' to the string if + * it is a Java reserved word. + */ + public static char[] convertToJavaIdentifier(char[] string) { + return convertToJavaIdentifier(string, '_'); + } + + /** + * Convert the specified string to a valid Java identifier + * by substituting the specified character for any invalid characters + * in the string and, if necessary, appending the specified character + * to the string until it is not a Java reserved word. + */ + public static char[] convertToJavaIdentifier(char[] string, char c) { + if (string.length == 0) { + return string; + } + if (JAVA_RESERVED_WORDS_SET.contains(new String(string))) { + // a reserved word is a valid identifier, we just need to tweak it a bit + checkCharIsJavaIdentifierPart(c); + return convertToJavaIdentifier(ArrayTools.add(string, c), c); + } + convertToJavaIdentifier_(string, c); + return string; + } + + /** + * The specified string must not be empty. + * Return whether the string was modified. + */ + private static boolean convertToJavaIdentifier_(char[] string, char c) { + boolean mod = false; + if ( ! Character.isJavaIdentifierStart(string[0])) { + checkCharIsJavaIdentifierStart(c); + string[0] = c; + mod = true; + } + checkCharIsJavaIdentifierPart(c); + for (int i = string.length; i-- > 1; ) { // NB: end with 1 + if ( ! Character.isJavaIdentifierPart(string[i])) { + string[i] = c; + mod = true; + } + } + return mod; + } + + private static void checkCharIsJavaIdentifierStart(char c) { + if ( ! Character.isJavaIdentifierStart(c)) { + throw new IllegalArgumentException("invalid Java identifier start char: '" + c + '\''); //$NON-NLS-1$ + } + } + + private static void checkCharIsJavaIdentifierPart(char c) { + if ( ! Character.isJavaIdentifierPart(c)) { + throw new IllegalArgumentException("invalid Java identifier part char: '" + c + '\''); //$NON-NLS-1$ + } + } + + /** + * Convert the specified method name to a property name. + */ + public static String convertGetterSetterMethodNameToPropertyName(String methodName) { + int beginIndex = 0; + if (methodName.startsWith("get")) { //$NON-NLS-1$ + beginIndex = 3; + } else if (methodName.startsWith("set")) { //$NON-NLS-1$ + beginIndex = 3; + } else if (methodName.startsWith("is")) { //$NON-NLS-1$ + beginIndex = 2; + } else { + return methodName; // return method name unchanged? + } + return Introspector.decapitalize(methodName.substring(beginIndex)); + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private NameTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NonNullBooleanTransformer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NonNullBooleanTransformer.java new file mode 100644 index 0000000000..0c9749fcd3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NonNullBooleanTransformer.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * A NonNullBooleanTransformer will transform a possibly-null + * {@link Boolean} to a non-null {@link Boolean}:
    + *
  • When the original {@link Boolean} is not null, + * the transformer will return it unchanged. + *
  • When the original {@link Boolean} is null, + * the transformer will return its client-specified "null value" + * ({@link Boolean#TRUE} or {@link Boolean#FALSE}). + *
+ */ +public final class NonNullBooleanTransformer + implements Transformer +{ + // not null + private final Boolean nullValue; + + /** + * A {@link Transformer} that will return the original {@link Boolean} when + * it is non-null; otherwise the {@link Transformer} will return + * {@link Boolean#TRUE}. + */ + public static final Transformer TRUE = new NonNullBooleanTransformer(Boolean.TRUE); + + /** + * A {@link Transformer} that will return the original {@link Boolean} when + * it is non-null; otherwise the {@link Transformer} will return + * {@link Boolean#FALSE}. + */ + public static final Transformer FALSE = new NonNullBooleanTransformer(Boolean.FALSE); + + /** + * Return a transformer that will return the specified value if the original + * value is null. Throw a {@link NullPointerException} if the + * specified value is null. + */ + public Transformer valueOf(Boolean b) { + return valueOf(b.booleanValue()); + } + + /** + * Return a transformer that will return the {@link Boolean} corresponding + * to the specified value if the original value is null. + */ + public Transformer valueOf(boolean b) { + return b ? TRUE : FALSE; + } + + /** + * Ensure only 2 constant versions. + */ + private NonNullBooleanTransformer(Boolean nullValue) { + super(); + if (nullValue == null) { + throw new NullPointerException(); + } + this.nullValue = nullValue; + } + + public Boolean transform(Boolean b) { + return (b != null) ? b : this.nullValue; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.nullValue); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotBooleanTransformer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotBooleanTransformer.java new file mode 100644 index 0000000000..c234ac7f73 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotBooleanTransformer.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * A NotBooleanTransformer will transform a + * {@link Boolean} to its NOT value:
    + *
  • If the original {@link Boolean} is {@link Boolean#TRUE}, + * the transformer will return {@link Boolean#FALSE}. + *
  • If the original {@link Boolean} is {@link Boolean#FALSE}, + * the transformer will return {@link Boolean#TRUE}. + *
  • If the original {@link Boolean} is null, + * the transformer will return null. + *
+ */ +public class NotBooleanTransformer + implements BidiTransformer +{ + public static final BidiTransformer INSTANCE = new NotBooleanTransformer(); + + public static BidiTransformer instance() { + return INSTANCE; + } + + // ensure single instance + private NotBooleanTransformer() { + super(); + } + + public Boolean transform(Boolean b) { + return (b == null) ? null : BooleanTools.not(b); + } + + public Boolean reverseTransform(Boolean b) { + return (b == null) ? null : BooleanTools.not(b); + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotNullFilter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotNullFilter.java new file mode 100644 index 0000000000..c1e27e45b9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotNullFilter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +import org.eclipse.jpt.common.utility.Filter; + +/** + * This filter accepts only non-null objects. + */ +public final class NotNullFilter + implements Filter, Serializable +{ + @SuppressWarnings("rawtypes") + public static final Filter INSTANCE = new NotNullFilter(); + + @SuppressWarnings("unchecked") + public static Filter instance() { + return INSTANCE; + } + + // ensure single instance + private NotNullFilter() { + super(); + } + + // accept only non-null objects + public boolean accept(T o) { + return o != null; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NullList.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NullList.java new file mode 100644 index 0000000000..547a882c58 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NullList.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyListIterator; + +/** + * A "null" list is a bit different from an "empty" list: it allows clients to + * add/remove elements to/from it but never changes. This is useful + * for passing to methods that require a "collecting parameter" but the + * client will ignore the resulting "collection". + */ +public final class NullList + implements List, Serializable +{ + + // singleton + @SuppressWarnings("rawtypes") + private static final NullList INSTANCE = new NullList(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static List instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NullList() { + super(); + } + + public boolean add(E o) { + return false; // the list did not change + } + + public void add(int index, E element) { + // ignore + } + + public boolean addAll(Collection c) { + return false; // the list did not change + } + + public boolean addAll(int index, Collection c) { + return false; // the list did not change + } + + public void clear() { + // ignore + } + + public boolean contains(Object o) { + return false; + } + + public boolean containsAll(Collection c) { + return c.isEmpty(); + } + + public E get(int index) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public int indexOf(Object o) { + return -1; + } + + public boolean isEmpty() { + return true; + } + + public Iterator iterator() { + return EmptyIterator.instance(); + } + + public int lastIndexOf(Object o) { + return -1; + } + + public ListIterator listIterator() { + return EmptyListIterator.instance(); + } + + public ListIterator listIterator(int index) { + return EmptyListIterator.instance(); + } + + public boolean remove(Object o) { + return false; // the list did not change + } + + public E remove(int index) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public boolean removeAll(Collection c) { + return false; // the list did not change + } + + public boolean retainAll(Collection c) { + return false; // the list did not change + } + + public E set(int index, E element) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public int size() { + return 0; + } + + public List subList(int fromIndex, int toIndex) { + return this; + } + + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public Object[] toArray() { + return EMPTY_OBJECT_ARRAY; + } + + public T[] toArray(T[] a) { + return a; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Queue.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Queue.java new file mode 100644 index 0000000000..9cc4bc5ce3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Queue.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +/** + * Interface defining the classic queue behavior, + * without the backdoors allowed by {@link java.util.Queue}. + * + * @param the type of elements contained by the queue + */ +public interface Queue { + + /** + * "Enqueue" the specified item to the tail of the queue. + */ + void enqueue(E o); + + /** + * "Dequeue" an item from the head of the queue. + */ + E dequeue(); + + /** + * Return the item on the head of the queue + * without removing it from the queue. + */ + E peek(); + + /** + * Return whether the queue is empty. + */ + boolean isEmpty(); + + + final class Empty implements Queue, Serializable { + @SuppressWarnings("rawtypes") + public static final Queue INSTANCE = new Empty(); + @SuppressWarnings("unchecked") + public static Queue instance() { + return INSTANCE; + } + // ensure single instance + private Empty() { + super(); + } + public void enqueue(E o) { + throw new UnsupportedOperationException(); + } + public E dequeue() { + throw new NoSuchElementException(); + } + public E peek() { + throw new NoSuchElementException(); + } + public boolean isEmpty() { + return true; + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Range.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Range.java new file mode 100644 index 0000000000..bc66046c0a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Range.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * This simple container class simply puts a bit of semantics + * around a pair of numbers. + */ +public class Range + implements Cloneable, Serializable +{ + /** The starting index of the range. */ + public final int start; + + /** The ending index of the range. */ + public final int end; + + /** + * The size can be negative if the ending index + * is less than the starting index. + */ + public final int size; + + private static final long serialVersionUID = 1L; + + + /** + * Construct with the specified start and end, + * both of which are immutable. + */ + public Range(int start, int end) { + super(); + this.start = start; + this.end = end; + this.size = end - start + 1; + } + + /** + * Return whether the range includes the specified + * index. + */ + public boolean includes(int index) { + return (this.start <= index) && (index <= this.end); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if ( ! (o instanceof Range)) { + return false; + } + Range otherRange = (Range) o; + return (this.start == otherRange.start) + && (this.end == otherRange.end); + } + + @Override + public int hashCode() { + return this.start ^ this.end; + } + + @Override + public Range clone() { + try { + return (Range) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + this.start + ", " + this.end + ']'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyBooleanReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyBooleanReference.java new file mode 100644 index 0000000000..0d2c56a3bf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyBooleanReference.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Interface for a container for holding a boolean that cannot be + * changed by clients. + */ +public interface ReadOnlyBooleanReference +{ + /** + * Return the current boolean value. + */ + boolean getValue(); + + /** + * Return whether the current boolean value is equal to the + * specified value. + */ + boolean is(boolean value); + + /** + * Return whether the current boolean value is not equal to + * the specified value. + */ + boolean isNot(boolean value); + + /** + * Return whether the current boolean value is + * true. + */ + boolean isTrue(); + + /** + * Return whether the current boolean value is + * false. + */ + boolean isFalse(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyIntReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyIntReference.java new file mode 100644 index 0000000000..50e5154669 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyIntReference.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Interface for a container for holding an int that cannot be + * changed by clients. + */ +public interface ReadOnlyIntReference + extends Comparable +{ + /** + * Return the current int value. + */ + int getValue(); + + /** + * Return whether the current int value is equal to the + * specified value. + */ + boolean equals(int v); + + /** + * Return whether the current int value is not equal to + * the specified value. + */ + boolean notEqual(int v); + + /** + * Return whether the current int value is zero. + */ + boolean isZero(); + + /** + * Return whether the current int value is not zero. + */ + boolean isNotZero(); + + /** + * Return whether the current int value is greater than + * the specified value. + */ + boolean isGreaterThan(int v); + + /** + * Return whether the current int value is greater than + * or equal to the specified value. + */ + boolean isGreaterThanOrEqual(int v); + + /** + * Return whether the current int value is less than + * the specified value. + */ + boolean isLessThan(int v); + + /** + * Return whether the current int value is less than + * or equal to the specified value. + */ + boolean isLessThanOrEqual(int v); + + /** + * Return whether the current int value is positive. + */ + boolean isPositive(); + + /** + * Return whether the current int value is not positive + * (i.e. negative or zero). + */ + boolean isNotPositive(); + + /** + * Return whether the current int value is negative. + */ + boolean isNegative(); + + /** + * Return whether the current int value is not negative + * (i.e. zero or positive). + */ + boolean isNotNegative(); + + /** + * Return the absolute value of the current int value. + */ + int abs(); + + /** + * Return the negative value of the current int value. + */ + int neg(); + + /** + * Return the current int value plus the specified value. + */ + int add(int v); + + /** + * Return current int value minus the specified value. + */ + int subtract(int v); + + /** + * Return current int value multiplied by the specified value. + */ + int multiply(int v); + + /** + * Return current int value divided by the specified value. + */ + int divide(int v); + + /** + * Return the remainder of the current int value divided by + * the specified value. + */ + int remainder(int v); + + /** + * Return the minimum of the current int value and + * the specified value. + */ + int min(int v); + + /** + * Return the maximum of the current int value and + * the specified value. + */ + int max(int v); + + /** + * Return the current int value raised to the power + * of the specified value. + */ + double pow(int v); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReflectionTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReflectionTools.java new file mode 100644 index 0000000000..6fb33c7f8c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReflectionTools.java @@ -0,0 +1,1544 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; + +/** + * Convenience methods related to the java.lang.reflect package. + * These methods provide shortcuts for manipulating objects via + * reflection; particularly when dealing with fields and/or methods that + * are not publicly accessible or are inherited. + *

+ * In most cases, all exceptions are handled and wrapped in + * {@link java.lang.RuntimeException}s; so these methods should + * be used when there should be no problems using reflection (i.e. + * the referenced members are presumably present etc.). + *

+ * There are also a number of methods whose names + * end with an underscore. These methods declare the expected checked + * exceptions (e.g. {@link NoSuchMethodException}, {@link NoSuchFieldException}). + * These methods can be used to probe + * for methods, fields, etc. that should be present but might not be. + */ +public final class ReflectionTools { + + public static final Class[] ZERO_PARAMETER_TYPES = new Class[0]; + public static final Object[] ZERO_ARGUMENTS = new Object[0]; + private static final String CR = StringTools.CR; + + public static final Class VOID_CLASS = void.class; + public static final Class VOID_WRAPPER_CLASS = java.lang.Void.class; + + + // ********** fields ********** + + /** + * Get a field value, given the containing object and field name. + * Return its result. + * Useful for accessing private, package, or protected fields. + *

+ * Object.getFieldValue(String fieldName) + */ + public static Object getFieldValue(Object object, String fieldName) { + try { + return getFieldValue_(object, fieldName); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(object, fieldName), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(object, fieldName), ex); + } + } + + /** + * Get a field value, given the containing object and field name. + * Return its result. + * Useful for accessing private, package, or protected fields. + *

+ * Object.getFieldValue(String fieldName) + */ + public static Object getFieldValue_(Object object, String fieldName) + throws NoSuchFieldException, IllegalAccessException + { + return getField_(object, fieldName).get(object); + } + + /** + * Get a static field value, given the containing class and field name. + * Return its result. + * Useful for accessing private, package, or protected fields. + *

+ * Class.getStaticFieldValue(String fieldName) + */ + public static Object getStaticFieldValue(Class javaClass, String fieldName) { + try { + return getStaticFieldValue_(javaClass, fieldName); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(javaClass, fieldName), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(javaClass, fieldName), ex); + } + } + + /** + * Get a static field value, given the containing class and field name. + * Return its result. + * Useful for accessing private, package, or protected fields. + *

+ * Class.getStaticFieldValue(String fieldName) + */ + public static Object getStaticFieldValue_(Class javaClass, String fieldName) + throws NoSuchFieldException, IllegalAccessException + { + return getField_(javaClass, fieldName).get(null); + } + + /** + * Set a field value, given the containing object, field name, and new value. + * Useful for accessing private, package, or protected fields. + *

+ * Object.setFieldValue(String fieldName, Object value) + */ + public static void setFieldValue(Object object, String fieldName, Object value) { + try { + setFieldValue_(object, fieldName, value); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(object, fieldName), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(object, fieldName), ex); + } + } + + /** + * Set a field value, given the containing object, field name, and new value. + * Useful for accessing private, package, or protected fields. + *

+ * Object.setFieldValue(String fieldName, Object value) + */ + public static void setFieldValue_(Object object, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException + { + getField_(object, fieldName).set(object, value); + } + + /** + * Set a static field value, given the containing class, field name, and new value. + * Useful for accessing private, package, or protected fields. + *

+ * Class.setStaticFieldValue(String fieldName, Object value) + */ + public static void setStaticFieldValue(Class javaClass, String fieldName, Object value) { + try { + setStaticFieldValue_(javaClass, fieldName, value); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(javaClass, fieldName), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(javaClass, fieldName), ex); + } + } + + /** + * Set a static field value, given the containing class, field name, and new value. + * Useful for accessing private, package, or protected fields. + *

+ * Class.setStaticFieldValue(String fieldName, Object value) + */ + public static void setStaticFieldValue_(Class javaClass, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException + { + getField_(javaClass, fieldName).set(null, value); + } + + /** + * Convenience method. + * Return a field for the specified object and field name. + * If the object's class does not directly + * define the field, look for it in the class's superclasses. + * Make any private/package/protected field accessible. + *

+ * Object.getField(String fieldName) + */ + public static Field getField(Object object, String fieldName) { + try { + return getField_(object, fieldName); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(object, fieldName), ex); + } + } + + /** + * Convenience method. + * Return a field for the specified object and field name. + * If the object's class does not directly + * define the field, look for it in the class's superclasses. + * Make any private/package/protected field accessible. + *

+ * Object.getField(String fieldName) + */ + public static Field getField_(Object object, String fieldName) + throws NoSuchFieldException + { + return getField_(object.getClass(), fieldName); + } + + /** + * Return a field for the specified class and field name. + * If the class does not directly + * define the field, look for it in the class's superclasses. + * Make any private/package/protected field accessible. + */ + public static Field getField(Class javaClass, String fieldName) { + try { + return getField_(javaClass, fieldName); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedFieldName(javaClass, fieldName), ex); + } + } + + /** + * Return a field for the specified class and field name. + * If the class does not directly + * define the field, look for it in the class's superclasses. + * Make any private/package/protected field accessible. + */ + public static Field getField_(Class javaClass, String fieldName) + throws NoSuchFieldException + { + Field field = null; + try { + field = javaClass.getDeclaredField(fieldName); + } catch (NoSuchFieldException ex) { + Class superclass = javaClass.getSuperclass(); + if (superclass == null) { + throw ex; + } + return getField_(superclass, fieldName); // recurse + } + field.setAccessible(true); + return field; + } + + /** + * Return all the fields for the + * specified class, including inherited fields. + * Make any private/package/protected fields accessible. + *

+ * Class.getAllFields() + */ + public static Iterable getAllFields(Class javaClass) { + ArrayList fields = new ArrayList(); + for (Class tempClass = javaClass; tempClass != null; tempClass = tempClass.getSuperclass()) { + addDeclaredFieldsTo(tempClass, fields); + } + return fields; + } + + /* + * Add the declared fields for the specified class + * to the specified list. + */ + private static void addDeclaredFieldsTo(Class javaClass, ArrayList fields) { + for (Field field : getDeclaredFields(javaClass)) { + fields.add(field); + } + } + + /** + * Return the declared fields for the specified class. + * Make any private/package/protected fields accessible. + *

+ * Class.getAccessibleDeclaredFields() + */ + public static Iterable getDeclaredFields(Class javaClass) { + Field[] fields = javaClass.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + } + return new ArrayIterable(fields); + } + + + // ********** methods ********** + + /** + * Convenience method. + * Execute a zero-argument method, given the receiver and method name. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName) + */ + public static Object executeMethod(Object receiver, String methodName) { + return executeMethod(receiver, methodName, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Execute a one-argument method, given the receiver, + * method name, parameter type, and argument. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName, Class parameterType, Object argument) + */ + public static Object executeMethod(Object receiver, String methodName, Class parameterType, Object argument) { + return executeMethod(receiver, methodName, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Execute a method, given the receiver, + * method name, parameter types, and arguments. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName, Class[] parameterTypes, Object[] arguments) + */ + public static Object executeMethod(Object receiver, String methodName, Class[] parameterTypes, Object[] arguments) { + return executeMethod(getMethod(receiver, methodName, parameterTypes), receiver, arguments); + } + + /** + * Execute the specified method, given the receiver and arguments. + * Return its result. + * Useful for invoking cached methods. + */ + public static Object executeMethod(Method method, Object receiver, Object[] arguments) { + try { + return method.invoke(receiver, arguments); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + method, ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(method + CR + ex.getTargetException(), ex); + } + } + + /** + * Convenience method. + * Execute a zero-argument method, + * given the receiver and method name. + * Return its result. + * Throw an exception if the method is not defined. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName) + */ + public static Object executeMethod_(Object receiver, String methodName) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return executeMethod_(receiver, methodName, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Execute a method, given the receiver, + * method name, parameter type, and argument. + * Return its result. + * Throw an exception if the method is not defined. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName, Class parameterType, Object argument) + */ + public static Object executeMethod_(Object receiver, String methodName, Class parameterType, Object argument) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return executeMethod_(receiver, methodName, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Execute a method, given the receiver, + * method name, parameter types, and arguments. + * Return its result. + * Throw an exception if the method is not defined. + * Useful for invoking private, package, or protected methods. + *

+ * Object.execute(String methodName, Class[] parameterTypes, Object[] arguments) + */ + public static Object executeMethod_(Object receiver, String methodName, Class[] parameterTypes, Object[] arguments) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return getMethod_(receiver, methodName, parameterTypes).invoke(receiver, arguments); + } + + /** + * Convenience method. + * Execute a zero-argument static method, + * given the class and method name. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName) + */ + public static Object executeStaticMethod(Class javaClass, String methodName) { + return executeStaticMethod(javaClass, methodName, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Execute a static method, given the class, + * method name, parameter type, and argument. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName, Class parameterType, Object argument) + */ + public static Object executeStaticMethod(Class javaClass, String methodName, Class parameterType, Object argument) { + return executeStaticMethod(javaClass, methodName, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Execute a static method, given the class, + * method name, parameter types, and arguments. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName, Class[] parameterTypes, Object[] arguments) + */ + public static Object executeStaticMethod(Class javaClass, String methodName, Class[] parameterTypes, Object[] arguments) { + try { + return executeStaticMethod_(javaClass, methodName, parameterTypes, arguments); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes), ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes) + CR + ex.getTargetException(), ex); + } + } + + /** + * Convenience method. + * Execute a zero-argument static method, + * given the class and method name. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName) + */ + public static Object executeStaticMethod_(Class javaClass, String methodName) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return executeStaticMethod_(javaClass, methodName, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Execute a static method, given the class, + * method name, parameter type, and argument. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName, Class parameterType, Object argument) + */ + public static Object executeStaticMethod_(Class javaClass, String methodName, Class parameterType, Object argument) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return executeStaticMethod_(javaClass, methodName, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Execute a static method, given the class, + * method name, parameter types, and arguments. + * Return its result. + * Useful for invoking private, package, or protected methods. + *

+ * Class.executeStaticMethod(String methodName, Class[] parameterTypes, Object[] arguments) + */ + public static Object executeStaticMethod_(Class javaClass, String methodName, Class[] parameterTypes, Object[] arguments) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + return getStaticMethod_(javaClass, methodName, parameterTypes).invoke(null, arguments); + } + + /** + * Convenience method. + * Return a zero-argument method for the specified class + * and method name. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Class javaClass, String methodName) { + return getMethod(javaClass, methodName, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return a zero-argument method for the specified class + * and method name. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Class javaClass, String methodName) + throws NoSuchMethodException + { + return getMethod_(javaClass, methodName, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return a method for the specified class, method name, + * and formal parameter type. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Class javaClass, String methodName, Class parameterType) { + return getMethod(javaClass, methodName, new Class[] {parameterType}); + } + + /** + * Convenience method. + * Return a method for the specified class, method name, + * and formal parameter type. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Class javaClass, String methodName, Class parameterType) + throws NoSuchMethodException + { + return getMethod_(javaClass, methodName, new Class[] {parameterType}); + } + + /** + * Return a method for the specified class, method name, + * and formal parameter types. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Class javaClass, String methodName, Class[] parameterTypes) { + try { + return getMethod_(javaClass, methodName, parameterTypes); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes), ex); + } + } + + /** + * Return a method for the specified class, method name, + * and formal parameter types. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Class javaClass, String methodName, Class[] parameterTypes) + throws NoSuchMethodException + { + Method method = null; + try { + method = javaClass.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException ex) { + Class superclass = javaClass.getSuperclass(); + if (superclass == null) { + throw ex; + } + // recurse + return getMethod_(superclass, methodName, parameterTypes); + } + method.setAccessible(true); + return method; + } + + /** + * Convenience method. + * Return a zero-argument method for the specified object + * and method name. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Object object, String methodName) { + return getMethod(object.getClass(), methodName); + } + + /** + * Convenience method. + * Return a zero-argument method for the specified object + * and method name. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Object object, String methodName) + throws NoSuchMethodException + { + return getMethod_(object.getClass(), methodName); + } + + /** + * Convenience method. + * Return a method for the specified object, method name, + * and formal parameter types. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Object object, String methodName, Class[] parameterTypes) { + return getMethod(object.getClass(), methodName, parameterTypes); + } + + /** + * Convenience method. + * Return a method for the specified object, method name, + * and formal parameter types. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Object object, String methodName, Class[] parameterTypes) + throws NoSuchMethodException + { + return getMethod_(object.getClass(), methodName, parameterTypes); + } + + /** + * Convenience method. + * Return a method for the specified object, method name, + * and formal parameter type. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod(Object object, String methodName, Class parameterType) { + return getMethod(object.getClass(), methodName, parameterType); + } + + /** + * Convenience method. + * Return a method for the specified object, method name, + * and formal parameter type. If the object's class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getMethod_(Object object, String methodName, Class parameterType) + throws NoSuchMethodException + { + return getMethod_(object.getClass(), methodName, parameterType); + } + + /** + * Convenience method. + * Return a zero-argument static method for the specified class + * and method name. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod(Class javaClass, String methodName) { + return getStaticMethod(javaClass, methodName, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return a static method for the specified class, method name, + * and formal parameter type. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod(Class javaClass, String methodName, Class parameterTypes) { + return getStaticMethod(javaClass, methodName, new Class[] {parameterTypes}); + } + + /** + * Return a static method for the specified class, method name, + * and formal parameter types. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod(Class javaClass, String methodName, Class[] parameterTypes) { + try { + return getStaticMethod_(javaClass, methodName, parameterTypes); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes), ex); + } + } + + /** + * Convenience method. + * Return a zero-argument static method for the specified class + * and method name. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod_(Class javaClass, String methodName) + throws NoSuchMethodException + { + return getStaticMethod_(javaClass, methodName, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return a static method for the specified class, method name, + * and formal parameter type. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod_(Class javaClass, String methodName, Class parameterTypes) + throws NoSuchMethodException + { + return getStaticMethod_(javaClass, methodName, new Class[] {parameterTypes}); + } + + /** + * Return a static method for the specified class, method name, + * and formal parameter types. If the class does not directly + * implement the method, look for it in the class's superclasses. + * Make any private/package/protected method accessible. + */ + public static Method getStaticMethod_(Class javaClass, String methodName, Class[] parameterTypes) + throws NoSuchMethodException + { + Method method = getMethod_(javaClass, methodName, parameterTypes); + if (Modifier.isStatic(method.getModifiers())) { + return method; + } + throw new NoSuchMethodException(buildFullyQualifiedMethodSignature(javaClass, methodName, parameterTypes)); + } + + /** + * Return all the methods for the + * specified class, including inherited methods. + * Make any private/package/protected methods accessible. + *

+ * Class.getAllMethods() + */ + public static Iterable getAllMethods(Class javaClass) { + ArrayList methods = new ArrayList(); + for (Class tempClass = javaClass; tempClass != null; tempClass = tempClass.getSuperclass()) { + addDeclaredMethodsTo(tempClass, methods); + } + return methods; + } + + /* + * Add the declared methods for the specified class + * to the specified list. + */ + private static void addDeclaredMethodsTo(Class javaClass, ArrayList methods) { + for (Method method : getDeclaredMethods(javaClass)) { + methods.add(method); + } + } + + /** + * Return the declared methods for the specified class. + * Make any private/package/protected methods accessible. + *

+ * Class.getAccessibleDeclaredMethods() + */ + public static Iterable getDeclaredMethods(Class javaClass) { + Method[] methods = javaClass.getDeclaredMethods(); + for (Method method : methods) { + method.setAccessible(true); + } + return new ArrayIterable(methods); + } + + + // ********** constructors ********** + + /** + * Return the default (zero-argument) constructor + * for the specified class. + * Make any private/package/protected constructor accessible. + *

+ * Class.getDefaultConstructor() + */ + public static Constructor getDefaultConstructor(Class javaClass) { + return getConstructor(javaClass); + } + + /** + * Convenience method. + * Return the default (zero-argument) constructor + * for the specified class. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor() + */ + public static Constructor getConstructor(Class javaClass) { + return getConstructor(javaClass, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return the constructor for the specified class + * and formal parameter type. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor(Class parameterType) + */ + public static Constructor getConstructor(Class javaClass, Class parameterType) { + return getConstructor(javaClass, new Class[] {parameterType}); + } + + /** + * Return the constructor for the specified class + * and formal parameter types. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor(Class[] parameterTypes) + */ + public static Constructor getConstructor(Class javaClass, Class[] parameterTypes) { + try { + return getConstructor_(javaClass, parameterTypes); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedConstructorSignature(javaClass, parameterTypes), ex); + } + } + + /** + * Return the default (zero-argument) constructor + * for the specified class. + * Make any private/package/protected constructor accessible. + *

+ * Class.getDefaultConstructor() + */ + public static Constructor getDefaultConstructor_(Class javaClass) + throws NoSuchMethodException + { + return getConstructor_(javaClass); + } + + /** + * Convenience method. + * Return the default (zero-argument) constructor + * for the specified class. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor() + */ + public static Constructor getConstructor_(Class javaClass) + throws NoSuchMethodException + { + return getConstructor_(javaClass, ZERO_PARAMETER_TYPES); + } + + /** + * Convenience method. + * Return the constructor for the specified class + * and formal parameter type. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor(Class parameterType) + */ + public static Constructor getConstructor_(Class javaClass, Class parameterType) + throws NoSuchMethodException + { + return getConstructor_(javaClass, new Class[] {parameterType}); + } + + /** + * Return the constructor for the specified class + * and formal parameter types. + * Make any private/package/protected constructor accessible. + *

+ * Class.getConstructor(Class[] parameterTypes) + */ + public static Constructor getConstructor_(Class javaClass, Class[] parameterTypes) + throws NoSuchMethodException + { + Constructor constructor = javaClass.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } + + /** + * Return the declared constructors for the specified class. + * Make any private/package/protected constructors accessible. + *

+ * Class.getAccessibleDeclaredConstructors() + */ + public static Iterable> getDeclaredConstructors(Class javaClass) { + @SuppressWarnings("unchecked") + Constructor[] constructors = (Constructor[]) javaClass.getDeclaredConstructors(); + for (Constructor constructor : constructors) { + constructor.setAccessible(true); + } + return new ArrayIterable>(constructors); + } + + + // ********** classes ********** + + /** + * Convenience method. + * Return the specified class (without the checked exception). + */ + public static Class classForName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(className, ex); + } + } + + /** + * Convenience method. + * Return the specified class (without the checked exception). + */ + public static Class classForName(String className, boolean initialize, ClassLoader classLoader) { + try { + return Class.forName(className, initialize, classLoader); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(className, ex); + } + } + + /** + * Return the "array depth" of the specified class. + * The depth is the number of dimensions for an array type. + * Non-array types have a depth of zero. + *

+ * Class.getArrayDepth() + */ + public static int getArrayDepth(Class javaClass) { + int depth = 0; + while (javaClass.isArray()) { + depth++; + javaClass = javaClass.getComponentType(); + } + return depth; + } + + /** + * Return the "element type" of the specified class. + * The element type is the base type held by an array type. + * A non-array type simply returns itself. + *

+ * Class.getElementType() + */ + public static Class getElementType(Class javaClass) { + while (javaClass.isArray()) { + javaClass = javaClass.getComponentType(); + } + return javaClass; + } + + /** + * Return the wrapper class corresponding to the specified + * primitive class. Return null if the specified class + * is not a primitive class. + *

+ * Class.getWrapperClass() + */ + public static Class getWrapperClass(Class primitiveClass) { + for (Primitive primitive : PRIMITIVES) { + if (primitive.javaClass == primitiveClass) { + return primitive.wrapperClass; + } + } + return null; + } + + /** + * Return whether the specified class is a primitive wrapper + * class (i.e. java.lang.Void or one of the primitive + * variable wrapper classes, java.lang.Boolean, + * java.lang.Integer, java.lang.Float, etc.). + *

+ * NB: void.class.isPrimitive() == true + *

+ * Class.isPrimitiveWrapper() + */ + public static boolean classIsPrimitiveWrapper(Class javaClass) { + if (javaClass.isArray() || (javaClass.getName().length() > MAX_PRIMITIVE_WRAPPER_CLASS_NAME_LENGTH)) { + return false; // performance tweak + } + for (Primitive primitive : PRIMITIVES) { + if (javaClass == primitive.wrapperClass) { + return true; + } + } + return false; + } + + /** + * Return whether the specified class is a "variable" primitive wrapper + * class (i.e. java.lang.Boolean, + * java.lang.Integer, java.lang.Float, etc., + * but not java.lang.Void). + *

+ * NB: void.class.isPrimitive() == true + *

+ * Class.isVariablePrimitiveWrapper() + */ + public static boolean classIsVariablePrimitiveWrapper(Class javaClass) { + return classIsPrimitiveWrapper(javaClass) + && (javaClass != VOID_WRAPPER_CLASS); + } + + /** + * Return whether the specified class is a "variable" primitive + * class (i.e. boolean, int, + * float, etc., but not void). + *

+ * NB: void.class.isPrimitive() == true + *

+ * Class.isVariablePrimitive() + */ + public static boolean classIsVariablePrimitive(Class javaClass) { + return javaClass.isPrimitive() && (javaClass != VOID_CLASS); + } + + /** + * Return the primitive class for the specified primitive class code. + * Return null if the specified code + * is not a primitive class code. + * @see java.lang.Class#getName() + */ + public static Class getClassForCode(int classCode) { + return getClassForCode((char) classCode); + } + + /** + * Return the primitive class for the specified primitive class code. + * Return null if the specified code + * is not a primitive class code. + * @see java.lang.Class#getName() + */ + public static Class getClassForCode(char classCode) { + for (Primitive primitive : PRIMITIVES) { + if (primitive.code == classCode) { + return primitive.javaClass; + } + } + return null; + } + + /** + * Return the class code for the specified primitive class. + * Return 0 if the specified class + * is not a primitive class. + * @see java.lang.Class#getName() + */ + public static char getCodeForClass(Class primitiveClass) { + if (( ! primitiveClass.isArray()) && (primitiveClass.getName().length() <= MAX_PRIMITIVE_CLASS_NAME_LENGTH)) { + for (Primitive primitive : PRIMITIVES) { + if (primitive.javaClass == primitiveClass) { + return primitive.code; + } + } + } + return 0; + } + + + // ********** instantiation ********** + + /** + * Convenience method. + * Return a new instance of the specified class, + * using the class's default (zero-argument) constructor. + *

+ * Class.newInstance() + */ + public static Object newInstance(String className) { + return newInstance(className, null); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter type and argument. + *

+ * Class.newInstance(Class parameterType, Object argument) + */ + public static Object newInstance(String className, Class parameterType, Object argument) { + return newInstance(className, parameterType, argument, null); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter types and arguments. + *

+ * Class.newInstance(Class[] parameterTypes, Object[] arguments) + */ + public static Object newInstance(String className, Class[] parameterTypes, Object[] arguments) { + return newInstance(className, parameterTypes, arguments, null); + } + + /** + * Convenience method. + * Return a new instance of the specified class, + * using the class's default (zero-argument) constructor. + * Use the specified class loader to load the class. + *

+ * Class.newInstance() + */ + public static Object newInstance(String className, ClassLoader classLoader) { + return newInstance(classForName(className, false, classLoader)); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter type and argument. + *

+ * Class.newInstance(Class parameterType, Object argument) + */ + public static Object newInstance(String className, Class parameterType, Object argument, ClassLoader classLoader) { + return newInstance(classForName(className, false, classLoader), parameterType, argument); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter types and arguments. + *

+ * Class.newInstance(Class[] parameterTypes, Object[] arguments) + */ + public static Object newInstance(String className, Class[] parameterTypes, Object[] arguments, ClassLoader classLoader) { + return newInstance(classForName(className, false, classLoader), parameterTypes, arguments); + } + + /** + * Convenience method. + * Return a new instance of the specified class, + * using the class's default (zero-argument) constructor. + *

+ * Class.newInstance() + */ + public static T newInstance(Class javaClass) { + return newInstance(javaClass, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Return a new instance of the specified class, + * given the constructor parameter type and argument. + *

+ * Class.newInstance(Class parameterType, Object argument) + */ + public static T newInstance(Class javaClass, Class parameterType, Object argument) { + return newInstance(javaClass, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter types and arguments. + *

+ * Class.newInstance(Class[] parameterTypes, Object[] arguments) + */ + public static T newInstance(Class javaClass, Class[] parameterTypes, Object[] arguments) { + try { + return newInstance_(javaClass, parameterTypes, arguments); + } catch (InstantiationException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedConstructorSignature(javaClass, parameterTypes), ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedConstructorSignature(javaClass, parameterTypes), ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(buildFullyQualifiedConstructorSignature(javaClass, parameterTypes) + CR + ex.getTargetException(), ex); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex + CR + buildFullyQualifiedConstructorSignature(javaClass, parameterTypes), ex); + } + } + + /** + * Convenience method. + * Return a new instance of the specified class, + * using the class's default (zero-argument) constructor. + *

+ * Class.newInstance() + */ + public static T newInstance_(Class javaClass) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException + { + return newInstance_(javaClass, ZERO_PARAMETER_TYPES, ZERO_ARGUMENTS); + } + + /** + * Convenience method. + * Return a new instance of the specified class, + * given the constructor parameter type and argument. + *

+ * Class.newInstance(Class parameterType, Object argument) + */ + public static T newInstance_(Class javaClass, Class parameterType, Object argument) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException + { + return newInstance_(javaClass, new Class[] {parameterType}, new Object[] {argument}); + } + + /** + * Return a new instance of the specified class, + * given the constructor parameter types and arguments. + *

+ * Class.newInstance(Class[] parameterTypes, Object[] arguments) + */ + public static T newInstance_(Class javaClass, Class[] parameterTypes, Object[] arguments) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException + { + return getConstructor_(javaClass, parameterTypes).newInstance(arguments); + } + + + // ********** type declarations ********** + + /** + * Return the class for the specified "type declaration". + */ + public static Class getClassForTypeDeclaration(String typeDeclaration) { + return getClassForTypeDeclaration(typeDeclaration, null); + } + + /** + * Return the class for the specified "type declaration". + */ + public static Class getClassForTypeDeclaration_(String typeDeclaration) + throws ClassNotFoundException + { + return getClassForTypeDeclaration_(typeDeclaration, null); + } + + /** + * Return the class for the specified "type declaration", + * using the specified class loader. + */ + public static Class getClassForTypeDeclaration(String typeDeclaration, ClassLoader classLoader) { + TypeDeclaration td = buildTypeDeclaration(typeDeclaration); + return getClassForTypeDeclaration(td.elementTypeName, td.arrayDepth, classLoader); + } + + /** + * Return the class for the specified "type declaration", + * using the specified class loader. + */ + public static Class getClassForTypeDeclaration_(String typeDeclaration, ClassLoader classLoader) + throws ClassNotFoundException + { + TypeDeclaration td = buildTypeDeclaration(typeDeclaration); + return getClassForTypeDeclaration_(td.elementTypeName, td.arrayDepth, classLoader); + } + + private static TypeDeclaration buildTypeDeclaration(String typeDeclaration) { + typeDeclaration = StringTools.removeAllWhitespace(typeDeclaration); + int arrayDepth = getArrayDepthForTypeDeclaration_(typeDeclaration); + String elementTypeName = getElementTypeNameForTypeDeclaration_(typeDeclaration, arrayDepth); + return new TypeDeclaration(elementTypeName, arrayDepth); + } + + /** + * Return the array depth for the specified "type declaration"; e.g.

    + *
  • "int[]" returns 1 + *
  • "java.lang.String[][][]" returns 3 + *
+ */ + public static int getArrayDepthForTypeDeclaration(String typeDeclaration) { + return getArrayDepthForTypeDeclaration_(StringTools.removeAllWhitespace(typeDeclaration)); + } + + /** + * pre-condition: no whitespace in the type declaration. + */ + private static int getArrayDepthForTypeDeclaration_(String typeDeclaration) { + int last = typeDeclaration.length() - 1; + int depth = 0; + int close = last; + while (typeDeclaration.charAt(close) == ']') { + if (typeDeclaration.charAt(close - 1) == '[') { + depth++; + } else { + throw new IllegalArgumentException("invalid type declaration: " + typeDeclaration); //$NON-NLS-1$ + } + close = last - (depth * 2); + } + return depth; + } + + /** + * Return the element type name for the specified "type declaration"; e.g.
    + *
  • "int[]" returns "int" + *
  • "java.lang.String[][][]" returns "java.lang.String" + *
+ */ + public static String getElementTypeNameForTypeDeclaration(String typeDeclaration) { + typeDeclaration = StringTools.removeAllWhitespace(typeDeclaration); + return getElementTypeNameForTypeDeclaration_(typeDeclaration, getArrayDepthForTypeDeclaration_(typeDeclaration)); + } + + /** + * Return the element type name for the specified "type declaration"; e.g.
    + *
  • "int[]" returns "int" + *
  • "java.lang.String[][][]" returns "java.lang.String" + *
+ * Useful for clients that have already queried the type declaration's array depth. + */ + public static String getElementTypeNameForTypeDeclaration(String typeDeclaration, int arrayDepth) { + return getElementTypeNameForTypeDeclaration_(StringTools.removeAllWhitespace(typeDeclaration), arrayDepth); + } + + /** + * pre-condition: no whitespace in the type declaration. + */ + private static String getElementTypeNameForTypeDeclaration_(String typeDeclaration, int arrayDepth) { + return typeDeclaration.substring(0, typeDeclaration.length() - (arrayDepth * 2)); + } + + /** + * Return the class for the specified "type declaration". + */ + public static Class getClassForTypeDeclaration(String elementTypeName, int arrayDepth) { + return getClassForTypeDeclaration(elementTypeName, arrayDepth, null); + } + + /** + * Return the class for the specified "type declaration". + */ + public static Class getClassForTypeDeclaration_(String elementTypeName, int arrayDepth) + throws ClassNotFoundException + { + return getClassForTypeDeclaration_(elementTypeName, arrayDepth, null); + } + + /** + * Return the class for the specified "type declaration", + * using the specified class loader. + */ + public static Class getClassForTypeDeclaration(String elementTypeName, int arrayDepth, ClassLoader classLoader) { + try { + return getClassForTypeDeclaration_(elementTypeName, arrayDepth, classLoader); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Return the class for the specified "type declaration", + * using the specified class loader. + */ + // see the "Evaluation" of JDK bug 6446627 for a discussion of loading classes + public static Class getClassForTypeDeclaration_(String elementTypeName, int arrayDepth, ClassLoader classLoader) + throws ClassNotFoundException + { + // primitives cannot be loaded via Class.forName(), + // so check for a primitive class name first + Primitive pcc = null; + if (elementTypeName.length() <= MAX_PRIMITIVE_CLASS_NAME_LENGTH) { // performance tweak + for (Primitive primitive : PRIMITIVES) { + if (primitive.javaClass.getName().equals(elementTypeName)) { + pcc = primitive; + break; + } + } + } + + // non-array + if (arrayDepth == 0) { + return (pcc == null) ? Class.forName(elementTypeName, false, classLoader) : pcc.javaClass; + } + + // array + StringBuilder sb = new StringBuilder(100); + for (int i = arrayDepth; i-- > 0; ) { + sb.append('['); + } + if (pcc == null) { + ClassName.append(elementTypeName, sb); + } else { + sb.append(pcc.code); + } + return Class.forName(sb.toString(), false, classLoader); + } + + /** + * Return the class name for the specified "type declaration"; e.g.
    + *
  • "int" returns "int" + *
  • "int[]" returns "[I" + *
  • "java.lang.String" returns "java.lang.String" + *
  • "java.lang.String[][][]" returns "[[[Ljava.lang.String;" + *
+ * @see java.lang.Class#getName() + */ + public static String getClassNameForTypeDeclaration(String typeDeclaration) { + TypeDeclaration td = buildTypeDeclaration(typeDeclaration); + return getClassNameForTypeDeclaration(td.elementTypeName, td.arrayDepth); + } + + /** + * Return the class name for the specified "type declaration". + * @see java.lang.Class#getName() + */ + public static String getClassNameForTypeDeclaration(String elementTypeName, int arrayDepth) { + // non-array + if (arrayDepth == 0) { + return elementTypeName; + } + + if (elementTypeName.equals(ClassName.VOID_CLASS_NAME)) { + throw new IllegalArgumentException('\'' + ClassName.VOID_CLASS_NAME + "' must have an array depth of zero: " + arrayDepth + '.'); //$NON-NLS-1$ + } + // array + StringBuilder sb = new StringBuilder(100); + for (int i = arrayDepth; i-- > 0; ) { + sb.append('['); + } + + // look for a primitive first + Primitive pcc = null; + if (elementTypeName.length() <= MAX_PRIMITIVE_CLASS_NAME_LENGTH) { // performance tweak + for (Primitive primitive : PRIMITIVES) { + if (primitive.javaClass.getName().equals(elementTypeName)) { + pcc = primitive; + break; + } + } + } + + if (pcc == null) { + ClassName.append(elementTypeName, sb); + } else { + sb.append(pcc.code); + } + + return sb.toString(); + } + + + // ********** exception messages ********** + + /** + * Return a string representation of the specified constructor. + */ + private static String buildFullyQualifiedConstructorSignature(Class javaClass, Class[] parameterTypes) { + return buildFullyQualifiedMethodSignature(javaClass, null, parameterTypes); + } + + /** + * Return a string representation of the specified field. + */ + private static String buildFullyQualifiedFieldName(Class javaClass, String fieldName) { + StringBuilder sb = new StringBuilder(200); + sb.append(javaClass.getName()); + sb.append('.'); + sb.append(fieldName); + return sb.toString(); + } + + /** + * Return a string representation of the specified field. + */ + private static String buildFullyQualifiedFieldName(Object object, String fieldName) { + return buildFullyQualifiedFieldName(object.getClass(), fieldName); + } + + /** + * Return a string representation of the specified method. + */ + private static String buildFullyQualifiedMethodSignature(Class javaClass, String methodName, Class[] parameterTypes) { + StringBuilder sb = new StringBuilder(200); + sb.append(javaClass.getName()); + // this check allows us to use this code for constructors, where the methodName is null + if (methodName != null) { + sb.append('.'); + sb.append(methodName); + } + sb.append('('); + int max = parameterTypes.length - 1; + if (max != -1) { + // stop one short of the end of the array + for (int i = 0; i < max; i++) { + sb.append(parameterTypes[i].getName()); + sb.append(", "); //$NON-NLS-1$ + } + sb.append(parameterTypes[max].getName()); + } + sb.append(')'); + return sb.toString(); + } + + + // ********** primitive constants ********** + + static final Iterable PRIMITIVES = buildPrimitives(); + + public static final char BYTE_CODE = 'B'; + public static final char CHAR_CODE = 'C'; + public static final char DOUBLE_CODE = 'D'; + public static final char FLOAT_CODE = 'F'; + public static final char INT_CODE = 'I'; + public static final char LONG_CODE = 'J'; + public static final char SHORT_CODE = 'S'; + public static final char BOOLEAN_CODE = 'Z'; + public static final char VOID_CODE = 'V'; + + static final int MAX_PRIMITIVE_CLASS_NAME_LENGTH = calculateMaxPrimitiveClassNameLength(); + static final int MAX_PRIMITIVE_WRAPPER_CLASS_NAME_LENGTH = calculateMaxPrimitiveWrapperClassNameLength(); + + private static int calculateMaxPrimitiveClassNameLength() { + int max = -1; + for (Primitive primitive : PRIMITIVES) { + int len = primitive.javaClass.getName().length(); + if (len > max) { + max = len; + } + } + return max; + } + + private static int calculateMaxPrimitiveWrapperClassNameLength() { + int max = -1; + for (Primitive primitive : PRIMITIVES) { + int len = primitive.wrapperClass.getName().length(); + if (len > max) { + max = len; + } + } + return max; + } + + /** + * NB: void.class.isPrimitive() == true + */ + private static Iterable buildPrimitives() { + Primitive[] array = new Primitive[9]; + array[0] = new Primitive(BYTE_CODE, java.lang.Byte.class); + array[1] = new Primitive(CHAR_CODE, java.lang.Character.class); + array[2] = new Primitive(DOUBLE_CODE, java.lang.Double.class); + array[3] = new Primitive(FLOAT_CODE, java.lang.Float.class); + array[4] = new Primitive(INT_CODE, java.lang.Integer.class); + array[5] = new Primitive(LONG_CODE, java.lang.Long.class); + array[6] = new Primitive(SHORT_CODE, java.lang.Short.class); + array[7] = new Primitive(BOOLEAN_CODE, java.lang.Boolean.class); + array[8] = new Primitive(VOID_CODE, java.lang.Void.class); + return new ArrayIterable(array); + } + + + // ********** member classes ********** + + static class Primitive { + final char code; + final Class javaClass; + final Class wrapperClass; + private static final String WRAPPER_CLASS_TYPE_FIELD_NAME = "TYPE"; //$NON-NLS-1$ + // e.g. java.lang.Boolean.TYPE => boolean.class + Primitive(char code, Class wrapperClass) { + this.code = code; + this.wrapperClass = wrapperClass; + this.javaClass = (Class) getStaticFieldValue(wrapperClass, WRAPPER_CLASS_TYPE_FIELD_NAME); + } + } + + private static class TypeDeclaration { + final String elementTypeName; + final int arrayDepth; + TypeDeclaration(String elementTypeName, int arrayDepth) { + this.elementTypeName = elementTypeName; + this.arrayDepth = arrayDepth; + } + } + + + // ********** suppressed constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private ReflectionTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReverseComparator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReverseComparator.java new file mode 100644 index 0000000000..948b8588e4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReverseComparator.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * This comparator will reverse the order of the specified comparator. + * If the comparator is null, the natural ordering of the objects will be used. + */ +public class ReverseComparator> + implements Comparator, Serializable +{ + private final Comparator comparator; + + public ReverseComparator() { + this(null); + } + + public ReverseComparator(Comparator comparator) { + super(); + this.comparator = comparator; + } + + public int compare(E e1, E e2) { + return (this.comparator == null) ? + e2.compareTo(e1) + : + this.comparator.compare(e2, e1); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/RunnableCommand.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/RunnableCommand.java new file mode 100644 index 0000000000..997996b5ff --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/RunnableCommand.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Wrap a Runnable so it can be used as a Command. + */ +public class RunnableCommand implements Command { + protected final Runnable runnable; + + public RunnableCommand(Runnable runnable) { + super(); + if (runnable == null) { + throw new NullPointerException(); + } + this.runnable = runnable; + } + + public void execute() { + this.runnable.run(); + } + + @Override + public String toString() { + return "Command[" + this.runnable.toString() +']'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleAssociation.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleAssociation.java new file mode 100644 index 0000000000..8490c267b4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleAssociation.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Straightforward implementation of {@link Association}. + */ +public class SimpleAssociation + extends AbstractAssociation + implements Cloneable, Serializable +{ + private final K key; + private V value; + + private static final long serialVersionUID = 1L; + + + /** + * Construct an association with the specified key + * and a null value. + */ + public SimpleAssociation(K key) { + super(); + this.key = key; + } + + /** + * Construct an association with the specified key and value. + */ + public SimpleAssociation(K key, V value) { + this(key); + this.value = value; + } + + + public K getKey() { + return this.key; + } + + public synchronized V getValue() { + return this.value; + } + + public synchronized V setValue(V value) { + V old = this.value; + this.value = value; + return old; + } + + @Override + @SuppressWarnings("unchecked") + public synchronized SimpleAssociation clone() { + try { + return (SimpleAssociation) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleBooleanReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleBooleanReference.java new file mode 100644 index 0000000000..9dabf58f59 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleBooleanReference.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Provide a container for passing a flag that can be changed by the recipient. + * + * @see SynchronizedBoolean + */ +public class SimpleBooleanReference + implements BooleanReference, Cloneable, Serializable +{ + /** Backing boolean. */ + protected volatile boolean value; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create a boolean reference with the specified initial value. + */ + public SimpleBooleanReference(boolean value) { + super(); + this.value = value; + } + + /** + * Create a boolean reference with an initial value of + * false. + */ + public SimpleBooleanReference() { + this(false); + } + + + // ********** accessors ********** + + public boolean getValue() { + return this.value; + } + + public boolean is(boolean v) { + return this.value == v; + } + + public boolean isNot(boolean v) { + return this.value != v; + } + + public boolean isTrue() { + return this.value; + } + + public boolean isFalse() { + return ! this.value; + } + + public boolean setValue(boolean value) { + boolean old = this.value; + this.value = value; + return old; + } + + public boolean flip() { + return this.value = ! this.value; + } + + public boolean setNot(boolean v) { + return this.setValue( ! v); + } + + public boolean setTrue() { + return this.setValue(true); + } + + public boolean setFalse() { + return this.setValue(false); + } + + + // ********** standard methods ********** + + @Override + public SimpleBooleanReference clone() { + try { + return (SimpleBooleanReference) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.value) + ']'; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleCommandExecutor.java new file mode 100644 index 0000000000..169654a766 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleCommandExecutor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Straightforward implementation of {@link StatefulCommandExecutor}. + */ +public class SimpleCommandExecutor + implements StatefulCommandExecutor +{ + private boolean active = false; + + public SimpleCommandExecutor() { + super(); + } + + public void start() { + if (this.active) { + throw new IllegalStateException("Not stopped."); //$NON-NLS-1$ + } + this.active = true; + } + + public void execute(Command command) { + if (this.active) { + command.execute(); + } + } + + public void stop() { + if ( ! this.active) { + throw new IllegalStateException("Not started."); //$NON-NLS-1$ + } + this.active = false; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleFilter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleFilter.java new file mode 100644 index 0000000000..7cff1214c6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleFilter.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import org.eclipse.jpt.common.utility.Filter; + +/** + * Simple, abstract implementation of Filter + * that holds on to a criterion object that can be used in the + * accept(Object) or reject(Object) + * methods. Subclasses can override either of these methods, + * depending on which is easier to implement. Note that at least + * one of these methods must be overridden or + * an infinite loop will occur. If both of them are overridden, + * only the accept(Object) method will be used. + *

+ * Simplifies the implementation of straightforward inner classes. + * Here is an example of a filter that can be used by a + * FilteringIterator to return only those strings + * in the nested iterator start with "prefix": + *

+ *	Filter filter = new SimpleFilter("prefix") {
+ *		public boolean accept(String o) {
+ *			return o.startsWith((String) criterion);
+ *		}
+ *	};
+ * 
+ */ +public abstract class SimpleFilter + implements Filter, Cloneable, Serializable +{ + protected final S criterion; + + private static final long serialVersionUID = 1L; + + + /** + * More useful constructor. The specified criterion can + * be used by a subclass to "accept" or "reject" objects. + */ + protected SimpleFilter(S criterion) { + super(); + this.criterion = criterion; + } + + /** + * Construct a simple filter with a null criterion + */ + protected SimpleFilter() { + this(null); + } + + /** + * Return whether the the specified object should be "rejected". + * The semantics of "rejected" is determined by the client. + */ + protected boolean reject(T o) { + return ! this.accept(o); + } + + /** + * Return whether the the specified object should be "accepted". + * The semantics of "accepted" is determined by the client. + */ + public boolean accept(T o) { + return ! this.reject(o); + } + + @Override + @SuppressWarnings("unchecked") + public SimpleFilter clone() { + try { + return (SimpleFilter) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public boolean equals(Object o) { + if ( ! (o instanceof SimpleFilter)) { + return false; + } + SimpleFilter other = (SimpleFilter) o; + return (this.criterion == null) ? + (other.criterion == null) : this.criterion.equals(other.criterion); + } + + @Override + public int hashCode() { + return (this.criterion == null) ? 0 : this.criterion.hashCode(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.criterion); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleIntReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleIntReference.java new file mode 100644 index 0000000000..fa7a3b452d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleIntReference.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * This class can be used wherever a mutable integer object is needed. + * It is a cross between an int and an {@link Integer}. + * It can be stored in a standard container (e.g. {@link java.util.Collection}) + * but can be modified. It is also useful passing a value that can be changed + * by the recipient. + * + * @see SynchronizedInt + */ +public final class SimpleIntReference + implements IntReference, Cloneable, Serializable +{ + /** Backing int. */ + private volatile int value = 0; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a int reference with the specified initial value. + */ + public SimpleIntReference(int count) { + super(); + this.value = count; + } + + /** + * Construct a int reference with an initial value of zero. + */ + public SimpleIntReference() { + this(0); + } + + + // ********** methods ********** + + public int getValue() { + return this.value; + } + + public boolean equals(int v) { + return this.value == v; + } + + public boolean notEqual(int v) { + return this.value != v; + } + + public boolean isZero() { + return this.value == 0; + } + + public boolean isNotZero() { + return this.value != 0; + } + + public boolean isGreaterThan(int v) { + return this.value > v; + } + + public boolean isGreaterThanOrEqual(int v) { + return this.value >= v; + } + + public boolean isLessThan(int v) { + return this.value < v; + } + + public boolean isLessThanOrEqual(int v) { + return this.value <= v; + } + + public boolean isPositive() { + return this.isGreaterThan(0); + } + + public boolean isNotPositive() { + return this.isLessThanOrEqual(0); + } + + public boolean isNegative() { + return this.isLessThan(0); + } + + public boolean isNotNegative() { + return this.isGreaterThanOrEqual(0); + } + + public int abs() { + return Math.abs(this.value); + } + + public int neg() { + return -this.value; + } + + public int add(int v) { + return this.value + v; + } + + public int subtract(int v) { + return this.value - v; + } + + public int multiply(int v) { + return this.value * v; + } + + public int divide(int v) { + return this.value / v; + } + + public int remainder(int v) { + return this.value % v; + } + + public int min(int v) { + return Math.min(this.value, v); + } + + public int max(int v) { + return Math.max(this.value, v); + } + + public double pow(int v) { + return Math.pow(this.value, v); + } + + public int setValue(int value) { + int old = this.value; + this.value = value; + return old; + } + + public int setZero() { + return this.setValue(0); + } + + public int increment() { + return ++this.value; + } + + public int decrement() { + return --this.value; + } + + + // ********** Comparable implementation ********** + + public int compareTo(ReadOnlyIntReference ref) { + int v = ref.getValue(); + return (this.value < v) ? -1 : ((this.value == v) ? 0 : 1); + } + + + // ********** standard methods ********** + + @Override + public SimpleIntReference clone() { + try { + return (SimpleIntReference) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.value) + ']'; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleJavaType.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleJavaType.java new file mode 100644 index 0000000000..ef47ba3097 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleJavaType.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.PrintWriter; +import java.io.Serializable; + +import org.eclipse.jpt.common.utility.JavaType; + +/** + * Straightforward implementation of the {@link JavaType} interface. + */ +public final class SimpleJavaType + implements JavaType, Cloneable, Serializable +{ + + /** + * store the type as a name, so we can reference classes + * that are not loaded + */ + private final String elementTypeName; + + /** + * non-array types have an array depth of zero + */ + private final int arrayDepth; + + private static final String BRACKETS = "[]"; //$NON-NLS-1$ + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a Java type with the specified element type and array depth. + */ + public SimpleJavaType(String elementTypeName, int arrayDepth) { + super(); + if ((elementTypeName == null) || (elementTypeName.length() == 0)) { + throw new IllegalArgumentException("The element type name is required."); //$NON-NLS-1$ + } + if (ClassName.getArrayDepth(elementTypeName) != 0) { // e.g. "[Ljava.lang.Object;" + throw new IllegalArgumentException("The element type must not be an array: " + elementTypeName + '.'); //$NON-NLS-1$ + } + if (arrayDepth < 0) { + throw new IllegalArgumentException("The array depth must be greater than or equal to zero: " + arrayDepth + '.'); //$NON-NLS-1$ + } + if (elementTypeName.equals(void.class.getName()) && (arrayDepth != 0)) { + throw new IllegalArgumentException("'void' must have an array depth of zero: " + arrayDepth + '.'); //$NON-NLS-1$ + } + this.elementTypeName = elementTypeName; + this.arrayDepth = arrayDepth; + } + + /** + * Construct a Java type for the specified class. + * The class name can be in one of the following forms:
    + *
  • java.lang.Object + *
  • int + *
  • java.util.Map$Entry + *
  • [Ljava.lang.Object; + *
  • [I + *
  • [Ljava.util.Map$Entry; + *
+ */ + public SimpleJavaType(String javaClassName) { + this(ClassName.getElementTypeName(javaClassName), ClassName.getArrayDepth(javaClassName)); + } + + /** + * Construct a Java type for the specified class. + */ + public SimpleJavaType(Class javaClass) { + this(javaClass.getName()); + } + + + // ********** accessors ********** + + public String getElementTypeName() { + return this.elementTypeName; + } + + public int getArrayDepth() { + return this.arrayDepth; + } + + + // ********** queries ********** + + public boolean isArray() { + return this.arrayDepth > 0; + } + + public boolean isPrimitive() { + return (this.arrayDepth == 0) && ClassName.isPrimitive(this.elementTypeName); + } + + public boolean isPrimitiveWrapper() { + return (this.arrayDepth == 0) && ClassName.isPrimitiveWrapper(this.elementTypeName); + } + + public boolean isVariablePrimitive() { + return (this.arrayDepth == 0) && ClassName.isVariablePrimitive(this.elementTypeName); + } + + public boolean isVariablePrimitiveWrapper() { + return (this.arrayDepth == 0) && ClassName.isVariablePrimitiveWrapper(this.elementTypeName); + } + + public Class getJavaClass() throws ClassNotFoundException { + return ReflectionTools.getClassForTypeDeclaration(this.elementTypeName, this.arrayDepth); + } + + public String getJavaClassName() { + return ReflectionTools.getClassNameForTypeDeclaration(this.elementTypeName, this.arrayDepth); + } + + + // ********** comparison ********** + + public boolean equals(String otherElementTypeName, int otherArrayDepth) { + return (this.arrayDepth == otherArrayDepth) + && this.elementTypeName.equals(otherElementTypeName); + } + + public boolean describes(String className) { + return this.equals(ClassName.getElementTypeName(className), ClassName.getArrayDepth(className)); + } + + public boolean describes(Class javaClass) { + return this.describes(javaClass.getName()); + } + + public boolean equals(JavaType other) { + return this.equals(other.getElementTypeName(), other.getArrayDepth()); + } + + @Override + public boolean equals(Object o) { + return (this == o) ? true : (o instanceof JavaType) ? this.equals((JavaType) o) : false; + } + + @Override + public int hashCode() { + return this.elementTypeName.hashCode() ^ this.arrayDepth; + } + + + // ********** printing and displaying ********** + + public String declaration() { + if (this.arrayDepth == 0) { + return this.getElementTypeNameDeclaration(); + } + StringBuilder sb = new StringBuilder(this.elementTypeName.length() + (2 * this.arrayDepth)); + this.appendDeclarationTo(sb); + return sb.toString(); + } + + public void appendDeclarationTo(StringBuilder sb) { + sb.append(this.getElementTypeNameDeclaration()); + for (int i = this.arrayDepth; i-- > 0; ) { + sb.append(BRACKETS); + } + } + + public void printDeclarationOn(PrintWriter pw) { + pw.print(this.getElementTypeNameDeclaration()); + for (int i = this.arrayDepth; i-- > 0; ) { + pw.print(BRACKETS); + } + } + + /** + * The '$' version of the name is used in {@link Class#forName(String)}, + * but the '.' version of the name is used in source code. + * Very irritating.... + */ + private String getElementTypeNameDeclaration() { + return this.elementTypeName.replace('$', '.'); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()); + sb.append('('); + this.appendDeclarationTo(sb); + sb.append(')'); + return sb.toString(); + } + + + // ********** cloning ********** + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleMethodSignature.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleMethodSignature.java new file mode 100644 index 0000000000..0a4b523c3e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleMethodSignature.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.JavaType; +import org.eclipse.jpt.common.utility.MethodSignature; + +/** + * Straightforward implementation of the MethodSignature interface. + */ +public final class SimpleMethodSignature + implements MethodSignature, Cloneable, Serializable +{ + private final String name; + + /** + * store the parameter types as names, so we can reference classes + * that are not loaded + */ + private final JavaType[] parameterTypes; + + public static final JavaType[] EMPTY_PARAMETER_TYPES = new JavaType[0]; + + private static final String PARAMETER_SEPARATOR = ", "; //$NON-NLS-1$ + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a method signature with the specified name and + * no parameter types. + */ + public SimpleMethodSignature(String name) { + this(name, EMPTY_PARAMETER_TYPES); + } + + /** + * Construct a method signature with the specified name and parameter + * types. + */ + public SimpleMethodSignature(String name, JavaType... parameterTypes) { + super(); + if ((name == null) || (name.length() == 0)) { + throw new IllegalArgumentException("The name is required."); //$NON-NLS-1$ + } + if (parameterTypes == null) { + throw new IllegalArgumentException("The parameter types are required."); //$NON-NLS-1$ + } + checkParameterTypes(parameterTypes); + this.name = name; + this.parameterTypes = parameterTypes; + } + + private static void checkParameterTypes(JavaType[] parameterTypes) { + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i] == null) { + throw new IllegalArgumentException("Missing parameter type: " + i); //$NON-NLS-1$ + } + if (parameterTypes[i].getElementTypeName().equals(void.class.getName())) { + throw new IllegalArgumentException("A parameter type of 'void' is not allowed: " + i); //$NON-NLS-1$ + } + } + } + + /** + * Construct a method signature with the specified name and parameter + * types. + */ + public SimpleMethodSignature(String name, String... parameterTypeNames) { + this(name, buildParameterTypes(parameterTypeNames)); + } + + private static JavaType[] buildParameterTypes(String[] parameterTypeNames) { + if (parameterTypeNames == null) { + throw new IllegalArgumentException("The parameter type names are required."); //$NON-NLS-1$ + } + JavaType[] parameterTypes = new JavaType[parameterTypeNames.length]; + for (int i = 0; i < parameterTypeNames.length; i++) { + if (parameterTypeNames[i] == null) { + throw new IllegalArgumentException("Missing parameter type name: " + i); //$NON-NLS-1$ + } + parameterTypes[i] = new SimpleJavaType(parameterTypeNames[i]); + } + return parameterTypes; + } + + /** + * Construct a method signature with the specified name and parameter + * types. + */ + public SimpleMethodSignature(String name, Class... parameterJavaClasses) { + this(name, buildParameterTypeNames(parameterJavaClasses)); + } + + private static String[] buildParameterTypeNames(Class[] parameterJavaClasses) { + if (parameterJavaClasses == null) { + throw new IllegalArgumentException("The parameter Java classes are required."); //$NON-NLS-1$ + } + String[] parameterTypeNames = new String[parameterJavaClasses.length]; + for (int i = 0; i < parameterJavaClasses.length; i++) { + if (parameterJavaClasses[i] == null) { + throw new IllegalArgumentException("Missing parameter Java class: " + i); //$NON-NLS-1$ + } + parameterTypeNames[i] = parameterJavaClasses[i].getName(); + } + return parameterTypeNames; + } + + /** + * Construct a method signature for the specified Java method. + */ + public SimpleMethodSignature(Method method) { + this(method.getName(), method.getParameterTypes()); + } + + + // ********** accessors ********** + + public String getName() { + return this.name; + } + + public JavaType[] getParameterTypes() { + return this.parameterTypes; + } + + + // ********** comparison ********** + + public boolean describes(Method method) { + return this.name.equals(method.getName()) + && this.parameterTypesDescribe(method.getParameterTypes()); + } + + private boolean parameterTypesDescribe(Class[] otherParameterTypes) { + JavaType[] localParameterTypes = this.parameterTypes; + int len = localParameterTypes.length; + if (otherParameterTypes.length != len) { + return false; + } + for (int i = len; i-- > 0; ) { + if ( ! localParameterTypes[i].describes(otherParameterTypes[i])) { + return false; + } + } + return true; + } + + public boolean equals(String otherName, JavaType[] otherParameterTypes) { + return this.name.equals(otherName) + && Arrays.equals(this.parameterTypes, otherParameterTypes); + } + + public boolean equals(MethodSignature other) { + return this.equals(other.getName(), other.getParameterTypes()); + } + + @Override + public boolean equals(Object o) { + return (this == o) ? true : (o instanceof MethodSignature) ? this.equals((MethodSignature) o) : false; + } + + @Override + public int hashCode() { + return this.name.hashCode() ^ Arrays.hashCode(this.parameterTypes); + } + + + // ********** printing and displaying ********** + + public String getSignature() { + StringBuilder sb = new StringBuilder(200); + this.appendSignatureTo(sb); + return sb.toString(); + } + + public void appendSignatureTo(StringBuilder sb) { + sb.append(this.name); + sb.append('('); + JavaType[] localParameterTypes = this.parameterTypes; + int len = localParameterTypes.length; + for (int i = 0; i < len; i++) { + if (i != 0) { + sb.append(PARAMETER_SEPARATOR); + } + localParameterTypes[i].appendDeclarationTo(sb); + } + sb.append(')'); + } + + public void printSignatureOn(PrintWriter pw) { + pw.print(this.name); + pw.print('('); + JavaType[] localParameterTypes = this.parameterTypes; + int len = localParameterTypes.length; + for (int i = 0; i < len; i++) { + if (i != 0) { + pw.print(PARAMETER_SEPARATOR); + } + localParameterTypes[i].printDeclarationOn(pw); + } + pw.print(')'); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(200); + sb.append(this.getClass().getSimpleName()); + sb.append('('); + this.appendSignatureTo(sb); + sb.append(')'); + return sb.toString(); + } + + + // ********** cloning ********** + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleObjectReference.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleObjectReference.java new file mode 100644 index 0000000000..5134aa98a3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleObjectReference.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import org.eclipse.jpt.common.utility.ObjectReference; + +/** + * Provide a container for passing an object that can be changed by the recipient. + * + * @see SynchronizedObject + */ +public class SimpleObjectReference + implements ObjectReference, Cloneable, Serializable +{ + /** Backing value. */ + private volatile V value; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create an object reference with the specified initial value. + */ + public SimpleObjectReference(V value) { + super(); + this.value = value; + } + + /** + * Create an object reference with an initial value of + * null. + */ + public SimpleObjectReference() { + this(null); + } + + + // ********** value ********** + + public V getValue() { + return this.value; + } + + public boolean valueEquals(Object object) { + return Tools.valuesAreEqual(this.value, object); + } + + public boolean valueNotEqual(Object object) { + return Tools.valuesAreDifferent(this.value, object); + } + + public boolean isNull() { + return this.value == null; + } + + public boolean isNotNull() { + return this.value != null; + } + + public V setValue(V value) { + V old = this.value; + this.value = value; + return old; + } + + public V setNull() { + return this.setValue(null); + } + + + // ********** standard methods ********** + + @Override + public SimpleObjectReference clone() { + try { + @SuppressWarnings("unchecked") + SimpleObjectReference clone = (SimpleObjectReference) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.value) + ']'; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleQueue.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleQueue.java new file mode 100644 index 0000000000..02e026c7db --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleQueue.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; + +/** + * Straightforward implementation of the {@link Queue} interface. + */ +public class SimpleQueue + implements Queue, Cloneable, Serializable +{ + private LinkedList elements; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct an empty queue. + */ + public SimpleQueue() { + super(); + this.elements = new LinkedList(); + } + + /** + * Construct a queue containing the elements of the specified + * collection. The queue will dequeue its elements in the same + * order they are returned by the collection's iterator (i.e. the + * first element returned by the collection's iterator will be the + * first element returned by {@link #dequeue()}). + */ + public SimpleQueue(Collection c) { + super(); + this.elements = new LinkedList(c); + } + + + // ********** Queue implementation ********** + + public void enqueue(E o) { + this.elements.addLast(o); + } + + public E dequeue() { + return this.elements.removeFirst(); + } + + public E peek() { + return this.elements.getFirst(); + } + + public boolean isEmpty() { + return this.elements.isEmpty(); + } + + + // ********** Cloneable implementation ********** + + @Override + public SimpleQueue clone() { + try { + @SuppressWarnings("unchecked") + SimpleQueue clone = (SimpleQueue) super.clone(); + @SuppressWarnings("unchecked") + LinkedList ll = (LinkedList) this.elements.clone(); + clone.elements = ll; + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.peek()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStack.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStack.java new file mode 100644 index 0000000000..3816b49a87 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStack.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.Collection; +import java.util.EmptyStackException; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +/** + * Straightforward implementation of the {@link Stack} interface. + */ +public class SimpleStack + implements Stack, Cloneable, Serializable +{ + private LinkedList elements; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct an empty stack. + */ + public SimpleStack() { + super(); + this.elements = new LinkedList(); + } + + /** + * Construct a stack containing the elements of the specified + * collection. The stack will pop its elements in reverse of the + * order they are returned by the collection's iterator (i.e. the + * last element returned by the collection's iterator will be the + * first element returned by {@link #pop()}). + */ + public SimpleStack(Collection collection) { + super(); + this.elements = new LinkedList(collection); + } + + + // ********** Stack implementation ********** + + public void push(E element) { + this.elements.addLast(element); + } + + public E pop() { + try { + return this.elements.removeLast(); + } catch (NoSuchElementException ex) { + throw new EmptyStackException(); + } + } + + public E peek() { + try { + return this.elements.getLast(); + } catch (NoSuchElementException ex) { + throw new EmptyStackException(); + } + } + + public boolean isEmpty() { + return this.elements.isEmpty(); + } + + + // ********** standard methods ********** + + @Override + public SimpleStack clone() { + try { + @SuppressWarnings("unchecked") + SimpleStack clone = (SimpleStack) super.clone(); + @SuppressWarnings("unchecked") + LinkedList ll = (LinkedList) this.elements.clone(); + clone.elements = ll; + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.peek()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStringMatcher.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStringMatcher.java new file mode 100644 index 0000000000..5f85f7895c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStringMatcher.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.regex.Pattern; +import org.eclipse.jpt.common.utility.Filter; + +// TODO the regex code is not very fast - we could probably do better, +// hand-coding the matching algorithm (eclipse StringMatcher?) +/** + * This class implements a simple string-matching algorithm that is a little + * more user-friendly than standard regular expressions. Instantiate a + * string matcher with a filter pattern and then you can use the matcher + * to determine whether another string (or object) matches the pattern. + * You can also specify whether the matching should be case-sensitive. + * + * The pattern can contain two "meta-characters": + * '*' will match any set of zero or more characters + * '?' will match any single character + * + * Subclasses can override #prefix() and/or #suffix() to change what + * strings are prepended or appended to the original pattern string. + * This can offer a slight performance improvement over concatenating + * strings before calling #setPatternString(String). + * By default, a '*' is appended to every string. + * + * This class also uses the string-matching algorithm to "filter" objects + * (and, as a result, also implements the Filter interface). + * A string converter is used to determine what string aspect of the + * object is compared to the pattern. By default the string returned + * by the object's #toString() method is passed to the pattern matcher. + */ +public class SimpleStringMatcher + implements StringMatcher, Filter, Serializable +{ + + /** An adapter that converts the objects into strings to be matched with the pattern. */ + private StringConverter stringConverter; + + /** The string used to construct the regular expression pattern. */ + private String patternString; + + /** Whether the matcher ignores case - the default is true. */ + private boolean ignoresCase; + + /** The regular expression pattern built from the pattern string. */ + private Pattern pattern; + + /** A list of the meta-characters we need to escape if found in the pattern string. */ + public static final char[] REG_EX_META_CHARS = { '(', '[', '{', '\\', '^', '$', '|', ')', '?', '*', '+', '.' }; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a string matcher with an pattern that will match + * any string and ignore case. + */ + public SimpleStringMatcher() { + this("*"); //$NON-NLS-1$ + } + + /** + * Construct a string matcher with the specified pattern + * that will ignore case. + */ + public SimpleStringMatcher(String patternString) { + this(patternString, true); + } + + /** + * Construct a string matcher with the specified pattern that will + * ignore case as specified. + */ + public SimpleStringMatcher(String patternString, boolean ignoresCase) { + super(); + this.patternString = patternString; + this.ignoresCase = ignoresCase; + this.initialize(); + } + + + // ********** initialization ********** + + protected void initialize() { + this.stringConverter = StringConverter.Default.instance(); + this.rebuildPattern(); + } + + /** + * Given the current pattern string and case-sensitivity setting, + * re-build the regular expression pattern. + */ + protected synchronized void rebuildPattern() { + this.pattern = this.buildPattern(); + } + + /** + * Given the current pattern string and case-sensitivity setting, + * build and return a regular expression pattern that can be used + * to match strings. + */ + protected Pattern buildPattern() { + int patternFlags = 0x0; + if (this.ignoresCase) { + patternFlags = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE; + } + return Pattern.compile(this.convertToRegEx(this.patternString), patternFlags); + } + + + // ********** StringMatcher implementation ********** + + public synchronized void setPatternString(String patternString) { + this.patternString = patternString; + this.rebuildPattern(); + } + + /** + * Return whether the specified string matches the pattern. + */ + public synchronized boolean matches(String string) { + return this.pattern.matcher(string).matches(); + } + + + // ********** Filter implementation ********** + + public synchronized boolean accept(T o) { + return this.matches(this.stringConverter.convertToString(o)); + } + + + // ********** accessors ********** + + /** + * Return the string converter used to convert the objects + * passed to the matcher into strings. + */ + public synchronized StringConverter stringConverter() { + return this.stringConverter; + } + + /** + * Set the string converter used to convert the objects + * passed to the matcher into strings. + */ + public synchronized void setStringConverter(StringConverter stringConverter) { + this.stringConverter = stringConverter; + } + + /** + * Return the original pattern string. + */ + public synchronized String patternString() { + return this.patternString; + } + + /** + * Return whether the matcher ignores case. + */ + public synchronized boolean ignoresCase() { + return this.ignoresCase; + } + + /** + * Set whether the matcher ignores case. + */ + public synchronized void setIgnoresCase(boolean ignoresCase) { + this.ignoresCase = ignoresCase; + this.rebuildPattern(); + } + + /** + * Return the regular expression pattern. + */ + public synchronized Pattern pattern() { + return this.pattern; + } + + + // ********** other public API ********** + + /** + * Return the regular expression corresponding to + * the original pattern string. + */ + public synchronized String regularExpression() { + return this.convertToRegEx(this.patternString); + } + + + // ********** converting ********** + + /** + * Convert the specified string to a regular expression. + */ + protected String convertToRegEx(String string) { + StringBuffer sb = new StringBuffer(string.length() + 10); + this.convertToRegExOn(this.prefix(), sb); + this.convertToRegExOn(string, sb); + this.convertToRegExOn(this.suffix(), sb); + return sb.toString(); + } + + /** + * Return any prefix that should be prepended to the original + * string. By default, there is no prefix. + */ + protected String prefix() { + return ""; //$NON-NLS-1$ + } + + /** + * Return any suffix that should be appended to the original + * string. Since this class is typically used in UI situation where + * the user is typing in a pattern used to filter a list, the default + * suffix is a wildcard character. + */ + protected String suffix() { + return "*"; //$NON-NLS-1$ + } + + /** + * Convert the specified string to a regular expression. + */ + protected void convertToRegExOn(String string, StringBuffer sb) { + char[] charArray = string.toCharArray(); + int length = charArray.length; + for (int i = 0; i < length; i++) { + char c = charArray[i]; + // convert user-friendly meta-chars into regex meta-chars + if (c == '*') { + sb.append(".*"); //$NON-NLS-1$ + continue; + } + if (c == '?') { + sb.append('.'); + continue; + } + // escape regex meta-chars + if (ArrayTools.contains(REG_EX_META_CHARS, c)) { + sb.append('\\'); + } + sb.append(c); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleThreadFactory.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleThreadFactory.java new file mode 100644 index 0000000000..4c2bbb037d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleThreadFactory.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.util.concurrent.ThreadFactory; + +/** + * A SimpleThreadFactory is a straightforward implementation of + * the JDK {@link ThreadFactory}. + */ +public class SimpleThreadFactory + implements ThreadFactory +{ + // singleton + private static final SimpleThreadFactory INSTANCE = new SimpleThreadFactory(); + + /** + * Return the singleton. + */ + public static ThreadFactory instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private SimpleThreadFactory() { + super(); + } + + public Thread newThread(Runnable r) { + return new Thread(r); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Stack.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Stack.java new file mode 100644 index 0000000000..88186bffe3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Stack.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.EmptyStackException; + +/** + * Interface defining the classic stack behavior, + * without the backdoors allowed by {@link java.util.Stack}. + * + * @param the type of elements contained by the stack + */ +public interface Stack { + + /** + * "Push" the specified item on to the top of the stack. + */ + void push(E element); + + /** + * "Pop" an item from the top of the stack. + */ + E pop(); + + /** + * Return the item on the top of the stack + * without removing it from the stack. + */ + E peek(); + + /** + * Return whether the stack is empty. + */ + boolean isEmpty(); + + + final class Empty implements Stack, Serializable { + @SuppressWarnings("rawtypes") + public static final Stack INSTANCE = new Empty(); + @SuppressWarnings("unchecked") + public static Stack instance() { + return INSTANCE; + } + // ensure single instance + private Empty() { + super(); + } + public void push(E element) { + throw new UnsupportedOperationException(); + } + public E pop() { + throw new EmptyStackException(); + } + public E peek() { + throw new EmptyStackException(); + } + public boolean isEmpty() { + return true; + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StatefulCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StatefulCommandExecutor.java new file mode 100644 index 0000000000..97e99de81c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StatefulCommandExecutor.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.CommandExecutor; + +/** + * This interface allows clients to control how a command is executed. + * This is useful when the server provides the command but the client provides + * the context (e.g. the client would like to dispatch the command to the UI + * thread). + */ +public interface StatefulCommandExecutor + extends CommandExecutor +{ + /** + * Start the command executor. + */ + void start(); + + /** + * Stop the command executor. + */ + void stop(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringConverter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringConverter.java new file mode 100644 index 0000000000..412c9c199f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringConverter.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Used by various "pluggable" classes to transform objects + * into strings. + */ +public interface StringConverter { + + /** + * Convert the specified object into a string. + * The semantics of "convert" is determined by the + * contract between the client and the server. + */ + String convertToString(T o); + + + final class Default implements StringConverter, Serializable { + @SuppressWarnings("rawtypes") + public static final StringConverter INSTANCE = new Default(); + @SuppressWarnings("unchecked") + public static StringConverter instance() { + return INSTANCE; + } + // ensure single instance + private Default() { + super(); + } + // simply return the object's #toString() result + public String convertToString(S o) { + return (o == null) ? null : o.toString(); + } + @Override + public String toString() { + return "StringConverter.Default"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Disabled implements StringConverter, Serializable { + @SuppressWarnings("rawtypes") + public static final StringConverter INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static StringConverter instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public String convertToString(S o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "StringConverter.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringMatcher.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringMatcher.java new file mode 100644 index 0000000000..c66900e3cb --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringMatcher.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * This interface defines a simple API for allowing "pluggable" + * string matchers that can be configured with a pattern string + * then used to determine what strings match the pattern. + */ +public interface StringMatcher { + + /** + * Set the pattern string used to determine future + * matches. The format and semantics of the pattern + * string are determined by the contract between the + * client and the server. + */ + void setPatternString(String patternString); + + /** + * Return whether the specified string matches the + * established pattern string. The semantics of a match + * is determined by the contract between the + * client and the server. + */ + boolean matches(String string); + + + final class Null implements StringMatcher, Serializable { + public static final StringMatcher INSTANCE = new Null(); + public static StringMatcher instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void setPatternString(String patternString) { + // ignore the pattern string + } + public boolean matches(String string) { + // everything is a match + return true; + } + @Override + public String toString() { + return "StringMatcher.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringTools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringTools.java new file mode 100644 index 0000000000..0eab3b6e26 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringTools.java @@ -0,0 +1,4708 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; + +/** + * Convenience methods related to the java.lang.String class. + * + * As of jdk 1.5, it's tempting to convert all of these methods to use + * java.lang.Appendable (instead of StringBuffer, StringBuilder, and Writer); + * but all the Appendable methods throw java.io.IOException (yech) and we + * [might?] get a bit better performance invoking methods on classes than + * we get on interfaces. :-) + */ +public final class StringTools { + + /** carriage return */ + public static final String CR = System.getProperty("line.separator"); //$NON-NLS-1$ + + /** double quote */ + public static final char QUOTE = '"'; + + /** parenthesis */ + public static final char OPEN_PARENTHESIS = '('; + public static final char CLOSE_PARENTHESIS = ')'; + + /** brackets */ + public static final char OPEN_BRACKET = '['; + public static final char CLOSE_BRACKET = ']'; + + /** brackets */ + public static final char OPEN_BRACE = '{'; + public static final char CLOSE_BRACE = '}'; + + /** brackets */ + public static final char OPEN_CHEVRON = '<'; + public static final char CLOSE_CHEVRON = '>'; + + /** empty string */ + public static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + /** empty char array */ + public static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + /** empty string array */ + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + + + // ********** padding/truncating ********** + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#pad(int) + */ + public static String pad(String string, int length) { + return pad(string, length, ' '); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, Writer) + */ + public static void padOn(String string, int length, Writer writer) { + padOn(string, length, ' ', writer); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, StringBuffer) + */ + public static void padOn(String string, int length, StringBuffer sb) { + padOn(string, length, ' ', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, StringBuilder) + */ + public static void padOn(String string, int length, StringBuilder sb) { + padOn(string, length, ' ', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#pad(int, char) + */ + public static String pad(String string, int length, char c) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + return string; + } + return pad_(string, length, c); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, Writer) + */ + public static void padOn(String string, int length, char c, Writer writer) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + writeStringOn(string, writer); + } else { + padOn_(string, length, c, writer); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, StringBuffer) + */ + public static void padOn(String string, int length, char c, StringBuffer sb) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, StringBuilder) + */ + public static void padOn(String string, int length, char c, StringBuilder sb) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#pad(int) + */ + public static char[] pad(char[] string, int length) { + return pad(string, length, ' '); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, writer) + */ + public static void padOn(char[] string, int length, Writer writer) { + padOn(string, length, ' ', writer); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, StringBuffer) + */ + public static void padOn(char[] string, int length, StringBuffer sb) { + padOn(string, length, ' ', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOn(int, StringBuilder) + */ + public static void padOn(char[] string, int length, StringBuilder sb) { + padOn(string, length, ' ', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#pad(int, char) + */ + public static char[] pad(char[] string, int length, char c) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + return string; + } + return pad_(string, length, c); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, Writer) + */ + public static void padOn(char[] string, int length, char c, Writer writer) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + writeStringOn(string, writer); + } else { + padOn_(string, length, c, writer); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, StringBuffer) + */ + public static void padOn(char[] string, int length, char c, StringBuffer sb) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOn(int, char, StringBuilder) + */ + public static void padOn(char[] string, int length, char c, StringBuilder sb) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncate(int) + */ + public static String padOrTruncate(String string, int length) { + return padOrTruncate(string, length, ' '); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, Writer) + */ + public static void padOrTruncateOn(String string, int length, Writer writer) { + padOrTruncateOn(string, length, ' ', writer); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, StringBuffer) + */ + public static void padOrTruncateOn(String string, int length, StringBuffer sb) { + padOrTruncateOn(string, length, ' ', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, StringBuilder) + */ + public static void padOrTruncateOn(String string, int length, StringBuilder sb) { + padOrTruncateOn(string, length, ' ', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncate(int, char) + */ + public static String padOrTruncate(String string, int length, char c) { + int stringLength = string.length(); + if (stringLength == length) { + return string; + } + if (stringLength > length) { + return string.substring(0, length); + } + return pad_(string, length, c); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, Writer) + */ + public static void padOrTruncateOn(String string, int length, char c, Writer writer) { + int stringLength = string.length(); + if (stringLength == length) { + writeStringOn(string, writer); + } else if (stringLength > length) { + writeStringOn(string.substring(0, length), writer); + } else { + padOn_(string, length, c, writer); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, StringBuffer) + */ + public static void padOrTruncateOn(String string, int length, char c, StringBuffer sb) { + int stringLength = string.length(); + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string.substring(0, length)); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, StringBuilder) + */ + public static void padOrTruncateOn(String string, int length, char c, StringBuilder sb) { + int stringLength = string.length(); + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string.substring(0, length)); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncate(int) + */ + public static char[] padOrTruncate(char[] string, int length) { + return padOrTruncate(string, length, ' '); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, Writer) + */ + public static void padOrTruncateOn(char[] string, int length, Writer writer) { + padOrTruncateOn(string, length, ' ', writer); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, StringBuffer) + */ + public static void padOrTruncate(char[] string, int length, StringBuffer sb) { + padOrTruncateOn(string, length, ' ', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with spaces at the end. + * String#padOrTruncateOn(int, StringBuilder) + */ + public static void padOrTruncate(char[] string, int length, StringBuilder sb) { + padOrTruncateOn(string, length, ' ', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncate(int, char) + */ + public static char[] padOrTruncate(char[] string, int length, char c) { + int stringLength = string.length; + if (stringLength == length) { + return string; + } + if (stringLength > length) { + char[] result = new char[length]; + System.arraycopy(string, 0, result, 0, length); + return result; + } + return pad_(string, length, c); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, Writer) + */ + public static void padOrTruncateOn(char[] string, int length, char c, Writer writer) { + int stringLength = string.length; + if (stringLength == length) { + writeStringOn(string, writer); + } else if (stringLength > length) { + writeStringOn(string, 0, length, writer); + } else { + padOn_(string, length, c, writer); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, StringBuffer) + */ + public static void padOrTruncateOn(char[] string, int length, char c, StringBuffer sb) { + int stringLength = string.length; + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string, 0, length); + } else { + padOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, it is truncated. + * If it is shorter than the specified length, it is padded with the + * specified character at the end. + * String#padOrTruncateOn(int, char, StringBuilder) + */ + public static void padOrTruncateOn(char[] string, int length, char c, StringBuilder sb) { + int stringLength = string.length; + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string, 0, length); + } else { + padOn_(string, length, c, sb); + } + } + + /* + * Pad the specified string without validating the parms. + */ + private static String pad_(String string, int length, char c) { + return new String(pad_(string.toCharArray(), length, c)); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(String string, int length, char c, Writer writer) { + writeStringOn(string, writer); + fill_(string, length, c, writer); + } + + /* + * Add enough characters to the specified writer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(String string, int length, char c, Writer writer) { + fill_(string.length(), length, c, writer); + } + + /* + * Add enough characters to the specified writer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(char[] string, int length, char c, Writer writer) { + fill_(string.length, length, c, writer); + } + + /* + * Add enough characters to the specified writer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(int stringLength, int length, char c, Writer writer) { + writeStringOn(ArrayTools.fill(new char[length - stringLength], c), writer); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(String string, int length, char c, StringBuffer sb) { + sb.append(string); + fill_(string, length, c, sb); + } + + /* + * Add enough characters to the specified string buffer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(String string, int length, char c, StringBuffer sb) { + fill_(string.length(), length, c, sb); + } + + /* + * Add enough characters to the specified string buffer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(char[] string, int length, char c, StringBuffer sb) { + fill_(string.length, length, c, sb); + } + + /* + * Add enough characters to the specified string buffer to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(int stringLength, int length, char c, StringBuffer sb) { + sb.append(ArrayTools.fill(new char[length - stringLength], c)); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(String string, int length, char c, StringBuilder sb) { + sb.append(string); + fill_(string, length, c, sb); + } + + /* + * Add enough characters to the specified string builder to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(String string, int length, char c, StringBuilder sb) { + fill_(string.length(), length, c, sb); + } + + /* + * Add enough characters to the specified string builder to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(char[] string, int length, char c, StringBuilder sb) { + fill_(string.length, length, c, sb); + } + + /* + * Add enough characters to the specified string builder to compensate for + * the difference between the specified string and specified length. + */ + private static void fill_(int stringLength, int length, char c, StringBuilder sb) { + sb.append(ArrayTools.fill(new char[length - stringLength], c)); + } + + /* + * Pad the specified string without validating the parms. + */ + private static char[] pad_(char[] string, int length, char c) { + char[] result = new char[length]; + int stringLength = string.length; + System.arraycopy(string, 0, result, 0, stringLength); + Arrays.fill(result, stringLength, length, c); + return result; + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(char[] string, int length, char c, Writer writer) { + writeStringOn(string, writer); + fill_(string, length, c, writer); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(char[] string, int length, char c, StringBuffer sb) { + sb.append(string); + fill_(string, length, c, sb); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void padOn_(char[] string, int length, char c, StringBuilder sb) { + sb.append(string); + fill_(string, length, c, sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPad(int) + */ + public static String zeroPad(String string, int length) { + return frontPad(string, length, '0'); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, Writer) + */ + public static void zeroPadOn(String string, int length, Writer writer) { + frontPadOn(string, length, '0', writer); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, StringBuffer) + */ + public static void zeroPadOn(String string, int length, StringBuffer sb) { + frontPadOn(string, length, '0', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, StringBuilder) + */ + public static void zeroPadOn(String string, int length, StringBuilder sb) { + frontPadOn(string, length, '0', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPad(int, char) + */ + public static String frontPad(String string, int length, char c) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + return string; + } + return frontPad_(string, length, c); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, Writer) + */ + public static void frontPadOn(String string, int length, char c, Writer writer) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + writeStringOn(string, writer); + } else { + frontPadOn_(string, length, c, writer); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, StringBuffer) + */ + public static void frontPadOn(String string, int length, char c, StringBuffer sb) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, StringBuilder) + */ + public static void frontPadOn(String string, int length, char c, StringBuilder sb) { + int stringLength = string.length(); + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPad(int) + */ + public static char[] zeroPad(char[] string, int length) { + return frontPad(string, length, '0'); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, Writer) + */ + public static void zeroPadOn(char[] string, int length, Writer writer) { + frontPadOn(string, length, '0', writer); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, StringBuffer) + */ + public static void zeroPadOn(char[] string, int length, StringBuffer sb) { + frontPadOn(string, length, '0', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOn(int, StringBuilder) + */ + public static void zeroPadOn(char[] string, int length, StringBuilder sb) { + frontPadOn(string, length, '0', sb); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPad(int, char) + */ + public static char[] frontPad(char[] string, int length, char c) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + return string; + } + return frontPad_(string, length, c); + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, Writer) + */ + public static void frontPadOn(char[] string, int length, char c, Writer writer) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + writeStringOn(string, writer); + } else { + frontPadOn_(string, length, c, writer); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, StringBuffer) + */ + public static void frontPadOn(char[] string, int length, char c, StringBuffer sb) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, an IllegalArgumentException is thrown. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOn(int, char, StringBuilder) + */ + public static void frontPadOn(char[] string, int length, char c, StringBuilder sb) { + int stringLength = string.length; + if (stringLength > length) { + throw new IllegalArgumentException("String is too long: " + stringLength + " > " + length); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (stringLength == length) { + sb.append(string); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncate(int) + */ + public static String zeroPadOrTruncate(String string, int length) { + return frontPadOrTruncate(string, length, '0'); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, Writer) + */ + public static void zeroPadOrTruncateOn(String string, int length, Writer writer) { + frontPadOrTruncateOn(string, length, '0', writer); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, StringBuffer) + */ + public static void zeroPadOrTruncateOn(String string, int length, StringBuffer sb) { + frontPadOrTruncateOn(string, length, '0', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, StringBuilder) + */ + public static void zeroPadOrTruncateOn(String string, int length, StringBuilder sb) { + frontPadOrTruncateOn(string, length, '0', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncate(int, char) + */ + public static String frontPadOrTruncate(String string, int length, char c) { + int stringLength = string.length(); + if (stringLength == length) { + return string; + } + if (stringLength > length) { + return string.substring(stringLength - length); + } + return frontPad_(string, length, c); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, Writer) + */ + public static void frontPadOrTruncateOn(String string, int length, char c, Writer writer) { + int stringLength = string.length(); + if (stringLength == length) { + writeStringOn(string, writer); + } else if (stringLength > length) { + writeStringOn(string.substring(stringLength - length), writer); + } else { + frontPadOn_(string, length, c, writer); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, StringBuffer) + */ + public static void frontPadOrTruncateOn(String string, int length, char c, StringBuffer sb) { + int stringLength = string.length(); + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string.substring(stringLength - length)); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, StringBuilder) + */ + public static void frontPadOrTruncateOn(String string, int length, char c, StringBuilder sb) { + int stringLength = string.length(); + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string.substring(stringLength - length)); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncate(int) + */ + public static char[] zeroPadOrTruncate(char[] string, int length) { + return frontPadOrTruncate(string, length, '0'); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, Writer) + */ + public static void zeroPadOrTruncateOn(char[] string, int length, Writer writer) { + frontPadOrTruncateOn(string, length, '0', writer); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, StringBuffer) + */ + public static void zeroPadOrTruncateOn(char[] string, int length, StringBuffer sb) { + frontPadOrTruncateOn(string, length, '0', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with zeros at the front. + * String#zeroPadOrTruncateOn(int, StringBuilder) + */ + public static void zeroPadOrTruncateOn(char[] string, int length, StringBuilder sb) { + frontPadOrTruncateOn(string, length, '0', sb); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncate(int, char) + */ + public static char[] frontPadOrTruncate(char[] string, int length, char c) { + int stringLength = string.length; + if (stringLength == length) { + return string; + } + if (stringLength > length) { + char[] result = new char[length]; + System.arraycopy(string, stringLength - length, result, 0, length); + return result; + } + return frontPad_(string, length, c); + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, Writer) + */ + public static void frontPadOrTruncateOn(char[] string, int length, char c, Writer writer) { + int stringLength = string.length; + if (stringLength == length) { + writeStringOn(string, writer); + } else if (stringLength > length) { + writeStringOn(string, stringLength - length, length, writer); + } else { + frontPadOn_(string, length, c, writer); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, StringBuffer) + */ + public static void frontPadOrTruncateOn(char[] string, int length, char c, StringBuffer sb) { + int stringLength = string.length; + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string, stringLength - length, length); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /** + * Pad or truncate the specified string to the specified length. + * If the string is already the specified length, it is returned unchanged. + * If it is longer than the specified length, only the last part of the string is returned. + * If it is shorter than the specified length, it is padded with the + * specified character at the front. + * String#frontPadOrTruncateOn(int, char, StringBuilder) + */ + public static void frontPadOrTruncateOn(char[] string, int length, char c, StringBuilder sb) { + int stringLength = string.length; + if (stringLength == length) { + sb.append(string); + } else if (stringLength > length) { + sb.append(string, stringLength - length, length); + } else { + frontPadOn_(string, length, c, sb); + } + } + + /* + * Front-pad the specified string without validating the parms. + */ + private static String frontPad_(String string, int length, char c) { + return new String(frontPad_(string.toCharArray(), length, c)); + } + + /* + * Zero-pad the specified string without validating the parms. + */ + private static char[] frontPad_(char[] string, int length, char c) { + char[] result = new char[length]; + int stringLength = string.length; + int padLength = length - stringLength; + System.arraycopy(string, 0, result, padLength, stringLength); + Arrays.fill(result, 0, padLength, c); + return result; + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(String string, int length, char c, Writer writer) { + fill_(string, length, c, writer); + writeStringOn(string, writer); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(char[] string, int length, char c, Writer writer) { + fill_(string, length, c, writer); + writeStringOn(string, writer); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(String string, int length, char c, StringBuffer sb) { + fill_(string, length, c, sb); + sb.append(string); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(char[] string, int length, char c, StringBuffer sb) { + fill_(string, length, c, sb); + sb.append(string); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(String string, int length, char c, StringBuilder sb) { + fill_(string, length, c, sb); + sb.append(string); + } + + /* + * Pad the specified string without validating the parms. + */ + private static void frontPadOn_(char[] string, int length, char c, StringBuilder sb) { + fill_(string, length, c, sb); + sb.append(string); + } + + + // ********** separating ********** + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static String separate(String string, char separator, int segmentSize) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + int len = string.length(); + return (len <= segmentSize) ? string : new String(separate(string.toCharArray(), separator, segmentSize, len)); + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(String string, char separator, int segmentSize, Writer writer) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length() <= segmentSize) { + writeStringOn(string, writer); + } else { + separateOn_(string.toCharArray(), separator, segmentSize, writer); + } + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(String string, char separator, int segmentSize, StringBuffer sb) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length() <= segmentSize) { + sb.append(string); + } else { + separateOn_(string.toCharArray(), separator, segmentSize, sb); + } + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(String string, char separator, int segmentSize, StringBuilder sb) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length() <= segmentSize) { + sb.append(string); + } else { + separateOn_(string.toCharArray(), separator, segmentSize, sb); + } + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static char[] separate(char[] string, char separator, int segmentSize) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + int len = string.length; + return (len <= segmentSize) ? string : separate(string, separator, segmentSize, len); + } + + /** + * pre-conditions: string is longer than segment size; segment size is positive + */ + private static char[] separate(char[] string, char separator, int segmentSize, int len) { + int resultLen = len + (len / segmentSize); + if ((len % segmentSize) == 0) { + resultLen--; // no separator after the final segment if nothing following it + } + char[] result = new char[resultLen]; + int j = 0; + int segCount = 0; + for (char c : string) { + if (segCount == segmentSize) { + result[j++] = separator; + segCount = 0; + } + segCount++; + result[j++] = c; + } + return result; + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(char[] string, char separator, int segmentSize, Writer writer) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length <= segmentSize) { + writeStringOn(string, writer); + } else { + separateOn_(string, separator, segmentSize, writer); + } + } + + /** + * pre-conditions: string is longer than segment size; segment size is positive + */ + private static void separateOn_(char[] string, char separator, int segmentSize, Writer writer) { + int segCount = 0; + for (char c : string) { + if (segCount == segmentSize) { + writeCharOn(separator, writer); + segCount = 0; + } + segCount++; + writeCharOn(c, writer); + } + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(char[] string, char separator, int segmentSize, StringBuffer sb) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length <= segmentSize) { + sb.append(string); + } else { + separateOn_(string, separator, segmentSize, sb); + } + } + + /** + * pre-conditions: string is longer than segment size; segment size is positive + */ + private static void separateOn_(char[] string, char separator, int segmentSize, StringBuffer sb) { + int segCount = 0; + for (char c : string) { + if (segCount == segmentSize) { + sb.append(separator); + segCount = 0; + } + segCount++; + sb.append(c); + } + } + + /** + * Separate the segments of the specified string with the specified + * separator:
+	 *     separate("012345", '-', 2) => "01-23-45"
+	 * 
+ */ + public static void separateOn(char[] string, char separator, int segmentSize, StringBuilder sb) { + if (segmentSize <= 0) { + throw new IllegalArgumentException("segment size must be positive: " + segmentSize); //$NON-NLS-1$ + } + if (string.length <= segmentSize) { + sb.append(string); + } else { + separateOn_(string, separator, segmentSize, sb); + } + } + + /** + * pre-conditions: string is longer than segment size; segment size is positive + */ + private static void separateOn_(char[] string, char separator, int segmentSize, StringBuilder sb) { + int segCount = 0; + for (char c : string) { + if (segCount == segmentSize) { + sb.append(separator); + segCount = 0; + } + segCount++; + sb.append(c); + } + } + + + // ********** delimiting/quoting ********** + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static String quote(String string) { + return delimit(string, QUOTE); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(String string, Writer writer) { + delimitOn(string, QUOTE, writer); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(String string, StringBuffer sb) { + delimitOn(string, QUOTE, sb); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(String string, StringBuilder sb) { + delimitOn(string, QUOTE, sb); + } + + /** + * Delimit each of the specified strings with double quotes. + * Escape any occurrences of a double quote in a string with another + * double quote. + */ + public static Iterator quote(Iterator strings) { + return delimit(strings, QUOTE); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static String delimit(String string, char delimiter) { + return new String(delimit(string.toCharArray(), delimiter)); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(String string, char delimiter, Writer writer) { + delimitOn(string.toCharArray(), delimiter, writer); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(String string, char delimiter, StringBuffer sb) { + delimitOn(string.toCharArray(), delimiter, sb); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(String string, char delimiter, StringBuilder sb) { + delimitOn(string.toCharArray(), delimiter, sb); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in a string with another delimiter. + */ + public static Iterable delimit(Iterable strings, char delimiter) { + return new TransformationIterable(strings, new CharStringDelimiter(delimiter)); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in a string with another delimiter. + */ + public static Iterator delimit(Iterator strings, char delimiter) { + return new TransformationIterator(strings, new CharStringDelimiter(delimiter)); + } + + private static class CharStringDelimiter implements Transformer { + private char delimiter; + CharStringDelimiter(char delimiter) { + super(); + this.delimiter = delimiter; + } + public String transform(String string) { + return StringTools.delimit(string, this.delimiter); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static String delimit(String string, String delimiter) { + if (delimiter.length() == 1) { + return delimit(string, delimiter.charAt(0)); + } + return new String(delimit(string.toCharArray(), delimiter.toCharArray())); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(String string, String delimiter, Writer writer) { + if (delimiter.length() == 1) { + delimitOn(string, delimiter.charAt(0), writer); + } else { + delimitOn(string.toCharArray(), delimiter.toCharArray(), writer); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(String string, String delimiter, StringBuffer sb) { + if (delimiter.length() == 1) { + delimitOn(string, delimiter.charAt(0), sb); + } else { + delimitOn(string.toCharArray(), delimiter.toCharArray(), sb); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(String string, String delimiter, StringBuilder sb) { + if (delimiter.length() == 1) { + delimitOn(string, delimiter.charAt(0), sb); + } else { + delimitOn(string.toCharArray(), delimiter.toCharArray(), sb); + } + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in a string with + * another delimiter. + */ + public static Iterable delimit(Iterable strings, String delimiter) { + return (delimiter.length() == 1) ? + delimit(strings, delimiter.charAt(0)) : + new TransformationIterable(strings, new StringStringDelimiter(delimiter)); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in a string with + * another delimiter. + */ + public static Iterator delimit(Iterator strings, String delimiter) { + return (delimiter.length() == 1) ? + delimit(strings, delimiter.charAt(0)) : + new TransformationIterator(strings, new StringStringDelimiter(delimiter)); + } + + private static class StringStringDelimiter implements Transformer { + private String delimiter; + StringStringDelimiter(String delimiter) { + super(); + this.delimiter = delimiter; + } + public String transform(String string) { + return StringTools.delimit(string, this.delimiter); + } + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static char[] quote(char[] string) { + return delimit(string, QUOTE); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(char[] string, Writer writer) { + delimitOn(string, QUOTE, writer); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(char[] string, StringBuffer sb) { + delimitOn(string, QUOTE, sb); + } + + /** + * Delimit the specified string with double quotes. + * Escape any occurrences of a double quote in the string with another + * double quote. + */ + public static void quoteOn(char[] string, StringBuilder sb) { + delimitOn(string, QUOTE, sb); + } + + /** + * Delimit each of the specified strings with double quotes. + * Escape any occurrences of a double quote in a string with another + * double quote. + */ + // cannot name method simply 'quote' because of type-erasure... + public static Iterator quoteCharArrays(Iterator strings) { + return delimitCharArrays(strings, QUOTE); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static char[] delimit(char[] string, char delimiter) { + StringBuilder sb = new StringBuilder(string.length + 2); + delimitOn(string, delimiter, sb); + return convertToCharArray(sb); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(char[] string, char delimiter, Writer writer) { + writeCharOn(delimiter, writer); + writeStringOn(string, delimiter, writer); + writeCharOn(delimiter, writer); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(char[] string, char delimiter, StringBuffer sb) { + sb.append(delimiter); + for (char c : string) { + if (c == delimiter) { + sb.append(c); + } + sb.append(c); + } + sb.append(delimiter); + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in the string with another delimiter. + */ + public static void delimitOn(char[] string, char delimiter, StringBuilder sb) { + sb.append(delimiter); + for (char c : string) { + if (c == delimiter) { + sb.append(c); + } + sb.append(c); + } + sb.append(delimiter); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in a string with another delimiter. + */ + // cannot name method simply 'delimit' because of type-erasure... + public static Iterable delimitCharArrays(Iterable strings, char delimiter) { + return new TransformationIterable(strings, new CharCharArrayDelimiter(delimiter)); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of the delimiter in a string with another delimiter. + */ + // cannot name method simply 'delimit' because of type-erasure... + public static Iterator delimitCharArrays(Iterator strings, char delimiter) { + return new TransformationIterator(strings, new CharCharArrayDelimiter(delimiter)); + } + + private static class CharCharArrayDelimiter implements Transformer { + private char delimiter; + CharCharArrayDelimiter(char delimiter) { + super(); + this.delimiter = delimiter; + } + public char[] transform(char[] string) { + return StringTools.delimit(string, this.delimiter); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static char[] delimit(char[] string, char[] delimiter) { + int delimiterLength = delimiter.length; + if (delimiterLength == 1) { + return delimit(string, delimiter[0]); + } + int stringLength = string.length; + char[] result = new char[stringLength+(2*delimiterLength)]; + System.arraycopy(delimiter, 0, result, 0, delimiterLength); + System.arraycopy(string, 0, result, delimiterLength, stringLength); + System.arraycopy(delimiter, 0, result, stringLength+delimiterLength, delimiterLength); + return result; + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(char[] string, char[] delimiter, Writer writer) { + if (delimiter.length == 1) { + delimitOn(string, delimiter[0], writer); + } else { + writeStringOn(delimiter, writer); + writeStringOn(string, writer); + writeStringOn(delimiter, writer); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(char[] string, char[] delimiter, StringBuffer sb) { + if (delimiter.length == 1) { + delimitOn(string, delimiter[0], sb); + } else { + sb.append(delimiter); + sb.append(string); + sb.append(delimiter); + } + } + + /** + * Delimit the specified string with the specified delimiter; i.e. put a copy of + * the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in the string with + * another delimiter. + */ + public static void delimitOn(char[] string, char[] delimiter, StringBuilder sb) { + if (delimiter.length == 1) { + delimitOn(string, delimiter[0], sb); + } else { + sb.append(delimiter); + sb.append(string); + sb.append(delimiter); + } + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in a string with + * another delimiter. + */ + // cannot name method simply 'delimit' because of type-erasure... + public static Iterable delimitCharArrays(Iterable strings, char[] delimiter) { + return new TransformationIterable(strings, new CharArrayCharArrayDelimiter(delimiter)); + } + + /** + * Delimit each of the specified strings with the specified delimiter; i.e. put a + * copy of the delimiter at the front and back of the resulting string. + * Escape any occurrences of a single-character delimiter in a string with + * another delimiter. + */ + // cannot name method simply 'delimit' because of type-erasure... + public static Iterator delimitCharArrays(Iterator strings, char[] delimiter) { + return new TransformationIterator(strings, new CharArrayCharArrayDelimiter(delimiter)); + } + + private static class CharArrayCharArrayDelimiter implements Transformer { + private char[] delimiter; + CharArrayCharArrayDelimiter(char[] delimiter) { + super(); + this.delimiter = delimiter; + } + public char[] transform(char[] string) { + return StringTools.delimit(string, this.delimiter); + } + } + + + // ********** delimiting queries ********** + + /** + * Return whether the specified string is quoted: "\"foo\"". + */ + public static boolean stringIsQuoted(String string) { + return stringIsDelimited(string, QUOTE); + } + + /** + * Return whether the specified string is parenthetical: "(foo)". + */ + public static boolean stringIsParenthetical(String string) { + return stringIsDelimited(string, OPEN_PARENTHESIS, CLOSE_PARENTHESIS); + } + + /** + * Return whether the specified string is bracketed: "[foo]". + */ + public static boolean stringIsBracketed(String string) { + return stringIsDelimited(string, OPEN_BRACKET, CLOSE_BRACKET); + } + + /** + * Return whether the specified string is braced: "{foo}". + */ + public static boolean stringIsBraced(String string) { + return stringIsDelimited(string, OPEN_BRACE, CLOSE_BRACE); + } + + /** + * Return whether the specified string is chevroned: "". + */ + public static boolean stringIsChevroned(String string) { + return stringIsDelimited(string, OPEN_CHEVRON, CLOSE_CHEVRON); + } + + /** + * Return whether the specified string is delimited by the specified + * character. + */ + public static boolean stringIsDelimited(String string, char c) { + return stringIsDelimited(string, c, c); + } + + /** + * Return whether the specified string is delimited by the specified + * characters. + */ + public static boolean stringIsDelimited(String string, char start, char end) { + int len = string.length(); + if (len < 2) { + return false; + } + return stringIsDelimited(string.toCharArray(), start, end, len); + } + + /** + * Return whether the specified string is quoted: "\"foo\"". + */ + public static boolean stringIsQuoted(char[] string) { + return stringIsDelimited(string, QUOTE); + } + + /** + * Return whether the specified string is parenthetical: "(foo)". + */ + public static boolean stringIsParenthetical(char[] string) { + return stringIsDelimited(string, OPEN_PARENTHESIS, CLOSE_PARENTHESIS); + } + + /** + * Return whether the specified string is bracketed: "[foo]". + */ + public static boolean stringIsBracketed(char[] string) { + return stringIsDelimited(string, OPEN_BRACKET, CLOSE_BRACKET); + } + + /** + * Return whether the specified string is braced: "{foo}". + */ + public static boolean stringIsBraced(char[] string) { + return stringIsDelimited(string, OPEN_BRACE, CLOSE_BRACE); + } + + /** + * Return whether the specified string is chevroned: "". + */ + public static boolean stringIsChevroned(char[] string) { + return stringIsDelimited(string, OPEN_CHEVRON, CLOSE_CHEVRON); + } + + /** + * Return whether the specified string is delimited by the specified + * character. + */ + public static boolean stringIsDelimited(char[] string, char c) { + return stringIsDelimited(string, c, c); + } + + /** + * Return whether the specified string is delimited by the specified + * characters. + */ + public static boolean stringIsDelimited(char[] string, char start, char end) { + int len = string.length; + if (len < 2) { + return false; + } + return stringIsDelimited(string, start, end, len); + } + + private static boolean stringIsDelimited(char[] s, char start, char end, int len) { + return (s[0] == start) && (s[len - 1] == end); + } + + + // ********** undelimiting ********** + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static String undelimit(String string) { + int len = string.length() - 2; + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + string + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return EMPTY_STRING; + } + return new String(undelimit_(string.toCharArray(), len)); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static String undelimit(String string, int count) { + int len = string.length() - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + string + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return EMPTY_STRING; + } + return new String(undelimit(string.toCharArray(), len, count)); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static char[] undelimit(char[] string) { + int len = string.length - 2; + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return EMPTY_CHAR_ARRAY; + } + return undelimit_(string, len); + } + + private static char[] undelimit_(char[] string, int length) { + StringBuilder sb = new StringBuilder(length); + undelimitOn_(string, sb); + return convertToCharArray(sb); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static char[] undelimit(char[] string, int count) { + int len = string.length - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return EMPTY_CHAR_ARRAY; + } + return undelimit(string, len, count); + } + + private static char[] undelimit(char[] string, int len, int count) { + char[] result = new char[len]; + System.arraycopy(string, count, result, 0, len); + return result; + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(String string, Writer writer) { + undelimitOn(string.toCharArray(), writer); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(String string, int count, Writer writer) { + int len = string.length() - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + string + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + writeStringOn(string, count, len, writer); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(String string, StringBuffer sb) { + undelimitOn(string.toCharArray(), sb); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(String string, int count, StringBuffer sb) { + int len = string.length() - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + string + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + sb.append(string, count, count + len); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(String string, StringBuilder sb) { + undelimitOn(string.toCharArray(), sb); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(String string, int count, StringBuilder sb) { + int len = string.length() - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + string + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + sb.append(string, count, count + len); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(char[] string, Writer writer) { + int len = string.length - 2; + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + undelimitOn_(string, writer); + } + + /** + * pre-condition: string is at least 3 characters long + */ + private static void undelimitOn_(char[] string, Writer writer) { + char delimiter = string[0]; // the first char is the delimiter + char c = string[0]; + char next = string[1]; + int i = 1; + int last = string.length - 1; + do { + c = next; + writeCharOn(c, writer); + i++; + next = string[i]; + if (c == delimiter) { + if ((next != delimiter) || (i == last)) { + // an embedded delimiter must be followed by another delimiter + return; + } + i++; + next = string[i]; + } + } while (i != last); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(char[] string, int count, Writer writer) { + int len = string.length - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + writeStringOn(string, count, len, writer); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(char[] string, StringBuffer sb) { + int len = string.length - 2; + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + undelimitOn_(string, sb); + } + + /** + * pre-condition: string is at least 3 characters long + */ + private static void undelimitOn_(char[] string, StringBuffer sb) { + char delimiter = string[0]; // the first char is the delimiter + char c = string[0]; + char next = string[1]; + int i = 1; + int last = string.length - 1; + do { + c = next; + sb.append(c); + i++; + next = string[i]; + if (c == delimiter) { + if ((next != delimiter) || (i == last)) { + // an embedded delimiter must be followed by another delimiter + return; + } + i++; + next = string[i]; + } + } while (i != last); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(char[] string, int count, StringBuffer sb) { + int len = string.length - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + sb.append(string, count, len); + } + + /** + * Remove the delimiters from the specified string, removing any escape + * characters. Throw an IllegalArgumentException if the string is too short + * to undelimit (i.e. length < 2). + */ + public static void undelimitOn(char[] string, StringBuilder sb) { + int len = string.length - 2; + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + undelimitOn_(string, sb); + } + + /** + * pre-condition: string is at least 3 characters long + */ + private static void undelimitOn_(char[] string, StringBuilder sb) { + char delimiter = string[0]; // the first char is the delimiter + char c = string[0]; + char next = string[1]; + int i = 1; + int last = string.length - 1; + do { + c = next; + sb.append(c); + i++; + next = string[i]; + if (c == delimiter) { + if ((next != delimiter) || (i == last)) { + // an embedded delimiter must be followed by another delimiter + return; + } + i++; + next = string[i]; + } + } while (i != last); + } + + /** + * Remove the first and last count characters from the specified string. + * If the string is too short to be undelimited, throw an + * IllegalArgumentException. + * Use this method to undelimit strings that do not escape embedded + * delimiters. + */ + public static void undelimitOn(char[] string, int count, StringBuilder sb) { + int len = string.length - (2 * count); + if (len < 0) { + throw new IllegalArgumentException("invalid string: \"" + new String(string) + '"'); //$NON-NLS-1$ + } + if (len == 0) { + return; + } + sb.append(string, count, len); + } + + + // ********** removing characters ********** + + /** + * Remove the first occurrence of the specified character + * from the specified string and return the result. + * String#removeFirstOccurrence(char) + */ + public static String removeFirstOccurrence(String string, char c) { + int index = string.indexOf(c); + if (index == -1) { + // character not found + return string; + } + if (index == 0) { + // character found at the front of string + return string.substring(1); + } + int last = string.length() - 1; + if (index == last) { + // character found at the end of string + return string.substring(0, last); + } + // character found somewhere in the middle of the string + return string.substring(0, index).concat(string.substring(index + 1)); + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, Writer) + */ + public static void removeFirstOccurrenceOn(String string, char c, Writer writer) { + int index = string.indexOf(c); + if (index == -1) { + writeStringOn(string, writer); + } else { + removeCharAtIndexOn(string.toCharArray(), index, writer); + } + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, StringBuffer) + */ + public static void removeFirstOccurrenceOn(String string, char c, StringBuffer sb) { + int index = string.indexOf(c); + if (index == -1) { + sb.append(string); + } else { + removeCharAtIndexOn(string.toCharArray(), index, sb); + } + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, StringBuilder) + */ + public static void removeFirstOccurrenceOn(String string, char c, StringBuilder sb) { + int index = string.indexOf(c); + if (index == -1) { + sb.append(string); + } else { + removeCharAtIndexOn(string.toCharArray(), index, sb); + } + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and return the result. + * String#removeFirstOccurrence(char) + */ + public static char[] removeFirstOccurrence(char[] string, char c) { + int index = ArrayTools.indexOf(string, c); + if (index == -1) { + // character not found + return string; + } + int last = string.length - 1; + char[] result = new char[last]; + if (index == 0) { + // character found at the front of string + System.arraycopy(string, 1, result, 0, last); + } else if (index == last) { + // character found at the end of string + System.arraycopy(string, 0, result, 0, last); + } else { + // character found somewhere in the middle of the string + System.arraycopy(string, 0, result, 0, index); + System.arraycopy(string, index + 1, result, index, last - index); + } + return result; + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, Writer) + */ + public static void removeFirstOccurrenceOn(char[] string, char c, Writer writer) { + int index = ArrayTools.indexOf(string, c); + if (index == -1) { + writeStringOn(string, writer); + } else { + removeCharAtIndexOn(string, index, writer); + } + } + + private static void removeCharAtIndexOn(char[] string, int index, Writer writer) { + int last = string.length - 1; + if (index == 0) { + // character found at the front of string + writeStringOn(string, 1, last, writer); + } else if (index == last) { + // character found at the end of string + writeStringOn(string, 0, last, writer); + } else { + // character found somewhere in the middle of the string + writeStringOn(string, 0, index, writer); + writeStringOn(string, index + 1, last - index, writer); + } + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, StringBuffer) + */ + public static void removeFirstOccurrenceOn(char[] string, char c, StringBuffer sb) { + int index = ArrayTools.indexOf(string, c); + if (index == -1) { + sb.append(string); + } else { + removeCharAtIndexOn(string, index, sb); + } + } + + private static void removeCharAtIndexOn(char[] string, int index, StringBuffer sb) { + int last = string.length - 1; + if (index == 0) { + // character found at the front of string + sb.append(string, 1, last); + } else if (index == last) { + // character found at the end of string + sb.append(string, 0, last); + } else { + // character found somewhere in the middle of the string + sb.append(string, 0, index); + sb.append(string, index + 1, last - index); + } + } + + /** + * Remove the first occurrence of the specified character + * from the specified string and print the result on the specified stream. + * String#removeFirstOccurrenceOn(char, StringBuilder) + */ + public static void removeFirstOccurrenceOn(char[] string, char c, StringBuilder sb) { + int index = ArrayTools.indexOf(string, c); + if (index == -1) { + sb.append(string); + } else { + removeCharAtIndexOn(string, index, sb); + } + } + + private static void removeCharAtIndexOn(char[] string, int index, StringBuilder sb) { + int last = string.length - 1; + if (index == 0) { + // character found at the front of string + sb.append(string, 1, last); + } else if (index == last) { + // character found at the end of string + sb.append(string, 0, last); + } else { + // character found somewhere in the middle of the string + sb.append(string, 0, index); + sb.append(string, index + 1, last - index); + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and return the result. + * String#removeAllOccurrences(char) + */ + public static String removeAllOccurrences(String string, char c) { + int first = string.indexOf(c); + return (first == -1) ? string : new String(removeAllOccurrences_(string.toCharArray(), c, first)); + } + + /** + * Remove all occurrences of the specified character + * from the specified string and write the result to the specified stream. + * String#removeAllOccurrencesOn(char, Writer) + */ + public static void removeAllOccurrencesOn(String string, char c, Writer writer) { + int first = string.indexOf(c); + if (first == -1) { + writeStringOn(string, writer); + } else { + removeAllOccurrencesOn_(string.toCharArray(), c, first, writer); + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and write the result to the specified stream. + * String#removeAllOccurrencesOn(char, StringBuffer) + */ + public static void removeAllOccurrencesOn(String string, char c, StringBuffer sb) { + int first = string.indexOf(c); + if (first == -1) { + sb.append(string); + } else { + removeAllOccurrencesOn_(string.toCharArray(), c, first, sb); + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and write the result to the specified stream. + * String#removeAllOccurrencesOn(char, StringBuilder) + */ + public static void removeAllOccurrencesOn(String string, char c, StringBuilder sb) { + int first = string.indexOf(c); + if (first == -1) { + sb.append(string); + } else { + removeAllOccurrencesOn_(string.toCharArray(), c, first, sb); + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and return the result. + * String#removeAllOccurrences(char) + */ + public static char[] removeAllOccurrences(char[] string, char c) { + int first = ArrayTools.indexOf(string, c); + return (first == -1) ? string : removeAllOccurrences_(string, c, first); + } + + /* + * The index of the first matching character is passed in. + */ + private static char[] removeAllOccurrences_(char[] string, char c, int first) { + StringBuilder sb = new StringBuilder(string.length); + removeAllOccurrencesOn_(string, c, first, sb); + return convertToCharArray(sb); + } + + /** + * Remove all occurrences of the specified character + * from the specified string and write the result to the + * specified writer. + * String#removeAllOccurrencesOn(char, Writer) + */ + public static void removeAllOccurrencesOn(char[] string, char c, Writer writer) { + int first = ArrayTools.indexOf(string, c); + if (first == -1) { + writeStringOn(string, writer); + } else { + removeAllOccurrencesOn_(string, c, first, writer); + } + } + + /* + * The index of the first matching character is passed in. + */ + private static void removeAllOccurrencesOn_(char[] string, char c, int first, Writer writer) { + writeStringOn(string, 0, first, writer); + int len = string.length; + for (int i = first; i < len; i++) { + char d = string[i]; + if (d != c) { + writeCharOn(d, writer); + } + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and append the result to the + * specified string buffer. + * String#removeAllOccurrencesOn(char, StringBuffer) + */ + public static void removeAllOccurrencesOn(char[] string, char c, StringBuffer sb) { + int first = ArrayTools.indexOf(string, c); + if (first == -1) { + sb.append(string); + } else { + removeAllOccurrencesOn_(string, c, first, sb); + } + } + + /* + * The index of the first matching character is passed in. + */ + private static void removeAllOccurrencesOn_(char[] string, char c, int first, StringBuffer sb) { + sb.append(string, 0, first); + int len = string.length; + for (int i = first; i < len; i++) { + char d = string[i]; + if (d != c) { + sb.append(d); + } + } + } + + /** + * Remove all occurrences of the specified character + * from the specified string and append the result to the + * specified string builder. + * String#removeAllOccurrencesOn(char, StringBuilder) + */ + public static void removeAllOccurrencesOn(char[] string, char c, StringBuilder sb) { + int first = ArrayTools.indexOf(string, c); + if (first == -1) { + sb.append(string); + } else { + removeAllOccurrencesOn_(string, c, first, sb); + } + } + + /* + * The index of the first matching character is passed in. + */ + private static void removeAllOccurrencesOn_(char[] string, char c, int first, StringBuilder sb) { + sb.append(string, 0, first); + int len = string.length; + for (int i = first; i < len; i++) { + char d = string[i]; + if (d != c) { + sb.append(d); + } + } + } + + /** + * Remove all the spaces from the specified string and return the result. + * String#removeAllSpaces() + */ + public static String removeAllSpaces(String string) { + return removeAllOccurrences(string, ' '); + } + + /** + * Remove all the spaces + * from the specified string and write the result to the specified writer. + * String#removeAllSpacesOn(Writer) + */ + public static void removeAllSpacesOn(String string, Writer writer) { + removeAllOccurrencesOn(string, ' ', writer); + } + + /** + * Remove all the spaces + * from the specified string and write the result to the specified + * string buffer. + * String#removeAllSpacesOn(StringBuffer) + */ + public static void removeAllSpacesOn(String string, StringBuffer sb) { + removeAllOccurrencesOn(string, ' ', sb); + } + + /** + * Remove all the spaces + * from the specified string and write the result to the specified + * string builder. + * String#removeAllSpacesOn(StringBuilder) + */ + public static void removeAllSpacesOn(String string, StringBuilder sb) { + removeAllOccurrencesOn(string, ' ', sb); + } + + /** + * Remove all the spaces from the specified string and return the result. + * String#removeAllSpaces() + */ + public static char[] removeAllSpaces(char[] string) { + return removeAllOccurrences(string, ' '); + } + + /** + * Remove all the spaces + * from the specified string and write the result to the + * specified writer. + * String#removeAllSpacesOn(Writer) + */ + public static void removeAllSpacesOn(char[] string, Writer writer) { + removeAllOccurrencesOn(string, ' ', writer); + } + + /** + * Remove all the spaces + * from the specified string and append the result to the + * specified string buffer. + * String#removeAllSpacesOn(StringBuffer) + */ + public static void removeAllSpacesOn(char[] string, StringBuffer sb) { + removeAllOccurrencesOn(string, ' ', sb); + } + + /** + * Remove all the spaces + * from the specified string and append the result to the + * specified string builder. + * String#removeAllSpacesOn(StringBuilder) + */ + public static void removeAllSpacesOn(char[] string, StringBuilder sb) { + removeAllOccurrencesOn(string, ' ', sb); + } + + /** + * Remove all the whitespace from the specified string and return the result. + * String#removeAllWhitespace() + */ + public static String removeAllWhitespace(String string) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + return (first == -1) ? string : new String(removeAllWhitespace_(string2, first)); + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified writer. + * String#removeAllWhitespaceOn(Writer) + */ + public static void removeAllWhitespaceOn(String string, Writer writer) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + writeStringOn(string, writer); + } else { + removeAllWhitespaceOn_(string2, first, writer); + } + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified string buffer. + * String#removeAllWhitespaceOn(StringBuffer) + */ + public static void removeAllWhitespaceOn(String string, StringBuffer sb) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + sb.append(string); + } else { + removeAllWhitespaceOn_(string2, first, sb); + } + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified string builder. + * String#removeAllWhitespaceOn(StringBuilder) + */ + public static void removeAllWhitespaceOn(String string, StringBuilder sb) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + sb.append(string); + } else { + removeAllWhitespaceOn_(string2, first, sb); + } + } + + /** + * Remove all the whitespace from the specified string and return the result. + * String#removeAllWhitespace() + */ + public static char[] removeAllWhitespace(char[] string) { + int first = indexOfWhitespace_(string); + return (first == -1) ? string : removeAllWhitespace_(string, first); + } + + private static int indexOfWhitespace_(char[] string) { + int len = string.length; + for (int i = 0; i < len; i++) { + if (Character.isWhitespace(string[i])) { + return i; + } + } + return -1; + } + + /* + * The index of the first non-whitespace character is passed in. + */ + private static char[] removeAllWhitespace_(char[] string, int first) { + StringBuilder sb = new StringBuilder(string.length); + removeAllWhitespaceOn_(string, first, sb); + return convertToCharArray(sb); + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified writer. + * String#removeAllWhitespaceOn(Writer) + */ + public static void removeAllWhitespaceOn(char[] string, Writer writer) { + int first = indexOfWhitespace_(string); + if (first == -1) { + writeStringOn(string, writer); + } else { + removeAllWhitespaceOn_(string, first, writer); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void removeAllWhitespaceOn_(char[] string, int first, Writer writer) { + writeStringOn(string, 0, first, writer); + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if ( ! Character.isWhitespace(c)) { + writeCharOn(c, writer); + } + } + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified string buffer. + * String#removeAllWhitespaceOn(StringBuffer) + */ + public static void removeAllWhitespaceOn(char[] string, StringBuffer sb) { + int first = indexOfWhitespace_(string); + if (first == -1) { + sb.append(string); + } else { + removeAllWhitespaceOn_(string, first, sb); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void removeAllWhitespaceOn_(char[] string, int first, StringBuffer sb) { + sb.append(string, 0, first); + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if ( ! Character.isWhitespace(c)) { + sb.append(c); + } + } + } + + /** + * Remove all the whitespace + * from the specified string and append the result to the + * specified string builder. + * String#removeAllWhitespaceOn(StringBuilder) + */ + public static void removeAllWhitespaceOn(char[] string, StringBuilder sb) { + int first = indexOfWhitespace_(string); + if (first == -1) { + sb.append(string); + } else { + removeAllWhitespaceOn_(string, first, sb); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void removeAllWhitespaceOn_(char[] string, int first, StringBuilder sb) { + sb.append(string, 0, first); + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if ( ! Character.isWhitespace(c)) { + sb.append(c); + } + } + } +//=============================== + /** + * Compress the whitespace in the specified string and return the result. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespace() + */ + public static String compressWhitespace(String string) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + return (first == -1) ? string : new String(compressWhitespace_(string2, first)); + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified writer. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(Writer) + */ + public static void compressWhitespaceOn(String string, Writer writer) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + writeStringOn(string, writer); + } else { + compressWhitespaceOn_(string2, first, writer); + } + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified string buffer. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(StringBuffer) + */ + public static void compressWhitespaceOn(String string, StringBuffer sb) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + sb.append(string); + } else { + compressWhitespaceOn_(string2, first, sb); + } + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified string builder. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(StringBuilder) + */ + public static void compressWhitespaceOn(String string, StringBuilder sb) { + char[] string2 = string.toCharArray(); + int first = indexOfWhitespace_(string2); + if (first == -1) { + sb.append(string); + } else { + compressWhitespaceOn_(string2, first, sb); + } + } + + /** + * Compress the whitespace in the specified string and return the result. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespace() + */ + public static char[] compressWhitespace(char[] string) { + int first = indexOfWhitespace_(string); + return (first == -1) ? string : compressWhitespace_(string, first); + } + + /* + * The index of the first whitespace character is passed in. + */ + private static char[] compressWhitespace_(char[] string, int first) { + StringBuilder sb = new StringBuilder(string.length); + compressWhitespaceOn_(string, first, sb); + return convertToCharArray(sb); + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified writer. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(Writer) + */ + public static void compressWhitespaceOn(char[] string, Writer writer) { + int first = indexOfWhitespace_(string); + if (first == -1) { + writeStringOn(string, writer); + } else { + compressWhitespaceOn_(string, first, writer); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void compressWhitespaceOn_(char[] string, int first, Writer writer) { + writeStringOn(string, 0, first, writer); + boolean spaceWritten = false; + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if (Character.isWhitespace(c)) { + if (spaceWritten) { + // skip subsequent whitespace characters + } else { + // replace first whitespace character with a space + spaceWritten = true; + writeCharOn(' ', writer); + } + } else { + spaceWritten = false; + writeCharOn(c, writer); + } + } + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified string buffer. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(StringBuffer) + */ + public static void compressWhitespaceOn(char[] string, StringBuffer sb) { + int first = indexOfWhitespace_(string); + if (first == -1) { + sb.append(string); + } else { + compressWhitespaceOn_(string, first, sb); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void compressWhitespaceOn_(char[] string, int first, StringBuffer sb) { + sb.append(string, 0, first); + boolean spaceWritten = false; + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if (Character.isWhitespace(c)) { + if (spaceWritten) { + // skip subsequent whitespace characters + } else { + // replace first whitespace character with a space + spaceWritten = true; + sb.append(' '); + } + } else { + spaceWritten = false; + sb.append(c); + } + } + } + + /** + * Compress the whitespace + * in the specified string and append the result to the + * specified string builder. + * The whitespace is compressed by replacing any occurrence of one or more + * whitespace characters with a single space. + * String#compressWhitespaceOn(StringBuilder) + */ + public static void compressWhitespaceOn(char[] string, StringBuilder sb) { + int first = indexOfWhitespace_(string); + if (first == -1) { + sb.append(string); + } else { + compressWhitespaceOn_(string, first, sb); + } + } + + /* + * The index of the first whitespace character is passed in. + */ + private static void compressWhitespaceOn_(char[] string, int first, StringBuilder sb) { + sb.append(string, 0, first); + boolean spaceWritten = false; + int len = string.length; + for (int i = first; i < len; i++) { + char c = string[i]; + if (Character.isWhitespace(c)) { + if (spaceWritten) { + // skip subsequent whitespace characters + } else { + // replace first whitespace character with a space + spaceWritten = true; + sb.append(' '); + } + } else { + spaceWritten = false; + sb.append(c); + } + } + } + + + // ********** common prefix ********** + + /** + * Return the length of the common prefix shared by the specified strings. + * String#commonPrefixLength(String) + */ + public static int commonPrefixLength(String s1, String s2) { + return commonPrefixLength(s1.toCharArray(), s2.toCharArray()); + } + + /** + * Return the length of the common prefix shared by the specified strings. + */ + public static int commonPrefixLength(char[] s1, char[] s2) { + return commonPrefixLength_(s1, s2, Math.min(s1.length, s2.length)); + } + + /** + * Return the length of the common prefix shared by the specified strings; + * but limit the length to the specified maximum. + * String#commonPrefixLength(String, int) + */ + public static int commonPrefixLength(String s1, String s2, int max) { + return commonPrefixLength(s1.toCharArray(), s2.toCharArray(), max); + } + + /** + * Return the length of the common prefix shared by the specified strings; + * but limit the length to the specified maximum. + */ + public static int commonPrefixLength(char[] s1, char[] s2, int max) { + return commonPrefixLength_(s1, s2, Math.min(max, Math.min(s1.length, s2.length))); + } + + /* + * Return the length of the common prefix shared by the specified strings; + * but limit the length to the specified maximum. Assume the specified + * maximum is less than the lengths of the specified strings. + */ + private static int commonPrefixLength_(char[] s1, char[] s2, int max) { + for (int i = 0; i < max; i++) { + if (s1[i] != s2[i]) { + return i; + } + } + return max; // all the characters up to 'max' are the same + } + + + // ********** capitalization ********** + + /* + * no zero-length check or lower case check + */ + private static char[] capitalize_(char[] string) { + string[0] = Character.toUpperCase(string[0]); + return string; + } + + /** + * Modify and return the specified string with + * its first letter capitalized. + */ + public static char[] capitalize(char[] string) { + if ((string.length == 0) || Character.isUpperCase(string[0])) { + return string; + } + return capitalize_(string); + } + + /** + * Return the specified string with its first letter capitalized. + * String#capitalize() + */ + public static String capitalize(String string) { + if ((string.length() == 0) || Character.isUpperCase(string.charAt(0))) { + return string; + } + return new String(capitalize_(string.toCharArray())); + } + + /** + * Modify each of the specified strings, capitalizing the first letter of + * each. + */ + public static Iterable capitalize(Iterable strings) { + return new TransformationIterable(strings, STRING_CAPITALIZER); + } + + /** + * Modify each of the specified strings, capitalizing the first letter of + * each. + */ + public static Iterator capitalize(Iterator strings) { + return new TransformationIterator(strings, STRING_CAPITALIZER); + } + + private static final Transformer STRING_CAPITALIZER = new Transformer() { + public String transform(String string) { + return StringTools.capitalize(string); + } + }; + + /** + * Modify each of the specified strings, capitalizing the first letter of + * each. + */ + // cannot name method simply 'capitalize' because of type-erasure... + public static Iterable capitalizeCharArrays(Iterable strings) { + return new TransformationIterable(strings, CHAR_ARRAY_CAPITALIZER); + } + + /** + * Modify each of the specified strings, capitalizing the first letter of + * each. + */ + // cannot name method simply 'capitalize' because of type-erasure... + public static Iterator capitalizeCharArrays(Iterator strings) { + return new TransformationIterator(strings, CHAR_ARRAY_CAPITALIZER); + } + + private static final Transformer CHAR_ARRAY_CAPITALIZER = new Transformer() { + public char[] transform(char[] string) { + return StringTools.capitalize(string); + } + }; + + /* + * no zero-length check or upper case check + */ + private static void capitalizeOn_(char[] string, StringBuffer sb) { + sb.append(Character.toUpperCase(string[0])); + sb.append(string, 1, string.length - 1); + } + + /** + * Append the specified string to the specified string buffer + * with its first letter capitalized. + */ + public static void capitalizeOn(char[] string, StringBuffer sb) { + if (string.length == 0) { + return; + } + if (Character.isUpperCase(string[0])) { + sb.append(string); + } else { + capitalizeOn_(string, sb); + } + } + + /** + * Append the specified string to the specified string buffer + * with its first letter capitalized. + * String#capitalizeOn(StringBuffer) + */ + public static void capitalizeOn(String string, StringBuffer sb) { + if (string.length() == 0) { + return; + } + if (Character.isUpperCase(string.charAt(0))) { + sb.append(string); + } else { + capitalizeOn_(string.toCharArray(), sb); + } + } + + /* + * no zero-length check or upper case check + */ + private static void capitalizeOn_(char[] string, StringBuilder sb) { + sb.append(Character.toUpperCase(string[0])); + sb.append(string, 1, string.length - 1); + } + + /** + * Append the specified string to the specified string builder + * with its first letter capitalized. + */ + public static void capitalizeOn(char[] string, StringBuilder sb) { + if (string.length == 0) { + return; + } + if (Character.isUpperCase(string[0])) { + sb.append(string); + } else { + capitalizeOn_(string, sb); + } + } + + /** + * Append the specified string to the specified string builder + * with its first letter capitalized. + * String#capitalizeOn(StringBuffer) + */ + public static void capitalizeOn(String string, StringBuilder sb) { + if (string.length() == 0) { + return; + } + if (Character.isUpperCase(string.charAt(0))) { + sb.append(string); + } else { + capitalizeOn_(string.toCharArray(), sb); + } + } + + /* + * no zero-length check or upper case check + */ + private static void capitalizeOn_(char[] string, Writer writer) { + writeCharOn(Character.toUpperCase(string[0]), writer); + writeStringOn(string, 1, string.length - 1, writer); + } + + /** + * Append the specified string to the specified string buffer + * with its first letter capitalized. + */ + public static void capitalizeOn(char[] string, Writer writer) { + if (string.length == 0) { + return; + } + if (Character.isUpperCase(string[0])) { + writeStringOn(string, writer); + } else { + capitalizeOn_(string, writer); + } + } + + /** + * Append the specified string to the specified string buffer + * with its first letter capitalized. + * String#capitalizeOn(Writer) + */ + public static void capitalizeOn(String string, Writer writer) { + if (string.length() == 0) { + return; + } + if (Character.isUpperCase(string.charAt(0))) { + writeStringOn(string, writer); + } else { + capitalizeOn_(string.toCharArray(), writer); + } + } + + /* + * no zero-length check or lower case check + */ + private static char[] uncapitalize_(char[] string) { + string[0] = Character.toLowerCase(string[0]); + return string; + } + + private static boolean stringNeedNotBeUncapitalized_(char[] string) { + if (string.length == 0) { + return true; + } + if (Character.isLowerCase(string[0])) { + return true; + } + // if both the first and second characters are capitalized, + // return the string unchanged + if ((string.length > 1) + && Character.isUpperCase(string[1]) + && Character.isUpperCase(string[0])){ + return true; + } + return false; + } + + /** + * Modify and return the specified string with its + * first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + */ + public static char[] uncapitalize(char[] string) { + if (stringNeedNotBeUncapitalized_(string)) { + return string; + } + return uncapitalize_(string); + } + + private static boolean stringNeedNotBeUncapitalized_(String string) { + if (string.length() == 0) { + return true; + } + if (Character.isLowerCase(string.charAt(0))) { + return true; + } + // if both the first and second characters are capitalized, + // return the string unchanged + if ((string.length() > 1) + && Character.isUpperCase(string.charAt(1)) + && Character.isUpperCase(string.charAt(0))){ + return true; + } + return false; + } + + /** + * Return the specified string with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + * String#uncapitalize() + */ + public static String uncapitalize(String string) { + if (stringNeedNotBeUncapitalized_(string)) { + return string; + } + return new String(uncapitalize_(string.toCharArray())); + } + + /* + * no zero-length check or lower case check + */ + private static void uncapitalizeOn_(char[] string, StringBuffer sb) { + sb.append(Character.toLowerCase(string[0])); + sb.append(string, 1, string.length - 1); + } + + /** + * Append the specified string to the specified string buffer + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + */ + public static void uncapitalizeOn(char[] string, StringBuffer sb) { + if (stringNeedNotBeUncapitalized_(string)) { + sb.append(string); + } else { + uncapitalizeOn_(string, sb); + } + } + + /** + * Append the specified string to the specified string buffer + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + * String#uncapitalizeOn(StringBuffer) + */ + public static void uncapitalizeOn(String string, StringBuffer sb) { + if (stringNeedNotBeUncapitalized_(string)) { + sb.append(string); + } else { + uncapitalizeOn_(string.toCharArray(), sb); + } + } + + /* + * no zero-length check or lower case check + */ + private static void uncapitalizeOn_(char[] string, StringBuilder sb) { + sb.append(Character.toLowerCase(string[0])); + sb.append(string, 1, string.length - 1); + } + + /** + * Append the specified string to the specified string builder + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + */ + public static void uncapitalizeOn(char[] string, StringBuilder sb) { + if (stringNeedNotBeUncapitalized_(string)) { + sb.append(string); + } else { + uncapitalizeOn_(string, sb); + } + } + + /** + * Append the specified string to the specified string builder + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + * String#uncapitalizeOn(StringBuffer) + */ + public static void uncapitalizeOn(String string, StringBuilder sb) { + if (stringNeedNotBeUncapitalized_(string)) { + sb.append(string); + } else { + uncapitalizeOn_(string.toCharArray(), sb); + } + } + + /* + * no zero-length check or upper case check + */ + private static void uncapitalizeOn_(char[] string, Writer writer) { + writeCharOn(Character.toLowerCase(string[0]), writer); + writeStringOn(string, 1, string.length - 1, writer); + } + + /** + * Append the specified string to the specified string buffer + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + */ + public static void uncapitalizeOn(char[] string, Writer writer) { + if (stringNeedNotBeUncapitalized_(string)) { + writeStringOn(string, writer); + } else { + uncapitalizeOn_(string, writer); + } + } + + /** + * Append the specified string to the specified string buffer + * with its first letter converted to lower case. + * (Unless both the first and second letters are upper case, + * in which case the string is returned unchanged.) + * String#uncapitalizeOn(Writer) + */ + public static void uncapitalizeOn(String string, Writer writer) { + if (stringNeedNotBeUncapitalized_(string)) { + writeStringOn(string, writer); + } else { + uncapitalizeOn_(string.toCharArray(), writer); + } + } + + + // ********** #toString() helper methods ********** + + /** + * Build a "standard" {@link Object#toString() toString()} result for the + * specified object and additional information:
+	 *     ClassName[00-F3-EE-42](add'l info)
+	 * 
+ */ + public static String buildToStringFor(Object o, Object additionalInfo) { + StringBuilder sb = new StringBuilder(); + buildSimpleToStringOn(o, sb); + sb.append('('); + sb.append(additionalInfo); + sb.append(')'); + return sb.toString(); + } + + /** + * Build a "standard" {@link Object#toString() toString()} result for the + * specified object:
+	 *     ClassName[00-F3-EE-42]
+	 * 
+ */ + public static String buildToStringFor(Object o) { + StringBuilder sb = new StringBuilder(); + buildSimpleToStringOn(o, sb); + return sb.toString(); + } + + /** + * Append a "standard" {@link Object#toString() toString()} result for the + * specified object to the specified buffer:
+	 *     ClassName[00-F3-EE-42]
+	 * 
+ */ + public static void buildSimpleToStringOn(Object o, StringBuffer sb) { + sb.append(buildToStringClassName(o.getClass())); + sb.append('['); + separateOn(buildHashCode(o), '-', 2, sb); + sb.append(']'); + } + + private static String buildHashCode(Object o) { + // use System#identityHashCode(Object), since Object#hashCode() may be overridden + return zeroPad(Integer.toHexString(System.identityHashCode(o)).toUpperCase(), 8); + } + + /** + * Append a "standard" {@link Object#toString() toString()} result for the + * specified object to the specified string builder:
+	 *     ClassName[00-F3-EE-42]
+	 * 
+ */ + public static void buildSimpleToStringOn(Object o, StringBuilder sb) { + sb.append(buildToStringClassName(o.getClass())); + sb.append('['); + separateOn(buildHashCode(o), '-', 2, sb); + sb.append(']'); + } + + /** + * Return a name suitable for a {@link Object#toString() toString()} implementation. + * {@link Class#getSimpleName()} isn't quite good enough for anonymous + * classes; since it returns an empty string. This method will return the + * name of the anonymous class's super class, which is a bit more helpful. + */ + public static String buildToStringClassName(Class javaClass) { + String simpleName = javaClass.getSimpleName(); + return simpleName.equals("") ? //$NON-NLS-1$ + buildToStringClassName(javaClass.getSuperclass()) : // recurse + simpleName; + } + + /** + * Append the string representations of the objects in the specified array + * to the specified string builder:
+	 *     ["foo", "bar", "baz"]
+	 * 
+ */ + public static String append(StringBuilder sb, T[] array) { + return append(sb, new ArrayListIterator(array)); + } + + /** + * Append the string representations of the objects in the specified iterable + * to the specified string builder:
+	 *     ["foo", "bar", "baz"]
+	 * 
+ */ + public static String append(StringBuilder sb, Iterable iterable) { + return append(sb, iterable.iterator()); + } + + /** + * Append the string representations of the objects in the specified iterator + * to the specified string builder:
+	 *     ["foo", "bar", "baz"]
+	 * 
+ */ + public static String append(StringBuilder sb, Iterator iterator) { + sb.append('['); + while (iterator.hasNext()) { + sb.append(iterator.next()); + if (iterator.hasNext()) { + sb.append(", "); //$NON-NLS-1$ + } + } + sb.append(']'); + return sb.toString(); + } + + + // ********** queries ********** + + /** + * Return whether the specified string is null, empty, or contains + * only whitespace characters. + */ + public static boolean stringIsEmpty(String string) { + if (string == null) { + return true; + } + int len = string.length(); + if (len == 0) { + return true; + } + return stringIsEmpty_(string.toCharArray(), len); + } + + /** + * Return whether the specified string is null, empty, or contains + * only whitespace characters. + */ + public static boolean stringIsEmpty(char[] string) { + if (string == null) { + return true; + } + int len = string.length; + if (len == 0) { + return true; + } + return stringIsEmpty_(string, len); + } + + private static boolean stringIsEmpty_(char[] s, int len) { + for (int i = len; i-- > 0; ) { + if ( ! Character.isWhitespace(s[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified string is non-null, non-empty, and does + * not contain only whitespace characters. + */ + public static boolean stringIsNotEmpty(String string) { + return ! stringIsEmpty(string); + } + + /** + * Return whether the specified string is non-null, non-empty, and does + * not contain only whitespace characters. + */ + public static boolean stringIsNotEmpty(char[] string) { + return ! stringIsEmpty(string); + } + + /** + * Return whether the specified strings are equal. + * Check for nulls. + */ + public static boolean stringsAreEqual(String s1, String s2) { + return Tools.valuesAreEqual(s1, s2); + } + + /** + * Return whether the specified strings are equal. + * Check for nulls. + */ + public static boolean stringsAreEqual(char[] s1, char[] s2) { + return (s1 == null) ? + (s2 == null) : + ((s2 != null) && stringsAreEqual_(s1, s2)); + } + + /** + * no null checks + */ + private static boolean stringsAreEqual_(char[] s1, char[] s2) { + int len = s1.length; + if (len != s2.length) { + return false; + } + for (int i = len; i-- > 0; ) { + if (s1[i] != s2[i]) { + return false; + } + } + return true; + } + + /** + * Return whether the specified strings are equal, ignoring case. + * Check for nulls. + */ + public static boolean stringsAreEqualIgnoreCase(String s1, String s2) { + return (s1 == null) ? + (s2 == null) : + ((s2 != null) && s1.equalsIgnoreCase(s2)); + } + + /** + * Return whether the specified strings are equal, ignoring case. + * Check for nulls. + */ + public static boolean stringsAreEqualIgnoreCase(char[] s1, char[] s2) { + return (s1 == null) ? + (s2 == null) : + ((s2 != null) && stringsAreEqualIgnoreCase_(s1, s2)); + } + + /** + * no null checks + */ + private static boolean stringsAreEqualIgnoreCase_(char[] s1, char[] s2) { + int len = s1.length; + if (len != s2.length) { + return false; + } + for (int i = len; i-- > 0; ) { + if ( ! charactersAreEqualIgnoreCase(s1[i], s2[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified string starts with the specified prefix, + * ignoring case. + */ + public static boolean stringStartsWithIgnoreCase(char[] string, char[] prefix) { + int prefixLength = prefix.length; + if (string.length < prefixLength) { + return false; + } + for (int i = prefixLength; i-- > 0; ) { + if ( ! charactersAreEqualIgnoreCase(string[i], prefix[i])) { + return false; + } + } + return true; + } + + /** + * Return whether the specified string starts with the specified prefix, + * ignoring case. + */ + public static boolean stringStartsWithIgnoreCase(String string, String prefix) { + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + } + + /** + * Return whether the specified characters are are equal, ignoring case. + * @see java.lang.String#regionMatches(boolean, int, String, int, int) + */ + public static boolean charactersAreEqualIgnoreCase(char c1, char c2) { + // something about the Georgian alphabet requires us to check lower case also + return (c1 == c2) + || (Character.toUpperCase(c1) == Character.toUpperCase(c2)) + || (Character.toLowerCase(c1) == Character.toLowerCase(c2)); + } + + /** + * Return whether the specified string is uppercase. + */ + public static boolean stringIsUppercase(String string) { + return (string.length() == 0) ? false : stringIsUppercase_(string); + } + + /** + * Return whether the specified string is uppercase. + */ + public static boolean stringIsUppercase(char[] string) { + return (string.length == 0) ? false : stringIsUppercase_(new String(string)); + } + + private static boolean stringIsUppercase_(String string) { + return string.equals(string.toUpperCase()); + } + + /** + * Return whether the specified string is lowercase. + */ + public static boolean stringIsLowercase(String string) { + return (string.length() == 0) ? false : stringIsLowercase_(string); + } + + /** + * Return whether the specified string is lowercase. + */ + public static boolean stringIsLowercase(char[] string) { + return (string.length == 0) ? false : stringIsLowercase_(new String(string)); + } + + private static boolean stringIsLowercase_(String string) { + return string.equals(string.toLowerCase()); + } + + + // ********** convert camel case to all caps ********** + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static String convertCamelCaseToAllCaps(String camelCaseString) { + int len = camelCaseString.length(); + if (len == 0) { + return camelCaseString; + } + return new String(convertCamelCaseToAllCaps_(camelCaseString.toCharArray(), len)); + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static char[] convertCamelCaseToAllCaps(char[] camelCaseString) { + int len = camelCaseString.length; + if (len == 0) { + return camelCaseString; + } + return convertCamelCaseToAllCaps_(camelCaseString, len); + } + + private static char[] convertCamelCaseToAllCaps_(char[] camelCaseString, int len) { + StringBuilder sb = new StringBuilder(len * 2); + convertCamelCaseToAllCapsOn_(camelCaseString, len, sb); + return convertToCharArray(sb); + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, StringBuffer sb) { + int len = camelCaseString.length(); + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), len, sb); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, StringBuffer sb) { + int len = camelCaseString.length; + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString, len, sb); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int len, StringBuffer sb) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + sb.append('_'); + } + sb.append(Character.toUpperCase(c)); + prev = c; + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, StringBuilder sb) { + int len = camelCaseString.length(); + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), len, sb); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, StringBuilder sb) { + int len = camelCaseString.length; + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString, len, sb); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int len, StringBuilder sb) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + sb.append('_'); + } + sb.append(Character.toUpperCase(c)); + prev = c; + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, Writer writer) { + int len = camelCaseString.length(); + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), len, writer); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, Writer writer) { + int len = camelCaseString.length; + if (len != 0) { + convertCamelCaseToAllCapsOn_(camelCaseString, len, writer); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int len, Writer writer) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + writeCharOn('_', writer); + } + writeCharOn(Character.toUpperCase(c), writer); + prev = c; + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static String convertCamelCaseToAllCaps(String camelCaseString, int maxLength) { + int len = camelCaseString.length(); + if ((len == 0) || (maxLength == 0)) { + return camelCaseString; + } + return new String(convertCamelCaseToAllCaps_(camelCaseString.toCharArray(), maxLength, len)); + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static char[] convertCamelCaseToAllCaps(char[] camelCaseString, int maxLength) { + int len = camelCaseString.length; + if ((len == 0) || (maxLength == 0)) { + return camelCaseString; + } + return convertCamelCaseToAllCaps_(camelCaseString, maxLength, len); + } + + private static char[] convertCamelCaseToAllCaps_(char[] camelCaseString, int maxLength, int len) { + StringBuilder sb = new StringBuilder(maxLength); + convertCamelCaseToAllCapsOn_(camelCaseString, maxLength, len, sb); + return convertToCharArray(sb); + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, int maxLength, StringBuffer sb) { + int len = camelCaseString.length(); + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), maxLength, len, sb); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, int maxLength, StringBuffer sb) { + int len = camelCaseString.length; + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString, maxLength, len, sb); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int maxLength, int len, StringBuffer sb) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + sb.append('_'); + if (sb.length() == maxLength) { + return; + } + } + sb.append(Character.toUpperCase(c)); + if (sb.length() == maxLength) { + return; + } + prev = c; + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, int maxLength, StringBuilder sb) { + int len = camelCaseString.length(); + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), maxLength, len, sb); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, int maxLength, StringBuilder sb) { + int len = camelCaseString.length; + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString, maxLength, len, sb); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int maxLength, int len, StringBuilder sb) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + sb.append('_'); + if (sb.length() == maxLength) { + return; + } + } + sb.append(Character.toUpperCase(c)); + if (sb.length() == maxLength) { + return; + } + prev = c; + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(String camelCaseString, int maxLength, Writer writer) { + int len = camelCaseString.length(); + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString.toCharArray(), maxLength, len, writer); + } + } + + /** + * Convert the specified "camel case" string to an "all caps" string: + * "largeProject" -> "LARGE_PROJECT" + * Limit the resulting string to the specified maximum length. + */ + public static void convertCamelCaseToAllCapsOn(char[] camelCaseString, int maxLength, Writer writer) { + int len = camelCaseString.length; + if ((len != 0) && (maxLength != 0)) { + convertCamelCaseToAllCapsOn_(camelCaseString, maxLength, len, writer); + } + } + + private static void convertCamelCaseToAllCapsOn_(char[] camelCaseString, int maxLength, int len, Writer writer) { + char prev = 0; // assume 0 is not a valid char + char c = 0; + char next = camelCaseString[0]; + int writerLength = 0; + for (int i = 1; i <= len; i++) { // NB: start at 1 and end at len! + c = next; + next = ((i == len) ? 0 : camelCaseString[i]); + if (camelCaseWordBreak_(prev, c, next)) { + writeCharOn('_', writer); + if (++writerLength == maxLength) { + return; + } + } + writeCharOn(Character.toUpperCase(c), writer); + if (++writerLength == maxLength) { + return; + } + prev = c; + } + } + + /* + * Return whether the specified series of characters occur at + * a "camel case" work break: + * "*aa" -> false + * "*AA" -> false + * "*Aa" -> false + * "AaA" -> false + * "AAA" -> false + * "aa*" -> false + * "AaA" -> false + * "aAa" -> true + * "AA*" -> false + * "AAa" -> true + * where '*' == any char + */ + private static boolean camelCaseWordBreak_(char prev, char c, char next) { + if (prev == 0) { // start of string + return false; + } + if (Character.isLowerCase(c)) { + return false; + } + if (Character.isLowerCase(prev)) { + return true; + } + if (next == 0) { // end of string + return false; + } + return Character.isLowerCase(next); + } + + + // ********** convert underscores to camel case ********** + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "LargeProject" + * Capitalize the first letter. + */ + public static String convertUnderscoresToCamelCase(String underscoreString) { + return convertUnderscoresToCamelCase(underscoreString, true); + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "LargeProject" + * Capitalize the first letter. + */ + public static char[] convertUnderscoresToCamelCase(char[] underscoreString) { + return convertUnderscoresToCamelCase(underscoreString, true); + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static String convertUnderscoresToCamelCase(String underscoreString, boolean capitalizeFirstLetter) { + int len = underscoreString.length(); + if (len == 0) { + return underscoreString; + } + return new String(convertUnderscoresToCamelCase_(underscoreString.toCharArray(), capitalizeFirstLetter, len)); + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static char[] convertUnderscoresToCamelCase(char[] underscoreString, boolean capitalizeFirstLetter) { + int len = underscoreString.length; + if (len == 0) { + return underscoreString; + } + return convertUnderscoresToCamelCase_(underscoreString, capitalizeFirstLetter, len); + } + + private static char[] convertUnderscoresToCamelCase_(char[] underscoreString, boolean capitalizeFirstLetter, int len) { + StringBuilder sb = new StringBuilder(len); + convertUnderscoresToCamelCaseOn_(underscoreString, capitalizeFirstLetter, len, sb); + return convertToCharArray(sb); + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(String underscoreString, boolean capitalizeFirstLetter, StringBuffer sb) { + int len = underscoreString.length(); + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString.toCharArray(), capitalizeFirstLetter, len, sb); + } + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(char[] underscoreString, boolean capitalizeFirstLetter, StringBuffer sb) { + int len = underscoreString.length; + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString, capitalizeFirstLetter, len, sb); + } + } + + private static void convertUnderscoresToCamelCaseOn_(char[] underscoreString, boolean capitalizeFirstLetter, int len, StringBuffer sb) { + char prev = 0; + char c = 0; + boolean first = true; + for (int i = 0; i < len; i++) { + prev = c; + c = underscoreString[i]; + if (c == '_') { + continue; + } + if (first) { + first = false; + sb.append(capitalizeFirstLetter ? Character.toUpperCase(c) : Character.toLowerCase(c)); + } else { + sb.append((prev == '_') ? Character.toUpperCase(c) : Character.toLowerCase(c)); + } + } + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(String underscoreString, boolean capitalizeFirstLetter, StringBuilder sb) { + int len = underscoreString.length(); + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString.toCharArray(), capitalizeFirstLetter, len, sb); + } + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(char[] underscoreString, boolean capitalizeFirstLetter, StringBuilder sb) { + int len = underscoreString.length; + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString, capitalizeFirstLetter, len, sb); + } + } + + private static void convertUnderscoresToCamelCaseOn_(char[] underscoreString, boolean capitalizeFirstLetter, int len, StringBuilder sb) { + char prev = 0; + char c = 0; + boolean first = true; + for (int i = 0; i < len; i++) { + prev = c; + c = underscoreString[i]; + if (c == '_') { + continue; + } + if (first) { + first = false; + sb.append(capitalizeFirstLetter ? Character.toUpperCase(c) : Character.toLowerCase(c)); + } else { + sb.append((prev == '_') ? Character.toUpperCase(c) : Character.toLowerCase(c)); + } + } + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(String underscoreString, boolean capitalizeFirstLetter, Writer writer) { + int len = underscoreString.length(); + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString.toCharArray(), capitalizeFirstLetter, len, writer); + } + } + + /** + * Convert the specified "underscore" string to a "camel case" string: + * "LARGE_PROJECT" -> "largeProject" + * Optionally capitalize the first letter. + */ + public static void convertUnderscoresToCamelCaseOn(char[] underscoreString, boolean capitalizeFirstLetter, Writer writer) { + int len = underscoreString.length; + if (len != 0) { + convertUnderscoresToCamelCaseOn_(underscoreString, capitalizeFirstLetter, len, writer); + } + } + + private static void convertUnderscoresToCamelCaseOn_(char[] underscoreString, boolean capitalizeFirstLetter, int len, Writer writer) { + char prev = 0; + char c = 0; + boolean first = true; + for (int i = 0; i < len; i++) { + prev = c; + c = underscoreString[i]; + if (c == '_') { + continue; + } + if (first) { + first = false; + writeCharOn(capitalizeFirstLetter ? Character.toUpperCase(c) : Character.toLowerCase(c), writer); + } else { + writeCharOn((prev == '_') ? Character.toUpperCase(c) : Character.toLowerCase(c), writer); + } + } + } + + + // ********** convert to Java string literal ********** + + public static final String EMPTY_JAVA_STRING_LITERAL = "\"\""; //$NON-NLS-1$ + public static final char[] EMPTY_JAVA_STRING_LITERAL_CHAR_ARRAY = EMPTY_JAVA_STRING_LITERAL.toCharArray(); + + public static String convertToJavaStringLiteral(String string) { + int len = string.length(); + if (len == 0) { + return EMPTY_JAVA_STRING_LITERAL; + } + StringBuilder sb = new StringBuilder(len + 5); + convertToJavaStringLiteralOn_(string.toCharArray(), sb, len); + return sb.toString(); + } + + public static char[] convertToJavaStringLiteral(char[] string) { + int len = string.length; + if (len == 0) { + return EMPTY_JAVA_STRING_LITERAL_CHAR_ARRAY; + } + StringBuilder sb = new StringBuilder(len + 5); + convertToJavaStringLiteralOn_(string, sb, len); + len = sb.length(); + char[] result = new char[len]; + sb.getChars(0, len, result, 0); + return result; + } + + public static Iterable convertToJavaStringLiterals(Iterable strings) { + return new TransformationIterable(strings, STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER); + } + + public static Iterator convertToJavaStringLiterals(Iterator strings) { + return new TransformationIterator(strings, STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER); + } + + private static final Transformer STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER = new Transformer() { + public String transform(String string) { + return StringTools.convertToJavaStringLiteral(string); + } + }; + + // cannot name method simply 'convertToJavaStringLiterals' because of type-erasure... + public static Iterable convertToJavaCharArrayLiterals(Iterable strings) { + return new TransformationIterable(strings, CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER); + } + + // cannot name method simply 'convertToJavaStringLiterals' because of type-erasure... + public static Iterator convertToJavaCharArrayLiterals(Iterator strings) { + return new TransformationIterator(strings, CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER); + } + + private static final Transformer CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER = new Transformer() { + public char[] transform(char[] string) { + return StringTools.convertToJavaStringLiteral(string); + } + }; + + public static void convertToJavaStringLiteralOn(String string, StringBuffer sb) { + int len = string.length(); + if (len == 0) { + sb.append(EMPTY_JAVA_STRING_LITERAL); + } else { + convertToJavaStringLiteralOn_(string.toCharArray(), sb, len); + } + } + + public static void convertToJavaStringLiteralOn(char[] string, StringBuffer sb) { + int len = string.length; + if (len == 0) { + sb.append(EMPTY_JAVA_STRING_LITERAL); + } else { + convertToJavaStringLiteralOn_(string, sb, len); + } + } + + /* + * no length checks + */ + private static void convertToJavaStringLiteralOn_(char[] string, StringBuffer sb, int len) { + sb.ensureCapacity(sb.length() + len + 5); + sb.append(QUOTE); + for (char c : string) { + switch (c) { + case '\b': // backspace + sb.append("\\b"); //$NON-NLS-1$ + break; + case '\t': // horizontal tab + sb.append("\\t"); //$NON-NLS-1$ + break; + case '\n': // line-feed LF + sb.append("\\n"); //$NON-NLS-1$ + break; + case '\f': // form-feed FF + sb.append("\\f"); //$NON-NLS-1$ + break; + case '\r': // carriage-return CR + sb.append("\\r"); //$NON-NLS-1$ + break; + case '"': // double-quote + sb.append("\\\""); //$NON-NLS-1$ + break; +// case '\'': // single-quote +// sb.append("\\'"); //$NON-NLS-1$ +// break; + case '\\': // backslash + sb.append("\\\\"); //$NON-NLS-1$ + break; + default: + sb.append(c); + break; + } + } + sb.append(QUOTE); + } + + public static void convertToJavaStringLiteralOn(String string, StringBuilder sb) { + int len = string.length(); + if (len == 0) { + sb.append(EMPTY_JAVA_STRING_LITERAL); + } else { + convertToJavaStringLiteralOn_(string.toCharArray(), sb, len); + } + } + + public static void convertToJavaStringLiteralOn(char[] string, StringBuilder sb) { + int len = string.length; + if (len == 0) { + sb.append(EMPTY_JAVA_STRING_LITERAL); + } else { + convertToJavaStringLiteralOn_(string, sb, len); + } + } + + /* + * no length checks + */ + private static void convertToJavaStringLiteralOn_(char[] string, StringBuilder sb, int len) { + sb.ensureCapacity(sb.length() + len + 5); + sb.append(QUOTE); + for (char c : string) { + switch (c) { + case '\b': // backspace + sb.append("\\b"); //$NON-NLS-1$ + break; + case '\t': // horizontal tab + sb.append("\\t"); //$NON-NLS-1$ + break; + case '\n': // line-feed LF + sb.append("\\n"); //$NON-NLS-1$ + break; + case '\f': // form-feed FF + sb.append("\\f"); //$NON-NLS-1$ + break; + case '\r': // carriage-return CR + sb.append("\\r"); //$NON-NLS-1$ + break; + case '"': // double-quote + sb.append("\\\""); //$NON-NLS-1$ + break; +// case '\'': // single-quote +// sb.append("\\'"); //$NON-NLS-1$ +// break; + case '\\': // backslash + sb.append("\\\\"); //$NON-NLS-1$ + break; + default: + sb.append(c); + break; + } + } + sb.append(QUOTE); + } + + public static void convertToJavaStringLiteralOn(String string, Writer writer) { + if (string.length() == 0) { + writeStringOn(EMPTY_JAVA_STRING_LITERAL, writer); + } else { + convertToJavaStringLiteralOn_(string.toCharArray(), writer); + } + } + + public static void convertToJavaStringLiteralOn(char[] string, Writer writer) { + if (string.length == 0) { + writeStringOn(EMPTY_JAVA_STRING_LITERAL, writer); + } else { + convertToJavaStringLiteralOn_(string, writer); + } + } + + /* + * no length checks + */ + private static void convertToJavaStringLiteralOn_(char[] string, Writer writer) { + writeCharOn(QUOTE, writer); + for (char c : string) { + switch (c) { + case '\b': // backspace + writeStringOn("\\b", writer); //$NON-NLS-1$ + break; + case '\t': // horizontal tab + writeStringOn("\\t", writer); //$NON-NLS-1$ + break; + case '\n': // line-feed LF + writeStringOn("\\n", writer); //$NON-NLS-1$ + break; + case '\f': // form-feed FF + writeStringOn("\\f", writer); //$NON-NLS-1$ + break; + case '\r': // carriage-return CR + writeStringOn("\\r", writer); //$NON-NLS-1$ + break; + case '"': // double-quote + writeStringOn("\\\"", writer); //$NON-NLS-1$ + break; +// case '\'': // single-quote +// writeStringOn("\\'", writer); //$NON-NLS-1$ +// break; + case '\\': // backslash + writeStringOn("\\\\", writer); //$NON-NLS-1$ + break; + default: + writeCharOn(c, writer); + break; + } + } + writeCharOn(QUOTE, writer); + } + + + // ********** convenience ********** + + public static char[] convertToCharArray(StringBuffer sb) { + int len = sb.length(); + char[] result = new char[len]; + sb.getChars(0, len, result, 0); + return result; + } + + public static char[] convertToCharArray(StringBuilder sb) { + int len = sb.length(); + char[] result = new char[len]; + sb.getChars(0, len, result, 0); + return result; + } + + private static void writeStringOn(char[] string, Writer writer) { + try { + writer.write(string); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static void writeStringOn(char[] string, char escape, Writer writer) { + try { + for (char c : string) { + if (c == escape) { + writer.write(c); + } + writer.write(c); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static void writeStringOn(char[] string, int off, int len, Writer writer) { + try { + writer.write(string, off, len); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static void writeStringOn(String string, int off, int len, Writer writer) { + try { + writer.write(string, off, len); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static void writeStringOn(String string, Writer writer) { + try { + writer.write(string); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static void writeCharOn(char c, Writer writer) { + try { + writer.write(c); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + + // ********** constructor ********** + + /* + * Suppress default constructor, ensuring non-instantiability. + */ + private StringTools() { + super(); + throw new UnsupportedOperationException(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBag.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBag.java new file mode 100644 index 0000000000..5bcce38db1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBag.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * Thread-safe implementation of the {@link Bag} interface. + */ +public class SynchronizedBag + implements Bag, Serializable +{ + /** Backing bag. */ + private final Bag bag; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a synchronized bag that wraps the + * specified bag and locks on the specified mutex. + */ + public SynchronizedBag(Bag bag, Object mutex) { + super(); + if (bag == null) { + throw new NullPointerException(); + } + this.bag = bag; + this.mutex = mutex; + } + + /** + * Construct a synchronized bag that wraps the + * specified bag and locks on itself. + */ + public SynchronizedBag(Bag bag) { + super(); + if (bag == null) { + throw new NullPointerException(); + } + this.bag = bag; + this.mutex = this; + } + + /** + * Construct a synchronized bag that locks on the specified mutex. + */ + public SynchronizedBag(Object mutex) { + this(new HashBag(), mutex); + } + + /** + * Construct a synchronized bag that locks on itself. + */ + public SynchronizedBag() { + this(new HashBag()); + } + + + // ********** Bag implementation ********** + + public boolean add(E o, int count) { + synchronized (this.mutex) { + return this.bag.add(o, count); + } + } + + public int count(Object o) { + synchronized (this.mutex) { + return this.bag.count(o); + } + } + + public Iterator> entries() { + synchronized (this.mutex) { + return this.bag.entries(); + } + } + + public boolean remove(Object o, int count) { + synchronized (this.mutex) { + return this.bag.remove(o, count); + } + } + + public int uniqueCount() { + synchronized (this.mutex) { + return this.bag.uniqueCount(); + } + } + + public Iterator uniqueIterator() { + synchronized (this.mutex) { + return this.bag.uniqueIterator(); + } + } + + + // ********** Collection implementation ********** + + public boolean add(E e) { + synchronized (this.mutex) { + return this.bag.add(e); + } + } + + public boolean addAll(Collection c) { + synchronized (this.mutex) { + return this.bag.addAll(c); + } + } + + public void clear() { + synchronized (this.mutex) { + this.bag.clear(); + } + } + + public boolean contains(Object o) { + synchronized (this.mutex) { + return this.bag.contains(o); + } + } + + public boolean containsAll(Collection c) { + synchronized (this.mutex) { + return this.bag.containsAll(c); + } + } + + public boolean isEmpty() { + synchronized (this.mutex) { + return this.bag.isEmpty(); + } + } + + public Iterator iterator() { + synchronized (this.mutex) { + return this.bag.iterator(); + } + } + + public boolean remove(Object o) { + synchronized (this.mutex) { + return this.bag.remove(o); + } + } + + public boolean removeAll(Collection c) { + synchronized (this.mutex) { + return this.bag.removeAll(c); + } + } + + public boolean retainAll(Collection c) { + synchronized (this.mutex) { + return this.bag.retainAll(c); + } + } + + public int size() { + synchronized (this.mutex) { + return this.bag.size(); + } + } + + public Object[] toArray() { + synchronized (this.mutex) { + return this.bag.toArray(); + } + } + + public T[] toArray(T[] a) { + synchronized (this.mutex) { + return this.bag.toArray(a); + } + } + + + // ********** additional public protocol ********** + + /** + * Return the object the stack locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + synchronized (this.mutex) { + return this.bag.toString(); + } + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBoolean.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBoolean.java new file mode 100644 index 0000000000..e42a7e6691 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBoolean.java @@ -0,0 +1,437 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import org.eclipse.jpt.common.utility.Command; + +/** + * This class provides synchronized access to a boolean value. + * It also provides protocol for suspending a thread until the + * boolean value is set to true or false, + * with optional time-outs. + * + * @see SimpleBooleanReference + */ +public class SynchronizedBoolean + implements BooleanReference, Cloneable, Serializable +{ + /** Backing boolean. */ + private boolean value; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create a synchronized boolean with the specified + * initial value and mutex. + */ + public SynchronizedBoolean(boolean value, Object mutex) { + super(); + this.value = value; + this.mutex = mutex; + } + + /** + * Create a synchronized boolean with the + * specified initial value. + * The synchronized boolean itself will be the mutex. + */ + public SynchronizedBoolean(boolean value) { + super(); + this.value = value; + this.mutex = this; + } + + /** + * Create a synchronized boolean + * with an initial value of false + * and specified mutex. + */ + public SynchronizedBoolean(Object mutex) { + this(false, mutex); + } + + /** + * Create a synchronized boolean + * with an initial value of false. + * The synchronized boolean itself will be the mutex. + */ + public SynchronizedBoolean() { + this(false); + } + + + // ********** accessors ********** + + public boolean getValue() { + synchronized (this.mutex) { + return this.value; + } + } + + public boolean is(boolean v) { + synchronized (this.mutex) { + return this.value == v; + } + } + + public boolean isNot(boolean v) { + synchronized (this.mutex) { + return this.value != v; + } + } + + public boolean isTrue() { + synchronized (this.mutex) { + return this.value; + } + } + + public boolean isFalse() { + synchronized (this.mutex) { + return ! this.value; + } + } + + /** + * If the value changes, all waiting threads are notified. + */ + public boolean setValue(boolean value) { + synchronized (this.mutex) { + return this.setValue_(value); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean setValue_(boolean v) { + return (v == this.value) ? v : this.setChangedValue_(v); + } + + /** + * Pre-condition: synchronized and new value is different + */ + private boolean setChangedValue_(boolean v) { + this.value = v; + this.mutex.notifyAll(); + return ! v; + } + + /** + * If the value changes, all waiting threads are notified. + */ + public boolean flip() { + synchronized (this.mutex) { + return ! this.setChangedValue_( ! this.value); + } + } + + /** + * If the value changes, all waiting threads are notified. + */ + public boolean setNot(boolean v) { + return this.setValue( ! v); + } + + /** + * If the value changes, all waiting threads are notified. + */ + public boolean setTrue() { + return this.setValue(true); + } + + /** + * If the value changes, all waiting threads are notified. + */ + public boolean setFalse() { + return this.setValue(false); + } + + /** + * Return the object this object locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the boolean value changes + * to the specified value. If the boolean value is already the + * specified value, return immediately. + */ + public void waitUntilValueIs(boolean v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIs_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilValueIs_(boolean v) throws InterruptedException { + while (this.value != v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the boolean value + * changes to the NOT of the specified value. + * If the boolean value is already the NOT of the specified + * value, return immediately. + */ + public void waitUntilValueIsNot(boolean v) throws InterruptedException { + this.waitUntilValueIs( ! v); + } + + /** + * Suspend the current thread until the boolean value + * changes to true. + * If the boolean value is already true, + * return immediately. + */ + public void waitUntilTrue() throws InterruptedException { + this.waitUntilValueIs(true); + } + + /** + * Suspend the current thread until the boolean value + * changes to false. + * If the boolean value is already false, + * return immediately. + */ + public void waitUntilFalse() throws InterruptedException { + this.waitUntilValueIs(false); + } + + /** + * Suspend the current thread until the boolean value changes to + * not the specified value, then change it back to the specified + * value and continue executing. If the boolean value is already + * not the specified value, set the value to the specified value + * immediately. + */ + public void waitToSetValue(boolean v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIs_( ! v); + this.setChangedValue_(v); + } + } + + /** + * Suspend the current thread until the boolean value + * changes to false, + * then change it back to true and continue executing. + * If the boolean value is already false, + * set the value to true immediately. + */ + public void waitToSetTrue() throws InterruptedException { + this.waitToSetValue(true); + } + + /** + * Suspend the current thread until the boolean value + * changes to true, + * then change it back to false and continue executing. + * If the boolean value is already true, + * set the value to false immediately. + */ + public void waitToSetFalse() throws InterruptedException { + this.waitToSetValue(false); + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the boolean value changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the specified value was achieved; + * return false if a time-out occurred. + * If the boolean value is already the specified value, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilValueIs(boolean v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilValueIs_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilValueIs_(boolean v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilValueIs_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value != v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value == v); + } + + /** + * Suspend the current thread until the boolean value + * changes to the NOT of the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the NOT of the specified value was achieved; + * return false if a time-out occurred. + * If the boolean value is already the NOT of the specified + * value, return immediately. + * If the time-out is zero, wait indefinitely. + */ + public void waitUntilValueIsNot(boolean v, long timeout) throws InterruptedException { + this.waitUntilValueIs( ! v, timeout); + } + + /** + * Suspend the current thread until the boolean value changes + * to true or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * true was achieved; + * return false if a time-out occurred. + * If the boolean value is already true, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilTrue(long timeout) throws InterruptedException { + return this.waitUntilValueIs(true, timeout); + } + + /** + * Suspend the current thread until the boolean value changes + * to false or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * false was achieved; + * return false if a time-out occurred. + * If the boolean value is already true, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilFalse(long timeout) throws InterruptedException { + return this.waitUntilValueIs(false, timeout); + } + + /** + * Suspend the current thread until the boolean value changes + * to not the specified value, then change it back to the specified + * value and continue executing. If the boolean value does not + * change to false before the time-out, simply continue + * executing without changing the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to the specified value; return false + * if a time-out occurred. If the boolean value is already + * not the specified value, set the value to the specified value + * immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetValue(boolean v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilValueIs_( ! v, timeout); + if (success) { + this.setChangedValue_(v); + } + return success; + } + } + + /** + * Suspend the current thread until the boolean value changes + * to false, then change it back to true and + * continue executing. If the boolean value does not change to + * false before the time-out, simply continue executing without + * changing the value. The time-out is specified in milliseconds. Return + * true if the value was set to true; + * return false if a time-out occurred. If the + * boolean value is already false, set the + * value to true immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetTrue(long timeout) throws InterruptedException { + return this.waitToSetValue(true, timeout); + } + + /** + * Suspend the current thread until the boolean value changes + * to true, then change it back to false and + * continue executing. If the boolean value does not change to + * true before the time-out, simply continue executing without + * changing the value. The time-out is specified in milliseconds. Return + * true if the value was set to false; + * return false if a time-out occurred. If the + * boolean value is already true, set the + * value to false immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetFalse(long timeout) throws InterruptedException { + return this.waitToSetValue(false, timeout); + } + + + // ********** synchronized behavior ********** + + /** + * If the current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the value from another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** standard methods ********** + + @Override + public SynchronizedBoolean clone() { + try { + synchronized (this.mutex) { + return (SynchronizedBoolean) super.clone(); + } + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.getValue()) + ']'; + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedInt.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedInt.java new file mode 100644 index 0000000000..999aa44aa8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedInt.java @@ -0,0 +1,914 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +import org.eclipse.jpt.common.utility.Command; + +/** + * This class provides synchronized access to an int. + * It also provides protocol for suspending a thread until the + * value is set to a specified value, with optional time-outs. + * + * @see SimpleIntReference + */ +public class SynchronizedInt + implements IntReference, Cloneable, Serializable +{ + /** Backing int. */ + private int value; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create a synchronized integer with the specified initial value + * and mutex. + */ + public SynchronizedInt(int value, Object mutex) { + super(); + this.value = value; + this.mutex = mutex; + } + + /** + * Create a synchronized integer with the specified initial value. + * The synchronized integer itself will be the mutex. + */ + public SynchronizedInt(int value) { + super(); + this.value = value; + this.mutex = this; + } + + /** + * Create a synchronized integer with an initial value of zero + * and the specified mutex. + */ + public SynchronizedInt(Object mutex) { + this(0, mutex); + } + + /** + * Create a synchronized object with an initial value of zero. + * The synchronized integer itself will be the mutex. + */ + public SynchronizedInt() { + this(0); + } + + + // ********** methods ********** + + public int getValue() { + synchronized (this.mutex) { + return this.value; + } + } + + public boolean equals(int v) { + synchronized (this.mutex) { + return this.value == v; + } + } + + public boolean notEqual(int v) { + synchronized (this.mutex) { + return this.value != v; + } + } + + public boolean isZero() { + synchronized (this.mutex) { + return this.value == 0; + } + } + + public boolean isNotZero() { + synchronized (this.mutex) { + return this.value != 0; + } + } + + public boolean isGreaterThan(int v) { + synchronized (this.mutex) { + return this.value > v; + } + } + + public boolean isGreaterThanOrEqual(int v) { + synchronized (this.mutex) { + return this.value >= v; + } + } + + public boolean isLessThan(int v) { + synchronized (this.mutex) { + return this.value < v; + } + } + + public boolean isLessThanOrEqual(int v) { + synchronized (this.mutex) { + return this.value <= v; + } + } + + public boolean isPositive() { + return this.isGreaterThan(0); + } + + public boolean isNotPositive() { + return this.isLessThanOrEqual(0); + } + + public boolean isNegative() { + return this.isLessThan(0); + } + + public boolean isNotNegative() { + return this.isGreaterThanOrEqual(0); + } + + public int abs() { + synchronized (this.mutex) { + return Math.abs(this.value); + } + } + + public int neg() { + synchronized (this.mutex) { + return -this.value; + } + } + + public int add(int v) { + synchronized (this.mutex) { + return this.value + v; + } + } + + public int subtract(int v) { + synchronized (this.mutex) { + return this.value - v; + } + } + + public int multiply(int v) { + synchronized (this.mutex) { + return this.value * v; + } + } + + public int divide(int v) { + synchronized (this.mutex) { + return this.value / v; + } + } + + public int remainder(int v) { + synchronized (this.mutex) { + return this.value % v; + } + } + + public int min(int v) { + synchronized (this.mutex) { + return Math.min(this.value, v); + } + } + + public int max(int v) { + synchronized (this.mutex) { + return Math.max(this.value, v); + } + } + + public double pow(int v) { + synchronized (this.mutex) { + return Math.pow(this.value, v); + } + } + + /** + * If the value changes, all waiting threads are notified. + */ + public int setValue(int value) { + synchronized (this.mutex) { + return this.setValue_(value); + } + } + + /** + * Pre-condition: synchronized + */ + private int setValue_(int v) { + int old = this.value; + return (old == v) ? old : this.setValue_(v, old); + } + + /** + * Pre-condition: synchronized and new value is different + */ + private int setChangedValue_(int v) { + return this.setValue_(v, this.value); + } + + /** + * Pre-condition: synchronized and new value is different + */ + private int setValue_(int v, int old) { + this.value = v; + this.mutex.notifyAll(); + return old; + } + + /** + * Set the value to zero. If the value changes, all waiting + * threads are notified. Return the previous value. + */ + public int setZero() { + return this.setValue(0); + } + + /** + * Increment the value by one. + * Return the new value. + */ + public int increment() { + synchronized (this.mutex) { + this.value++; + this.mutex.notifyAll(); + return this.value; + } + } + + /** + * Decrement the value by one. + * Return the new value. + */ + public int decrement() { + synchronized (this.mutex) { + this.value--; + this.mutex.notifyAll(); + return this.value; + } + } + + /** + * If the current value is the specified expected value, set it to the + * specified new value. Return the previous value. + */ + public int compareAndSwap(int expectedValue, int newValue) { + synchronized (this.mutex) { + return this.compareAndSwap_(expectedValue, newValue); + } + } + + /** + * Pre-condition: synchronized + */ + private int compareAndSwap_(int expectedValue, int newValue) { + return (this.value == expectedValue) ? this.setValue_(newValue) : this.value; + } + + /** + * Return the object the synchronized integer locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the value changes + * to the specified value. If the value is already the + * specified value, return immediately. + */ + public void waitUntilEqual(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEqual_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilEqual_(int v) throws InterruptedException { + while (this.value != v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes + * to something other than the specified value. If the + * value is already not the specified value, + * return immediately. + */ + public void waitUntilNotEqual(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilNotEqual_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilNotEqual_(int v) throws InterruptedException { + while (this.value == v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes to zero. + * If the value is already zero, return immediately. + */ + public void waitUntilZero() throws InterruptedException { + this.waitUntilEqual(0); + } + + /** + * Suspend the current thread until the value changes + * to something other than zero. + * If the value is already not zero, + * return immediately. + */ + public void waitUntilNotZero() throws InterruptedException { + this.waitUntilNotEqual(0); + } + + /** + * Suspend the current thread until the value changes + * to a value greater than the specified value. If the value is already + * greater than the specified value, return immediately. + */ + public void waitUntilGreaterThan(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilGreaterThan_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilGreaterThan_(int v) throws InterruptedException { + while (this.value <= v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes + * to a value greater than or equal to the specified value. If the value is already + * greater than or equal the specified value, return immediately. + */ + public void waitUntilGreaterThanOrEqual(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilGreaterThanOrEqual_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilGreaterThanOrEqual_(int v) throws InterruptedException { + while (this.value < v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes + * to a value less than the specified value. If the value is already + * less than the specified value, return immediately. + */ + public void waitUntilLessThan(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilLessThan_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilLessThan_(int v) throws InterruptedException { + while (this.value >= v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes + * to a value less than or equal to the specified value. If the value is already + * less than or equal the specified value, return immediately. + */ + public void waitUntilLessThanOrEqual(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilLessThanOrEqual_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilLessThanOrEqual_(int v) throws InterruptedException { + while (this.value > v) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value is positive. + * If the value is already positive, return immediately. + */ + public void waitUntilPositive() throws InterruptedException { + this.waitUntilGreaterThan(0); + } + + /** + * Suspend the current thread until the value is not positive + * (i.e. negative or zero). + * If the value is already not positive, + * return immediately. + */ + public void waitUntilNotPositive() throws InterruptedException { + this.waitUntilLessThanOrEqual(0); + } + + /** + * Suspend the current thread until the value is negative. + * If the value is already negative, return immediately. + */ + public void waitUntilNegative() throws InterruptedException { + this.waitUntilLessThan(0); + } + + /** + * Suspend the current thread until the value is not negative + * (i.e. zero or positive). + * If the value is already not negative, + * return immediately. + */ + public void waitUntilNotNegative() throws InterruptedException { + this.waitUntilGreaterThanOrEqual(0); + } + + /** + * Suspend the current thread until the value changes to + * something other than the specified value, then change + * it back to the specified value and continue executing. + * If the value is already not the specified value, set + * the value immediately. + * Return the previous value. + */ + public int waitToSetValue(int v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilNotEqual_(v); + return this.setChangedValue_(v); + } + } + + /** + * Suspend the current thread until the value changes to + * something other than zero, then change it + * back to zero and continue executing. + * If the value is already not zero, + * set the value to zero immediately. + * Return the previous value. + */ + public int waitToSetZero() throws InterruptedException { + return this.waitToSetValue(0); + } + + /** + * Suspend the current thread until the value changes to + * the specified expected value, then change it + * to the specified new value and continue executing. + * If the value is already the specified expected value, + * set the value to the specified new value immediately. + * Return the previous value. + */ + public int waitToSwap(int expectedValue, int newValue) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEqual_(expectedValue); + return this.setValue_(newValue); + } + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the value changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already the specified value, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEqual(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilEqual_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilEqual_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilEqual_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value != v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value == v); + } + + /** + * Suspend the current thread until the value changes to something + * other than the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was removed; return false if a + * time-out occurred. If the value is already not the specified + * value, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotEqual(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilNotEqual_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilNotEqual_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilNotEqual_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value == v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value != v); + } + + /** + * Suspend the current thread until the value changes + * to zero or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now zero; return false + * if a time-out occurred. If the value is already zero, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilZero(long timeout) throws InterruptedException { + return this.waitUntilEqual(0, timeout); + } + + /** + * Suspend the current thread until the value changes + * to something other than zero or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now not zero; return false + * if a time-out occurred. If the value is already not + * zero, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotZero(long timeout) throws InterruptedException { + return this.waitUntilNotEqual(0, timeout); + } + + /** + * Suspend the current thread until the value changes to a value greater + * than the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already greater than the specified value, return immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilGreaterThan(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilGreaterThan_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilGreaterThan_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilGreaterThan_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value <= v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value > v); + } + + /** + * Suspend the current thread until the value changes to a value greater + * than or equal to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already greater than or equal to the specified value, + * return immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilGreaterThanOrEqual(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilGreaterThanOrEqual_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilGreaterThanOrEqual_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilGreaterThanOrEqual_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value < v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value >= v); + } + + /** + * Suspend the current thread until the value changes to a value less + * than the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already less than the specified value, return immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilLessThan(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilLessThan_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilLessThan_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilLessThan_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value >= v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value < v); + } + + /** + * Suspend the current thread until the value changes to a value less + * than or equal to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already less than or equal to the specified value, + * return immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilLessThanOrEqual(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilLessThanOrEqual_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilLessThanOrEqual_(int v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilLessThanOrEqual_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.value > v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.value <= v); + } + + /** + * Suspend the current thread until the value is positive + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now positive; return false + * if a time-out occurred. If the value is already positive, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilPositive(long timeout) throws InterruptedException { + return this.waitUntilGreaterThan(0, timeout); + } + + /** + * Suspend the current thread until the value is not positive + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now not positive; return false + * if a time-out occurred. If the value is already not positive, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotPositive(long timeout) throws InterruptedException { + return this.waitUntilLessThanOrEqual(0, timeout); + } + + /** + * Suspend the current thread until the value is negative + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now negative; return false + * if a time-out occurred. If the value is already negative, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNegative(long timeout) throws InterruptedException { + return this.waitUntilLessThan(0, timeout); + } + + /** + * Suspend the current thread until the value is not negative + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the value is now not negative; return false + * if a time-out occurred. If the value is already not negative, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotNegative(long timeout) throws InterruptedException { + return this.waitUntilGreaterThanOrEqual(0, timeout); + } + + /** + * Suspend the current thread until the value changes to + * something other than the specified value, then change + * it back to the specified value and continue executing. + * If the value does not change to something other than the + * specified value before the time-out, simply continue executing + * without changing the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to the specified value; return false + * if a time-out occurred. + * If the value is already something other than the specified value, set + * the value immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetValue(int v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilNotEqual_(v, timeout); + if (success) { + this.setChangedValue_(v); + } + return success; + } + } + + /** + * Suspend the current thread until the value changes to something + * other than zero, then change it back to zero + * and continue executing. If the value does not change to something + * other than zero before the time-out, simply continue + * executing without changing the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to zero; return false + * if a time-out occurred. + * If the value is already something other than zero, set + * the value to zero immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetZero(long timeout) throws InterruptedException { + return this.waitToSetValue(0, timeout); + } + + /** + * Suspend the current thread until the value changes to + * the specified expected value, then change it + * to the specified new value and continue executing. + * If the value does not change to the specified expected value + * before the time-out, simply continue executing without changing + * the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to the specified new value; return + * false if a time-out occurred. + * If the value is already the specified expected value, + * set the value to the specified new value immediately. + * Return the previous value. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSwap(int expectedValue, int newValue, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEqual_(expectedValue, timeout); + if (success) { + this.setValue_(newValue); + } + return success; + } + } + + + // ********** synchronized behavior ********** + + /** + * If current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the value from another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** Comparable implementation ********** + + public int compareTo(ReadOnlyIntReference ref) { + int thisValue = this.getValue(); + int otherValue = ref.getValue(); + return (thisValue < otherValue) ? -1 : ((thisValue == otherValue) ? 0 : 1); + } + + + // ********** standard methods ********** + + @Override + public SynchronizedInt clone() { + try { + synchronized (this.mutex) { + return (SynchronizedInt) super.clone(); + } + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.getValue()) + ']'; + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedObject.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedObject.java new file mode 100644 index 0000000000..f3f2048e01 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedObject.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.ObjectReference; + +/** + * This class provides synchronized access to an object of type V. + * It also provides protocol for suspending a thread until the + * value is set to null or a non-null value, + * with optional time-outs. + * + * @parm V the type of the synchronized object's value + * @see SimpleObjectReference + */ +public class SynchronizedObject + implements ObjectReference, Cloneable, Serializable +{ + /** Backing value. */ + private V value; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Create a synchronized object with the specified initial value + * and mutex. + */ + public SynchronizedObject(V value, Object mutex) { + super(); + this.value = value; + this.mutex = mutex; + } + + /** + * Create a synchronized object with the specified initial value. + * The synchronized object itself will be the mutex. + */ + public SynchronizedObject(V value) { + super(); + this.value = value; + this.mutex = this; + } + + /** + * Create a synchronized object with an initial value of null. + * The synchronized object itself will be the mutex. + */ + public SynchronizedObject() { + this(null); + } + + + // ********** accessors ********** + + public V getValue() { + synchronized (this.mutex) { + return this.value; + } + } + + public boolean valueEquals(Object object) { + return Tools.valuesAreEqual(this.getValue(), object); + } + + public boolean valueNotEqual(Object object) { + return Tools.valuesAreDifferent(this.getValue(), object); + } + + public boolean isNull() { + synchronized (this.mutex) { + return this.value == null; + } + } + + public boolean isNotNull() { + synchronized (this.mutex) { + return this.value != null; + } + } + + /** + * Set the value. If the value changes, all waiting + * threads are notified. Return the previous value. + */ + public V setValue(V value) { + synchronized (this.mutex) { + return this.setValue_(value); + } + } + + /** + * Pre-condition: synchronized + */ + private V setValue_(V v) { + V old = this.value; + return Tools.valuesAreEqual(old, v) ? old : this.setValue_(v, old); + } + + /** + * Pre-condition: synchronized and new value is different + */ + private V setChangedValue_(V v) { + return this.setValue_(v, this.value); + } + + /** + * Pre-condition: synchronized and new value is different + */ + private V setValue_(V v, V old) { + this.value = v; + this.mutex.notifyAll(); + return old; + } + + /** + * Set the value to null. If the value changes, all waiting + * threads are notified. Return the previous value. + */ + public V setNull() { + return this.setValue(null); + } + + /** + * If the current value is the specified expected value, set it to the + * specified new value. Return the previous value. + */ + public V compareAndSwap(V expectedValue, V newValue) { + synchronized (this.mutex) { + return this.compareAndSwap_(expectedValue, newValue); + } + } + + /** + * Pre-condition: synchronized + */ + private V compareAndSwap_(V expectedValue, V newValue) { + return Tools.valuesAreEqual(this.value, expectedValue) ? this.setValue_(newValue) : this.value; + } + + /** + * Return the object the synchronized object locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the value changes + * to the specified value. If the value is already the + * specified value, return immediately. + */ + public void waitUntilValueIs(V v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIs_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilValueIs_(V v) throws InterruptedException { + while (Tools.valuesAreDifferent(this.value, v)) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes + * to something other than the specified value. If the + * value is already not the specified value, + * return immediately. + */ + public void waitUntilValueIsNot(V v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIsNot_(v); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilValueIsNot_(V v) throws InterruptedException { + while (Tools.valuesAreEqual(this.value, v)) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the value changes to null. + * If the value is already null, return immediately. + */ + public void waitUntilNull() throws InterruptedException { + this.waitUntilValueIs(null); + } + + /** + * Suspend the current thread until the value changes + * to something other than null. + * If the value is already not null, + * return immediately. + */ + public void waitUntilNotNull() throws InterruptedException { + this.waitUntilValueIsNot(null); + } + + /** + * Suspend the current thread until the value changes to + * something other than the specified value, then change + * it back to the specified value and continue executing. + * If the value is already not the specified value, set + * the value immediately. + * Return the previous value. + */ + public V waitToSetValue(V v) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIsNot_(v); + return this.setChangedValue_(v); + } + } + + /** + * Suspend the current thread until the value changes to + * something other than null, then change it + * back to null and continue executing. + * If the value is already not null, + * set the value to null immediately. + * Return the previous value. + */ + public V waitToSetNull() throws InterruptedException { + return this.waitToSetValue(null); + } + + /** + * Suspend the current thread until the value changes to + * the specified expected value, then change it + * to the specified new value and continue executing. + * If the value is already the specified expected value, + * set the value to the specified new value immediately. + * Return the previous value. + */ + public V waitToSwap(V expectedValue, V newValue) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilValueIs_(expectedValue); + return this.setValue_(newValue); + } + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the value changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. + * If the value is already the specified value, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilValueIs(V v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilValueIs_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilValueIs_(V v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilValueIs_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while (Tools.valuesAreDifferent(this.value, v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return Tools.valuesAreEqual(this.value, v); + } + + /** + * Suspend the current thread until the value changes to something + * other than the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was removed; return false if a + * time-out occurred. If the value is already not the specified + * value, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilValueIsNot(V v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilValueIsNot_(v, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilValueIsNot_(V v, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilValueIsNot_(v); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while (Tools.valuesAreEqual(this.value, v) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return Tools.valuesAreDifferent(this.value, v); + } + + /** + * Suspend the current thread until the value changes + * to null or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. If the value is already null, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNull(long timeout) throws InterruptedException { + return this.waitUntilValueIs(null, timeout); + } + + /** + * Suspend the current thread until the value changes + * to something other than null or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true + * if the specified value was achieved; return false + * if a time-out occurred. If the value is already not + * null, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotNull(long timeout) throws InterruptedException { + return this.waitUntilValueIsNot(null, timeout); + } + + /** + * Suspend the current thread until the value changes to + * something other than the specified value, then change + * it back to the specified value and continue executing. + * If the value does not change to something other than the + * specified value before the time-out, simply continue executing + * without changing the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to the specified value; return false + * if a time-out occurred. + * If the value is already something other than the specified value, set + * the value immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetValue(V v, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilValueIsNot_(v, timeout); + if (success) { + this.setChangedValue_(v); + } + return success; + } + } + + /** + * Suspend the current thread until the value changes to something + * other than null, then change it back to null + * and continue executing. If the value does not change to something + * other than null before the time-out, simply continue + * executing without changing the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to null; return false + * if a time-out occurred. + * If the value is already something other than null, set + * the value to null immediately and return true. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSetNull(long timeout) throws InterruptedException { + return this.waitToSetValue(null, timeout); + } + + /** + * Suspend the current thread until the value changes to + * the specified expected value, then change it + * to the specified new value and continue executing. + * If the value does not change to the specified expected value + * before the time-out, simply continue executing without changing + * the value. + * The time-out is specified in milliseconds. Return true + * if the value was set to the specified new value; return + * false if a time-out occurred. + * If the value is already the specified expected value, + * set the value to the specified new value immediately. + * Return the previous value. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToSwap(V expectedValue, V newValue, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilValueIs_(expectedValue, timeout); + if (success) { + this.setValue_(newValue); + } + return success; + } + } + + + // ********** synchronized behavior ********** + + /** + * If current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the value from another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** standard methods ********** + + @Override + public SynchronizedObject clone() { + try { + synchronized (this.mutex) { + @SuppressWarnings("unchecked") + SynchronizedObject clone = (SynchronizedObject) super.clone(); + return clone; + } + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return '[' + String.valueOf(this.getValue()) + ']'; + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedQueue.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedQueue.java new file mode 100644 index 0000000000..5abb326021 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedQueue.java @@ -0,0 +1,348 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Thread-safe implementation of the {@link Queue} interface. + * This also provides protocol for suspending a thread until the + * queue is empty or not empty, with optional time-outs. + */ +public class SynchronizedQueue + implements Queue, Serializable +{ + /** Backing queue. */ + private final Queue queue; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a synchronized queue that wraps the + * specified queue and locks on the specified mutex. + */ + public SynchronizedQueue(Queue queue, Object mutex) { + super(); + if (queue == null) { + throw new NullPointerException(); + } + this.queue = queue; + this.mutex = mutex; + } + + /** + * Construct a synchronized queue that wraps the + * specified queue and locks on itself. + */ + public SynchronizedQueue(Queue queue) { + super(); + if (queue == null) { + throw new NullPointerException(); + } + this.queue = queue; + this.mutex = this; + } + + /** + * Construct an empty synchronized queue that locks on the specified mutex. + */ + public SynchronizedQueue(Object mutex) { + this(new SimpleQueue(), mutex); + } + + /** + * Construct an empty synchronized queue that locks on itself. + */ + public SynchronizedQueue() { + this(new SimpleQueue()); + } + + + // ********** Queue implementation ********** + + public void enqueue(E element) { + synchronized (this.mutex) { + this.enqueue_(element); + } + } + + /** + * Pre-condition: synchronized + */ + private void enqueue_(E element) { + this.queue.enqueue(element); + this.mutex.notifyAll(); + } + + public E dequeue() { + synchronized (this.mutex) { + return this.dequeue_(); + } + } + + /** + * Pre-condition: synchronized + */ + private E dequeue_() { + E element = this.queue.dequeue(); + this.mutex.notifyAll(); + return element; + } + + public E peek() { + synchronized (this.mutex) { + return this.queue.peek(); + } + } + + public boolean isEmpty() { + synchronized (this.mutex) { + return this.queue.isEmpty(); + } + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the queue's empty status changes + * to the specified value. + */ + public void waitUntilEmptyIs(boolean empty) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(empty); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilEmptyIs_(boolean empty) throws InterruptedException { + while (this.queue.isEmpty() != empty) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the queue is empty. + */ + public void waitUntilEmpty() throws InterruptedException { + this.waitUntilEmptyIs(true); + } + + /** + * Suspend the current thread until the queue has something on it. + */ + public void waitUntilNotEmpty() throws InterruptedException { + this.waitUntilEmptyIs(false); + } + + /** + * Suspend the current thread until the queue is empty, + * then "enqueue" the specified item to the tail of the queue + * and continue executing. + */ + public void waitToEnqueue(E element) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(true); + this.enqueue_(element); + } + } + + /** + * Suspend the current thread until the queue has something on it, + * then "dequeue" an item from the head of the queue and return it. + */ + public Object waitToDequeue() throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(false); + return this.dequeue_(); + } + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the queue's empty status changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if the specified + * empty status was achieved; return false if a time-out occurred. + * If the queue's empty status is already the specified value, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmptyIs(boolean empty, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilEmptyIs_(empty, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilEmptyIs_(boolean empty, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilEmptyIs_(empty); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.queue.isEmpty() != empty) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.queue.isEmpty() == empty); + } + + /** + * Suspend the current thread until the queue is empty + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the queue is empty; return false if a time-out occurred. + * If the queue is already empty, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(true, timeout); + } + + /** + * Suspend the current thread until the queue has something on it. + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the queue is not empty; return false if a time-out occurred. + * If the queue already has something on it, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(false, timeout); + } + + /** + * Suspend the current thread until the queue is empty, + * then "enqueue" the specified item to the tail of the queue + * and continue executing. If the queue is not emptied out + * before the time-out, simply continue executing without + * "enqueueing" the item. + * The time-out is specified in milliseconds. Return true if the + * item was enqueued; return false if a time-out occurred. + * If the queue is already empty, "enqueue" the specified item and + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToEnqueue(E element, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(true, timeout); + if (success) { + this.enqueue_(element); + } + return success; + } + } + + /** + * Suspend the current thread until the queue has something on it, + * then "dequeue" an item from the head of the queue and return it. + * If the queue is empty and nothing is "enqueued" on to it before the + * time-out, throw a no such element exception. + * The time-out is specified in milliseconds. + * If the queue is not empty, "dequeue" an item and + * return it immediately. + * If the time-out is zero, wait indefinitely. + */ + public Object waitToDequeue(long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(false, timeout); + if (success) { + return this.dequeue_(); + } + throw new NoSuchElementException(); + } + } + + + // ********** synchronized behavior ********** + + /** + * If the current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the queue in another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** additional public protocol ********** + + /** + * "Drain" all the current items from the queue into specified queue. + */ + public void drainTo(Queue q) { + synchronized (this.mutex) { + this.drainTo_(q); + } + } + + /** + * Pre-condition: synchronized + */ + private void drainTo_(Queue q) { + boolean changed = false; + while ( ! this.queue.isEmpty()) { + q.enqueue(this.queue.dequeue()); + changed = true; + } + if (changed) { + this.mutex.notifyAll(); + } + } + + /** + * Return the object the queue locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** standard methods ********** + + @Override + public String toString() { + synchronized (this.mutex) { + return '[' + this.queue.toString() + ']'; + } + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedStack.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedStack.java new file mode 100644 index 0000000000..17033fc574 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedStack.java @@ -0,0 +1,325 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; +import java.util.EmptyStackException; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Thread-safe implementation of the {@link Stack} interface. + * This also provides protocol for suspending a thread until the + * stack is empty or not empty, with optional time-outs. + */ +public class SynchronizedStack + implements Stack, Serializable +{ + /** Backing stack. */ + private final Stack stack; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a synchronized stack that wraps the + * specified stack and locks on the specified mutex. + */ + public SynchronizedStack(Stack stack, Object mutex) { + super(); + if (stack == null) { + throw new NullPointerException(); + } + this.stack = stack; + this.mutex = mutex; + } + + /** + * Construct a synchronized stack that wraps the + * specified stack and locks on itself. + */ + public SynchronizedStack(Stack stack) { + super(); + if (stack == null) { + throw new NullPointerException(); + } + this.stack = stack; + this.mutex = this; + } + + /** + * Construct an empty synchronized stack that locks on the specified mutex. + */ + public SynchronizedStack(Object mutex) { + this(new SimpleStack(), mutex); + } + + /** + * Construct an empty synchronized stack that locks on itself. + */ + public SynchronizedStack() { + this(new SimpleStack()); + } + + + // ********** Stack implementation ********** + + public void push(E element) { + synchronized (this.mutex) { + this.push_(element); + } + } + + /** + * Pre-condition: synchronized + */ + private void push_(E element) { + this.stack.push(element); + this.mutex.notifyAll(); + } + + public E pop() { + synchronized (this.mutex) { + return this.pop_(); + } + } + + /** + * Pre-condition: synchronized + */ + private E pop_() { + E o = this.stack.pop(); + this.mutex.notifyAll(); + return o; + } + + public E peek() { + synchronized (this.mutex) { + return this.stack.peek(); + } + } + + public boolean isEmpty() { + synchronized (this.mutex) { + return this.stack.isEmpty(); + } + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the stack's empty status changes + * to the specified value. + */ + public void waitUntilEmptyIs(boolean empty) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(empty); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilEmptyIs_(boolean empty) throws InterruptedException { + while (this.stack.isEmpty() != empty) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the stack is empty. + */ + public void waitUntilEmpty() throws InterruptedException { + this.waitUntilEmptyIs(true); + } + + /** + * Suspend the current thread until the stack has something on it. + */ + public void waitUntilNotEmpty() throws InterruptedException { + this.waitUntilEmptyIs(false); + } + + /** + * Suspend the current thread until the stack is empty, + * then "push" the specified item on to the top of the stack + * and continue executing. + */ + public void waitToPush(E element) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(true); + this.push_(element); + } + } + + /** + * Suspend the current thread until the stack has something on it, + * then "pop" an item from the top of the stack and return it. + */ + public Object waitToPop() throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(false); + return this.pop_(); + } + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the stack's empty status changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if the specified + * empty status was achieved; return false if a time-out occurred. + * If the stack's empty status is already the specified value, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmptyIs(boolean empty, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilEmptyIs_(empty, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilEmptyIs_(boolean empty, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilEmptyIs_(empty); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.stack.isEmpty() != empty) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.stack.isEmpty() == empty); + } + + /** + * Suspend the current thread until the stack is empty + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the stack is empty; return false if a time-out occurred. + * If the stack is already empty, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(true, timeout); + } + + /** + * Suspend the current thread until the stack has something on it. + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the stack is not empty; return false if a time-out occurred. + * If the stack already has something on it, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(false, timeout); + } + + /** + * Suspend the current thread until the stack is empty, + * then "push" the specified item on to the top of the stack + * and continue executing. If the stack is not emptied out + * before the time-out, simply continue executing without + * "pushing" the item. + * The time-out is specified in milliseconds. Return true if the + * item was pushed; return false if a time-out occurred. + * If the stack is already empty, "push" the specified item and + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToPush(E element, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(true, timeout); + if (success) { + this.push_(element); + } + return success; + } + } + + /** + * Suspend the current thread until the stack has something on it, + * then "pop" an item from the top of the stack and return it. + * If the stack is empty and nothing is "pushed" on to it before the + * time-out, throw an empty stack exception. + * The time-out is specified in milliseconds. + * If the stack is not empty, "pop" an item and + * return it immediately. + * If the time-out is zero, wait indefinitely. + */ + public Object waitToPop(long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(false, timeout); + if (success) { + return this.pop_(); + } + throw new EmptyStackException(); + } + } + + + // ********** synchronized behavior ********** + + /** + * If the current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the stack in another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** additional public protocol ********** + + /** + * Return the object the stack locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** standard methods ********** + + @Override + public String toString() { + synchronized (this.mutex) { + return '[' + this.stack.toString() + ']'; + } + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommand.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommand.java new file mode 100644 index 0000000000..02168fc1b6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommand.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; + +/** + * This implementation of the Command interface allows the client to + * specify a different Command for each thread. + */ +public class ThreadLocalCommand implements Command { + protected final ThreadLocal threadLocal; + protected final Command defaultCommand; + + /** + * The default command does nothing. + */ + public ThreadLocalCommand() { + this(Command.Null.instance()); + } + + public ThreadLocalCommand(Command defaultCommand) { + super(); + this.defaultCommand = defaultCommand; + this.threadLocal = this.buildThreadLocal(); + } + + protected ThreadLocal buildThreadLocal() { + return new ThreadLocal(); + } + + public void execute() { + this.get().execute(); + } + + protected Command get() { + Command command = this.threadLocal.get(); + return (command != null) ? command : this.defaultCommand; + } + + /** + * Set the current thread's command to the specified value. + */ + public void set(Command command) { + this.threadLocal.set(command); + } + + /** + * Return the string representation of the current thread's command. + */ + @Override + public String toString() { + return this.get().toString(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommandExecutor.java new file mode 100644 index 0000000000..e8e5064614 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommandExecutor.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.CommandExecutor; + +/** + * This implementation of the CommandExecutor interface allows the client to + * specify a different Command Executor for each thread. + */ +public class ThreadLocalCommandExecutor implements CommandExecutor { + protected final ThreadLocal threadLocal; + protected final CommandExecutor defaultCommandExecutor; + + /** + * The default command executor simply executes the command directly. + */ + public ThreadLocalCommandExecutor() { + this(CommandExecutor.Default.instance()); + } + + public ThreadLocalCommandExecutor(CommandExecutor defaultCommandExecutor) { + super(); + this.defaultCommandExecutor = defaultCommandExecutor; + this.threadLocal = this.buildThreadLocal(); + } + + protected ThreadLocal buildThreadLocal() { + return new ThreadLocal(); + } + + public void execute(Command command) { + this.getThreadLocalCommandExecutor().execute(command); + } + + protected CommandExecutor getThreadLocalCommandExecutor() { + CommandExecutor ce = this.threadLocal.get(); + return (ce != null) ? ce : this.defaultCommandExecutor; + } + + /** + * Set the current thread's command executor to the specified value. + */ + public void set(CommandExecutor commandExecutor) { + this.threadLocal.set(commandExecutor); + } + + /** + * Return the string representation of the current thread's command + * executor. + */ + @Override + public String toString() { + return this.getThreadLocalCommandExecutor().toString(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Tools.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Tools.java new file mode 100644 index 0000000000..e3c238dc4d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Tools.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +/** + * Various utility methods. + */ +@SuppressWarnings("nls") +public final class Tools { + + // ********** object comparison ********** + + /** + * Return whether the specified values are equal, with the appropriate + * null checks. + */ + public static boolean valuesAreEqual(Object value1, Object value2) { + return (value1 == null) ? + (value2 == null) : + ((value2 != null) && value1.equals(value2)); + } + + /** + * Return whether the specified values are different, with the appropriate + * null checks. + */ + public static boolean valuesAreDifferent(Object value1, Object value2) { + return (value1 == null) ? + (value2 != null) : + ((value2 == null) || ! value1.equals(value2)); + } + + + // ********** System properties ********** + + /** + * Return whether the current JVM is Sun's (or Oracle's). + */ + public static boolean jvmIsSun() { + return jvmIs("Sun") || jvmIs("Oracle"); + } + + /** + * Return whether the current JVM is IBM's. + */ + public static boolean jvmIsIBM() { + return jvmIs("IBM"); + } + + private static boolean jvmIs(String jvmVendorName) { + return System.getProperty("java.vendor").startsWith(jvmVendorName); + } + + /** + * Return whether the current operating system is Microsoft Windows. + */ + public static boolean osIsWindows() { + return osIs("Windows"); + } + + /** + * Return whether the current operating system is Linux. + */ + public static boolean osIsLinux() { + return osIs("Linux"); + } + + private static boolean osIs(String osName) { + return System.getProperty("os.name").indexOf(osName) != -1; + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private Tools() { + super(); + throw new UnsupportedOperationException(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Transformer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Transformer.java new file mode 100644 index 0000000000..d95534e737 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Transformer.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.Serializable; + +/** + * Used by various "pluggable" classes to transform objects. + * Transform an object of type T1 to an object of type + * T2. + * + * @param the type of the object passed to the transformer + * @param the type of the object returned by the transformer + */ +public interface Transformer { + + /** + * Return the transformed object. + * The semantics of "transform" is determined by the + * contract between the client and the server. + */ + T2 transform(T1 o); + + + /** + * A "null" transformer will perform no transformation at all; + * it will simply return the object "untransformed". + */ + final class Null implements Transformer, Serializable { + @SuppressWarnings("rawtypes") + public static final Transformer INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static Transformer instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // simply return the object, unchanged + @SuppressWarnings("unchecked") + public S2 transform(S1 o) { + return (S2) o; + } + @Override + public String toString() { + return "Transformer.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * A "disabled" transformer will throw an exception if + * {@link #transform(Object)} is called. This is useful in situations + * where a transformer is optional and the default transformer should + * not be used. + */ + final class Disabled implements Transformer, Serializable { + @SuppressWarnings("rawtypes") + public static final Transformer INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static Transformer instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public S2 transform(S1 o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "Transformer.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/XMLStringEncoder.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/XMLStringEncoder.java new file mode 100644 index 0000000000..baf7d30684 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/XMLStringEncoder.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +/** + * This encoder will replace any of a specified set of characters with an XML + * "character reference": '/' => "/" + */ +public final class XMLStringEncoder { + + /** The set of characters to be converted into XML character references. */ + private final char[] chars; + + /** Cache the value of the highest character in the set above. */ + private final char maxChar; + + + // ********** constructors/initialization ********** + + /** + * Construct an encoder that converts the specified set of characters + * into XML character references. + */ + public XMLStringEncoder(char[] chars) { + super(); + if (chars == null) { + throw new NullPointerException(); + } + // the ampersand must be included since it is the escape character + if (ArrayTools.contains(chars, '&')) { + this.chars = chars; + } else { + this.chars = ArrayTools.add(chars, '&'); + } + this.maxChar = this.calculateMaxInvalidFileNameChar(); + } + + /** + * Calculate the maximum value of the set of characters to be converted + * into XML character references. This will be used to short-circuit the + * search for a character in the set. + * @see #charIsToBeEncoded(char) + */ + private char calculateMaxInvalidFileNameChar() { + char[] localChars = this.chars; + char max = 0; + for (int i = localChars.length; i-- > 0; ) { + char c = localChars[i]; + if (max < c) { + max = c; + } + } + return max; + } + + + // ********** API ********** + + /** + * Return the specified string with any characters in the set + * replaced with XML character references. + */ + public String encode(String s) { + int len = s.length(); + // allow for a few encoded characters + StringBuilder sb = new StringBuilder(len + 20); + for (int i = 0; i < len; i++) { + this.appendCharacterTo(s.charAt(i), sb); + } + return sb.toString(); + } + + /** + * Return the specified string with any XML character references + * replaced by the characters themselves. + */ + public String decode(String s) { + StringBuilder sb = new StringBuilder(s.length()); + StringBuilder temp = new StringBuilder(); // performance tweak + this.decodeTo(new StringReader(s), sb, temp); + return sb.toString(); + } + + + // ********** internal methods ********** + + /** + * Append the specified character to the string buffer, + * converting it to an XML character reference if necessary. + */ + private void appendCharacterTo(char c, StringBuilder sb) { + if (this.charIsToBeEncoded(c)) { + this.appendCharacterReferenceTo(c, sb); + } else { + sb.append(c); + } + } + + /** + * Return whether the specified character is one of the characters + * to be converted to XML character references. + */ + private boolean charIsToBeEncoded(char c) { + return (c <= this.maxChar) && ArrayTools.contains(this.chars, c); + } + + /** + * Append the specified character's XML character reference to the + * specified string buffer (e.g. '/' => "/"). + */ + private void appendCharacterReferenceTo(char c, StringBuilder sb) { + sb.append("&#x"); //$NON-NLS-1$ + sb.append(Integer.toString(c, 16)); + sb.append(';'); + } + + private void decodeTo(Reader reader, StringBuilder sb, StringBuilder temp) { + try { + this.decodeTo_(reader, sb, temp); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private void decodeTo_(Reader reader, StringBuilder sb, StringBuilder temp) throws IOException { + int c = reader.read(); + while (c != -1) { + if (c == '&') { + this.decodeCharacterReferenceTo(reader, sb, temp); + } else { + sb.append((char) c); + } + c = reader.read(); + } + reader.close(); + } + + private void decodeCharacterReferenceTo(Reader reader, StringBuilder sb, StringBuilder temp) throws IOException { + int c = reader.read(); + this.checkChar(c, '#'); + c = reader.read(); + this.checkChar(c, 'x'); + + temp.setLength(0); // re-use temp + c = reader.read(); + while (c != ';') { + this.checkEndOfStream(c); + temp.append((char) c); + c = reader.read(); + } + String charValue = temp.toString(); + if (charValue.length() == 0) { + throw new IllegalStateException("missing numeric string"); //$NON-NLS-1$ + } + sb.append((char) Integer.parseInt(charValue, 16)); + } + + private void checkChar(int c, int expected) { + this.checkEndOfStream(c); + if (c != expected) { + throw new IllegalStateException("expected '" + (char) expected + "', but encountered '" + (char) c + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + + private void checkEndOfStream(int c) { + if (c == -1) { + throw new IllegalStateException("unexpected end of string"); //$NON-NLS-1$ + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/EmptyEnumeration.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/EmptyEnumeration.java new file mode 100644 index 0000000000..7cd0c023b4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/EmptyEnumeration.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.enumerations; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * An EmptyEnumeration is just that. + * + * @param the type of elements returned by the enumeration + */ +public final class EmptyEnumeration + implements Enumeration +{ + + // singleton + @SuppressWarnings("rawtypes") + private static final EmptyEnumeration INSTANCE = new EmptyEnumeration(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static Enumeration instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private EmptyEnumeration() { + super(); + } + + public boolean hasMoreElements() { + return false; + } + + public E nextElement() { + throw new NoSuchElementException(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/IteratorEnumeration.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/IteratorEnumeration.java new file mode 100644 index 0000000000..ee6000c172 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/IteratorEnumeration.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.enumerations; + +import java.util.Enumeration; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * An IteratorEnumeration wraps an + * {@link Iterator} so that it can be treated like an + * {@link Enumeration}. + * Hopefully we don't have much need for this.... + * + * @param the type of elements returned by the enumeration + */ +public class IteratorEnumeration + implements Enumeration +{ + private final Iterator iterator; + + /** + * Construct an enumeration that wraps the specified iterable. + */ + public IteratorEnumeration(Iterable iterable) { + this(iterable.iterator()); + } + + /** + * Construct an enumeration that wraps the specified iterator. + */ + public IteratorEnumeration(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasMoreElements() { + return this.iterator.hasNext(); + } + + public E nextElement() { + return this.iterator.next(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayIterable.java new file mode 100644 index 0000000000..5f67e72a49 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayIterable.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Arrays; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; + +/** + * An ArrayIterable provides an {@link Iterable} + * for an array of objects of type E. + * + * @param the type of elements returned by the iterable's iterator + * + * @see ArrayIterator + * @see ArrayListIterable + */ +public class ArrayIterable + implements Iterable +{ + final E[] array; + final int start; + final int length; + + /** + * Construct an iterable for the specified array. + */ + public ArrayIterable(E... array) { + this(array, 0, array.length); + } + + /** + * Construct an iterable for the specified array, + * starting at the specified start index and continuing for + * the rest of the array. + */ + public ArrayIterable(E[] array, int start) { + this(array, start, array.length - start); + } + + /** + * Construct an iterable for the specified array, + * starting at the specified start index and continuing for + * the specified length. + */ + public ArrayIterable(E[] array, int start, int length) { + super(); + if ((start < 0) || (start > array.length)) { + throw new IllegalArgumentException("start: " + start); //$NON-NLS-1$ + } + if ((length < 0) || (length > array.length - start)) { + throw new IllegalArgumentException("length: " + length); //$NON-NLS-1$ + } + this.array = array; + this.start = start; + this.length = length; + } + + public Iterator iterator() { + return new ArrayIterator(this.array, this.start, this.length); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, Arrays.toString(this.array)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayListIterable.java new file mode 100644 index 0000000000..78ceb0e23d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayListIterable.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.iterators.ArrayListIterator; + +/** + * An ArrayListIterable provides a {@link ListIterable} + * for an array of objects of type E. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see ArrayIterable + * @see ArrayListIterator + */ +public class ArrayListIterable + extends ArrayIterable + implements ListIterable +{ + /** + * Construct a list iterable for the specified array. + */ + public ArrayListIterable(E... array) { + this(array, 0, array.length); + } + + /** + * Construct a list iterable for the specified array, + * starting at the specified start index and continuing for + * the rest of the array. + */ + public ArrayListIterable(E[] array, int start) { + this(array, start, array.length - start); + } + + /** + * Construct a list iterable for the specified array, + * starting at the specified start index and continuing for + * the specified length. + */ + public ArrayListIterable(E[] array, int start, int length) { + super(array, start, length); + } + + @Override + public ListIterator iterator() { + return new ArrayListIterator(this.array, this.start, this.length); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ChainIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ChainIterable.java new file mode 100644 index 0000000000..12dcc76816 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ChainIterable.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ChainIterator; + +/** + * A ChainIterable provides a pluggable {@link Iterable} + * that loops over a chain of arbitrarily linked objects. The chain + * should be null-terminated (i.e. a call to the {@link #nextLink(Object)} + * method should return null when it is passed the last + * link of the chain). + * To use, supply a starting link and supply a {@link ChainIterator.Linker} or + * subclass ChainIterable and override the + * {@link #nextLink(Object)} method. + * The starting link will be the first object returned by the iterable's iterator. + * If the starting link is null, the iterable will be empty. + * Note this iterable does not support null elements. + * + * @param the type of elements returned by the iterable's iterator + * + * @see ChainIterator + */ +public class ChainIterable + implements Iterable +{ + private final E startLink; + private final ChainIterator.Linker linker; + + + /** + * Construct an iterable with the specified starting link + * and a default linker that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #nextLink(Object)} method instead of building + * a {@link ChainIterator.Linker}. + */ + public ChainIterable(E startLink) { + super(); + this.startLink = startLink; + this.linker = this.buildDefaultLinker(); + } + + /** + * Construct an iterator with the specified starting link + * and linker. + */ + public ChainIterable(E startLink, ChainIterator.Linker linker) { + super(); + this.startLink = startLink; + this.linker = linker; + } + + protected ChainIterator.Linker buildDefaultLinker() { + return new DefaultLinker(); + } + + public Iterator iterator() { + return new ChainIterator(this.startLink, this.linker); + } + + /** + * Return the next link in the chain; null if there are no more links. + *

+ * This method can be overridden by a subclass as an alternative to + * building a {@link ChainIterator.Linker}. + */ + protected E nextLink(@SuppressWarnings("unused") E currentLink) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.startLink); + } + + + //********** default linker ********** + + protected class DefaultLinker implements ChainIterator.Linker { + public E nextLink(E currentLink) { + return ChainIterable.this.nextLink(currentLink); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneIterable.java new file mode 100644 index 0000000000..22a21a4979 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneIterable.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import org.eclipse.jpt.common.utility.internal.iterators.CloneIterator; + +/** + * Pull together remover state and behavior for subclasses. + * + * @param the type of elements returned by the iterable's iterator + * + * @see SnapshotCloneIterable + * @see LiveCloneIterable + */ +public abstract class CloneIterable + implements Iterable +{ + final CloneIterator.Remover remover; + + + // ********** constructors ********** + + protected CloneIterable() { + super(); + this.remover = this.buildDefaultRemover(); + } + + protected CloneIterable(CloneIterator.Remover remover) { + super(); + this.remover = remover; + } + + protected CloneIterator.Remover buildDefaultRemover() { + return new DefaultRemover(); + } + + + // ********** default removal ********** + + /** + * Remove the specified element from the original collection. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link CloneIterator.Remover}. + */ + protected void remove(@SuppressWarnings("unused") E element) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + + //********** default mutator ********** + + protected class DefaultRemover implements CloneIterator.Remover { + public void remove(E element) { + CloneIterable.this.remove(element); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneListIterable.java new file mode 100644 index 0000000000..b98c42303a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneListIterable.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import org.eclipse.jpt.common.utility.internal.iterators.CloneListIterator; + +/** + * Pull together mutator state and behavior for subclasses. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see SnapshotCloneListIterable + * @see LiveCloneListIterable + */ +public abstract class CloneListIterable + implements ListIterable +{ + final CloneListIterator.Mutator mutator; + + + // ********** constructors ********** + + protected CloneListIterable() { + super(); + this.mutator = this.buildDefaultMutator(); + } + + protected CloneListIterable(CloneListIterator.Mutator mutator) { + super(); + this.mutator = mutator; + } + + protected CloneListIterator.Mutator buildDefaultMutator() { + return new DefaultMutator(); + } + + + // ********** default mutations ********** + + /** + * At the specified index, add the specified element to the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link CloneListIterator.Mutator}. + */ + protected void add(@SuppressWarnings("unused") int index, @SuppressWarnings("unused") E element) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * Remove the element at the specified index from the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link CloneListIterator.Mutator}. + */ + protected void remove(@SuppressWarnings("unused") int index) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * At the specified index, set the specified element in the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link CloneListIterator.Mutator}. + */ + protected void set(@SuppressWarnings("unused") int index, @SuppressWarnings("unused") E element) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + + //********** default mutator ********** + + protected class DefaultMutator implements CloneListIterator.Mutator { + public void add(int index, E element) { + CloneListIterable.this.add(index, element); + } + public void remove(int index) { + CloneListIterable.this.remove(index); + } + public void set(int index, E element) { + CloneListIterable.this.set(index, element); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeIterable.java new file mode 100644 index 0000000000..442737ede9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeIterable.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CompositeIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; + +/** + * A CompositeIterable wraps an {@link Iterable} + * of {@link Iterable}s and makes them appear to be a single + * {@link Iterable}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see CompositeIterator + * @see CompositeListIterable + */ +public class CompositeIterable + implements Iterable +{ + private final Iterable> iterables; + + + /** + * Construct an iterable with the specified collection of iterables. + */ + public CompositeIterable(Iterable> iterables) { + super(); + this.iterables = iterables; + } + + /** + * Construct an iterable with the specified object prepended + * to the specified iterable. + */ + @SuppressWarnings("unchecked") + public CompositeIterable(E object, Iterable iterable) { + this(new SingleElementIterable(object), iterable); + } + + /** + * Construct an iterable with the specified object appended + * to the specified iterable. + */ + @SuppressWarnings("unchecked") + public CompositeIterable(Iterable iterable, E object) { + this(iterable, new SingleElementIterable(object)); + } + + /** + * Construct an iterable with the specified iterables. + */ + public CompositeIterable(Iterable... iterables) { + this(new ArrayIterable>(iterables)); + } + + /** + * combined iterators + */ + public Iterator iterator() { + return new CompositeIterator(this.iterators()); + } + + /** + * iterator of iterators + */ + protected Iterator> iterators() { + return new TransformationIterator, Iterator>(this.iterables()) { + @Override + protected Iterator transform(Iterable next) { + return next.iterator(); + } + }; + } + + /** + * iterator of iterables + */ + protected Iterator> iterables() { + return this.iterables.iterator(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterables); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeListIterable.java new file mode 100644 index 0000000000..66f75626ef --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeListIterable.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CompositeListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationListIterator; + +/** + * A CompositeListIterable wraps a {@link ListIterable} + * of {@link ListIterable}s and makes them appear to be a single + * {@link ListIterable}. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see CompositeListIterator + * @see CompositeIterable + * @see ReadOnlyCompositeListIterable + */ +public class CompositeListIterable + implements ListIterable +{ + private final ListIterable> iterables; + + + /** + * Construct a list iterable with the specified list of list iterables. + */ + public CompositeListIterable(List> iterables) { + this(new ListListIterable>(iterables)); + } + + /** + * Construct a list iterable with the specified list of list iterables. + */ + public CompositeListIterable(ListIterable> iterables) { + super(); + this.iterables = iterables; + } + + /** + * Construct a list iterable with the specified object prepended + * to the specified list. + */ + public CompositeListIterable(E object, List list) { + this(object, new ListListIterable(list)); + } + + /** + * Construct a list iterable with the specified object prepended + * to the specified list iterable. + */ + @SuppressWarnings("unchecked") + public CompositeListIterable(E object, ListIterable iterable) { + this(new SingleElementListIterable(object), iterable); + } + + /** + * Construct a list iterable with the specified object appended + * to the specified list. + */ + public CompositeListIterable(List list, E object) { + this(new ListListIterable(list), object); + } + + /** + * Construct a list iterable with the specified object appended + * to the specified list iterable. + */ + @SuppressWarnings("unchecked") + public CompositeListIterable(ListIterable iterable, E object) { + this(iterable, new SingleElementListIterable(object)); + } + + /** + * Construct a list iterable with the specified list iterables. + */ + public CompositeListIterable(ListIterable... iterables) { + this(new ArrayListIterable>(iterables)); + } + + /** + * Construct a list iterable with the specified lists. + */ + public CompositeListIterable(List... lists) { + this(new TransformationListIterable, ListIterable>(new ArrayListIterable>(lists)) { + @Override + protected ListIterable transform(List list) { + return new ListListIterable(list); + } + }); + } + + /** + * combined list iterators + */ + public ListIterator iterator() { + return new CompositeListIterator(this.iterators()); + } + + /** + * list iterator of list iterators + */ + protected ListIterator> iterators() { + return new TransformationListIterator, ListIterator>(this.iterables()) { + @Override + protected ListIterator transform(ListIterable next) { + return next.iterator(); + } + }; + } + + /** + * list iterator of list iterables + */ + protected ListIterator> iterables() { + return this.iterables.iterator(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterables); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyIterable.java new file mode 100644 index 0000000000..c3ed1c49dc --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyIterable.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.io.Serializable; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; + +/** + * An EmptyIterable is just that. + * Maybe just a touch better-performing than {@link java.util.Collections#EMPTY_SET} + * since we don't create a new {@link Iterator} every time {@link #iterator()} is called. + * (Not sure why they do that....) + * + * @param the type of elements returned by the iterable's iterator + * + * @see EmptyIterator + * @see EmptyListIterable + */ +public final class EmptyIterable + implements Iterable, Serializable +{ + // singleton + @SuppressWarnings("rawtypes") + private static final Iterable INSTANCE = new EmptyIterable(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static Iterable instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private EmptyIterable() { + super(); + } + + public Iterator iterator() { + return EmptyIterator.instance(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyListIterable.java new file mode 100644 index 0000000000..eb6613c071 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyListIterable.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.io.Serializable; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.iterators.EmptyListIterator; + +/** + * An EmptyListIterable is just that. + * Maybe just a touch better-performing than {@link java.util.Collections#EMPTY_LIST} + * since we don't create a new {@link Iterator} every time {@link #iterator()} is called. + * (Not sure why they do that....) + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see EmptyListIterator + * @see EmptyIterable + */ +public final class EmptyListIterable + implements ListIterable, Serializable +{ + // singleton + @SuppressWarnings("rawtypes") + private static final ListIterable INSTANCE = new EmptyListIterable(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static ListIterable instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private EmptyListIterable() { + super(); + } + + public ListIterator iterator() { + return EmptyListIterator.instance(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/FilteringIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/FilteringIterable.java new file mode 100644 index 0000000000..897f0d908e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/FilteringIterable.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.Filter; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.FilteringIterator; + +/** + * A FilteringIterable wraps another {@link Iterable} + * and uses a {@link Filter} to determine which elements in the + * nested iterable are to be returned by the iterable's iterator. + *

+ * As an alternative to building a {@link Filter}, a subclass + * of FilteringIterable can override the + * {@link #accept(Object)} method. + * + * @param the type of elements to be filtered + * + * @see FilteringIterator + */ +public class FilteringIterable + implements Iterable +{ + private final Iterable iterable; + private final Filter filter; + + + /** + * Construct an iterable with the specified nested + * iterable and a default filter that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #accept(Object)} method instead of building + * a {@link Filter}. + */ + public FilteringIterable(Iterable iterable) { + super(); + this.iterable = iterable; + this.filter = this.buildDefaultFilter(); + } + + /** + * Construct an iterable with the specified nested + * iterable and filter. + */ + public FilteringIterable(Iterable iterable, Filter filter) { + super(); + this.iterable = iterable; + this.filter = filter; + } + + protected Filter buildDefaultFilter() { + return new DefaultFilter(); + } + + public Iterator iterator() { + return new FilteringIterator(this.iterable.iterator(), this.filter); + } + + /** + * Return whether the iterable's iterator + * should return the specified next element from a call to the + * {@link Iterator#next()} method. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Filter}. + */ + protected boolean accept(@SuppressWarnings("unused") E o) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + + + //********** default filter ********** + + protected class DefaultFilter implements Filter { + public boolean accept(E o) { + return FilteringIterable.this.accept(o); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/GraphIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/GraphIterable.java new file mode 100644 index 0000000000..01912a47b5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/GraphIterable.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Arrays; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.GraphIterator; + +/** + * A GraphIterable is similar to a {@link TreeIterable} + * except that it cannot be assumed that all nodes assume a strict tree + * structure. For instance, in a tree, a node cannot be a descendent of + * itself, but a graph may have a cyclical structure. + * + * A GraphIterable simplifies the traversal of a + * graph of objects, where the objects' protocol(s) provides + * a method for getting the next collection of nodes in the graph, + * (or neighbors), but does not provide a method for + * getting all of the nodes in the graph. + * (e.g. a neighbor can return his neighbors, and those neighbors + * can return their neighbors, which might also include the original + * neighbor, but you only want to visit the original neighbor once.) + *

+ * If a neighbor has already been visited (determined by using + * {@link #equals(Object)}), that neighbor is not visited again, + * nor are the neighbors of that object. + *

+ * It is up to the user of this class to ensure a complete graph. + *

+ * To use, supply:

    + *
  • either the initial node of the graph or an {@link Iterable} + * of the initial collection of graph nodes + *
  • a {@link GraphIterator.MisterRogers} that tells who the neighbors are + * of each node + * (alternatively, subclass GraphIterable + * and override the {@link #neighbors(Object)} method) + *
+ * The {@link Iterator#remove()} operation is not supported. This behavior, if + * desired, must be implemented by the user of this class. + * + * @param the type of elements returned by the iterable's iterator + * + * @see GraphIterator + */ +public class GraphIterable + implements Iterable +{ + private final Iterable roots; + private final GraphIterator.MisterRogers misterRogers; + + + /** + * Construct an iterable containing the nodes of a graph with the specified root + * and a default Mr. Rogers that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link GraphIterator.MisterRogers}. + */ + public GraphIterable(E root) { + this(new SingleElementIterable(root)); + } + + /** + * Construct an iterable containing the nodes of a graph + * with the specified root and Mr. Rogers. + */ + public GraphIterable(E root, GraphIterator.MisterRogers misterRogers) { + this(new SingleElementIterable(root), misterRogers); + } + + /** + * Construct an iterable containing the nodes of a graph + * with the specified collection of roots + * and a default Mr. Rogers that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link GraphIterator.MisterRogers}. + */ + public GraphIterable(E... roots) { + this(Arrays.asList(roots)); + } + + /** + * Construct an iterable containing the nodes of a graph + * with the specified roots and Mr. Rogers. + */ + public GraphIterable(E[] roots, GraphIterator.MisterRogers misterRogers) { + this(Arrays.asList(roots), misterRogers); + } + + /** + * Construct an iterable containing the nodes of a graph + * with the specified collection of roots + * and a default Mr. Rogers that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link GraphIterator.MisterRogers}. + */ + public GraphIterable(Iterable roots) { + super(); + this.roots = roots; + this.misterRogers = this.buildDefaultMisterRogers(); + } + + /** + * Construct an iterable containing the nodes of a graph + * with the specified roots and Mr. Rogers. + */ + public GraphIterable(Iterable roots, GraphIterator.MisterRogers misterRogers) { + super(); + this.roots = roots; + this.misterRogers = misterRogers; + } + + protected GraphIterator.MisterRogers buildDefaultMisterRogers() { + return new DefaultMisterRogers(); + } + + public Iterator iterator() { + return new GraphIterator(this.roots, this.misterRogers); + } + + /** + * Return the immediate neighbors of the specified object. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link GraphIterator.MisterRogers}. + */ + protected Iterator neighbors(@SuppressWarnings("unused") E next) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.roots); + } + + + //********** default Mr. Rogers ********** + + protected class DefaultMisterRogers implements GraphIterator.MisterRogers { + public Iterator neighbors(E node) { + return GraphIterable.this.neighbors(node); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListIterable.java new file mode 100644 index 0000000000..91bab82950 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListIterable.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.ListIterator; + +/** + * A ListIterable simply extends {@link Iterable} + * to return a {@link ListIterator} of type E. + * + * @param the type of elements returned by the iterable's iterators + */ +public interface ListIterable + extends Iterable +{ + /** + * Return a list iterator over a set of elements of type E. + */ + ListIterator iterator(); +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListListIterable.java new file mode 100644 index 0000000000..ad5a5d7926 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListListIterable.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +/** + * A ListListIterable adapts a {@link List} + * to the {@link ListIterable} interface. + * + * @param the type of elements returned by the iterable's iterators + */ +public class ListListIterable + implements ListIterable +{ + private final List list; + + public ListListIterable(List list) { + super(); + this.list = list; + } + + public ListIterator iterator() { + return this.list.listIterator(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneIterable.java new file mode 100644 index 0000000000..e8aaccff06 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneIterable.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Collection; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CloneIterator; + +/** + * A LiveCloneIterable returns an iterator on a current copy of a + * collection, allowing for concurrent access to the original collection. A + * copy of the collection is created every time {@link #iterator()} is + * called. As a result, the contents of the collection can be different with + * each call to {@link #iterator()} (i.e. it is "live"). + *

+ * The original collection passed to the LiveCloneIterable's + * constructor should be thread-safe (e.g. {@link java.util.Vector}); + * otherwise you run the risk of a corrupted collection. + *

+ * By default, the iterator returned by a LiveCloneIterable does not + * support the {@link Iterator#remove()} operation; this is because it does not + * have access to the original collection. But if the LiveCloneIterable + * is supplied with an {@link CloneIterator.Remover} it will delegate the + * {@link Iterator#remove()} operation to the Remover. + * Alternatively, a subclass can override the iterable's {@link #remove(Object)} + * method. + * + * @param the type of elements returned by the iterable's iterator + * + * @see CloneIterator + * @see SnapshotCloneIterable + * @see LiveCloneListIterable + */ +public class LiveCloneIterable + extends CloneIterable +{ + private final Collection collection; + + + // ********** constructors ********** + + /** + * Construct a "live" iterable for the specified collection. + * The {@link Iterator#remove()} operation will not be supported + * by the iterator returned by {@link #iterator()} + * unless a subclass overrides the iterable's {@link #remove(Object)} + * method. + */ + public LiveCloneIterable(Collection collection) { + super(); + this.collection = collection; + } + + /** + * Construct a "live" iterable for the specified collection. + * The specified remover will be used by any generated iterators to + * remove objects from the original collection. + */ + public LiveCloneIterable(Collection collection, CloneIterator.Remover remover) { + super(remover); + this.collection = collection; + } + + + // ********** Iterable implementation ********** + + public Iterator iterator() { + return new CloneIterator(this.collection, this.remover); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.collection); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneListIterable.java new file mode 100644 index 0000000000..9d26554db1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneListIterable.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CloneListIterator; + +/** + * A LiveCloneListIterable returns a list iterator on a current + * copy of a list, allowing for concurrent access to the original list. A + * copy of the list is created every time {@link #iterator()} is + * called. As a result, the contents of the list can be different with + * each call to {@link #iterator()} (i.e. it is "live"). + *

+ * The original list passed to the LiveCloneListIterable's + * constructor should be thread-safe (e.g. {@link java.util.Vector}); + * otherwise you run the risk of a corrupted list. + *

+ * By default, the list iterator returned by a LiveCloneListIterable + * does not support the modify operations; this is because it does not + * have access to the original list. But if the LiveCloneListIterable + * is supplied with an {@link CloneListIterator.Mutator} it will delegate the + * modify operations to the Mutator. + * Alternatively, a subclass can override the list iterable's mutation + * methods. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see CloneListIterator + * @see SnapshotCloneListIterable + * @see LiveCloneIterable + */ +public class LiveCloneListIterable + extends CloneListIterable +{ + private final List list; + + + // ********** constructors ********** + + /** + * Construct a "live" list iterable for the specified list. + * The {@link ListIterator} mutation operations will not be supported + * by the list iterator returned by {@link #iterator()} + * unless a subclass overrides the iterable's mutation + * methods. + */ + public LiveCloneListIterable(List list) { + super(); + this.list = list; + } + + /** + * Construct a "live" list iterable for the specified list. + * The specified mutator will be used by any generated list iterators to + * modify the original list. + */ + public LiveCloneListIterable(List list, CloneListIterator.Mutator mutator) { + super(mutator); + this.list = list; + } + + + // ********** ListIterable implementation ********** + + public ListIterator iterator() { + return new CloneListIterator(this.list, this.mutator); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.list); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/PeekableIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/PeekableIterable.java new file mode 100644 index 0000000000..ad381ee41c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/PeekableIterable.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.PeekableIterator; + +/** + * A PeekableIterable wraps another {@link Iterable} + * and returns an {@link PeekableIterator} that allows a + * {@link PeekableIterator#peek() peek} at the next element to be + * returned by {@link Iterator#next()}. + *

+ * One, possibly undesirable, side-effect of using this iterator is that + * the nested iterator's next() method will be invoked + * before the peekable iterator's {@link Iterator#next()} + * method is invoked. This is because the "next" element must be + * pre-loaded for the {@link PeekableIterator#peek()} method. + * This also prevents a peekable iterator from supporting the optional + * {@link Iterator#remove()} method. + * + * @param the type of elements returned by the iterable's iterator + * + * @see PeekableIterator + */ +public class PeekableIterable + implements Iterable +{ + private final Iterable iterable; + + /** + * Construct a peekable iterable that wraps the specified + * iterable. + */ + public PeekableIterable(Iterable iterable) { + super(); + this.iterable = iterable; + } + + public PeekableIterator iterator() { + return new PeekableIterator(this.iterable); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/QueueIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/QueueIterable.java new file mode 100644 index 0000000000..5b2aa6d0b4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/QueueIterable.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.Queue; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.QueueIterator; + +/** + * A QueueIterable provides an {@link Iterable} + * for a {@link Queue} of objects of type E. The queue's elements + * are {@link Queue#dequeue() dequeue}d" as the iterable's iterator returns + * them with calls to {@link Iterator#next()}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see Queue + * @see QueueIterator + */ +public class QueueIterable + implements Iterable +{ + private final Queue queue; + + /** + * Construct an iterable for the specified queue. + */ + public QueueIterable(Queue queue) { + super(); + this.queue = queue; + } + + public Iterator iterator() { + return new QueueIterator(this.queue); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.queue); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyCompositeListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyCompositeListIterable.java new file mode 100644 index 0000000000..ff2810fdda --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyCompositeListIterable.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyCompositeListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationListIterator; + +/** + * A ReadOnlyCompositeListIterable wraps a {@link ListIterable} + * of {@link ListIterable}s and makes them appear to be a single + * read-only {@link ListIterable}. A read-only composite list + * iterable is more flexible than a normal composite list iterable when it + * comes to the element types of the nested list iterables. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see ReadOnlyCompositeListIterator + * @see CompositeListIterable + */ +public class ReadOnlyCompositeListIterable + implements ListIterable +{ + private final ListIterable> iterables; + + + /** + * Construct a list iterable with the specified list of list iterables. + */ + public ReadOnlyCompositeListIterable(ListIterable> iterables) { + super(); + this.iterables = iterables; + } + + /** + * Construct a list iterable with the specified object prepended + * to the specified list iterable. + */ + @SuppressWarnings("unchecked") + public ReadOnlyCompositeListIterable(E object, ListIterable iterable) { + this(new SingleElementListIterable(object), iterable); + } + + /** + * Construct a list iterable with the specified object appended + * to the specified list iterable. + */ + @SuppressWarnings("unchecked") + public ReadOnlyCompositeListIterable(ListIterable iterable, E object) { + this(iterable, new SingleElementListIterable(object)); + } + + /** + * Construct a list iterable with the specified list iterables. + */ + public ReadOnlyCompositeListIterable(ListIterable... iterables) { + this(new ArrayListIterable>(iterables)); + } + + /** + * combined list iterators + */ + public ListIterator iterator() { + return new ReadOnlyCompositeListIterator(this.iterators()); + } + + /** + * list iterator of list iterators + */ + protected ListIterator> iterators() { + return new TransformationListIterator, ListIterator>(this.iterables()) { + @Override + protected ListIterator transform(ListIterable next) { + return next.iterator(); + } + }; + } + + /** + * list iterator of list iterables + */ + protected ListIterator> iterables() { + return this.iterables.iterator(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterables); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyIterable.java new file mode 100644 index 0000000000..0483928be6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyIterable.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyIterator; + +/** + * A ReadOnlyIterable wraps another {@link Iterable} + * and returns a read-only {@link Iterator}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see ReadOnlyIterator + * @see ReadOnlyListIterable + */ +public class ReadOnlyIterable + implements Iterable +{ + private final Iterable iterable; + + + /** + * Construct an iterable the returns a read-only iterator on the elements + * in the specified iterable. + */ + public ReadOnlyIterable(Iterable iterable) { + super(); + this.iterable = iterable; + } + + public Iterator iterator() { + return new ReadOnlyIterator(this.iterable); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyListIterable.java new file mode 100644 index 0000000000..1efde41a59 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyListIterable.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; + +/** + * A ReadOnlyListIterable wraps another {@link ListIterable} + * and returns a read-only {@link ListIterator}. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see ReadOnlyListIterator + * @see ReadOnlyIterable + */ +public class ReadOnlyListIterable + implements ListIterable +{ + private final ListIterable listIterable; + + + /** + * Construct a list iterable the returns a read-only list iterator on the elements + * in the specified list iterable. + */ + public ReadOnlyListIterable(ListIterable iterable) { + super(); + this.listIterable = iterable; + } + + public ListIterator iterator() { + return new ReadOnlyListIterator(this.listIterable); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listIterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementIterable.java new file mode 100644 index 0000000000..34ea3a7e24 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementIterable.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementIterator; + +/** + * A SingleElementIterable returns an {@link Iterator} + * that holds a single element + * and returns it with the first call to {@link Iterator#next()}, at + * which point it will return false to any subsequent + * call to {@link Iterator#hasNext()}. + *

+ * A SingleElementIterable is equivalent to the + * {@link Iterable} returned by: + * {@link java.util.Collections#singleton(Object)}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see SingleElementIterator + * @see SingleElementListIterable + */ +public class SingleElementIterable + implements Iterable +{ + private final E element; + + /** + * Construct an iterable that contains only the specified element. + */ + public SingleElementIterable(E element) { + super(); + this.element = element; + } + + public Iterator iterator() { + return new SingleElementIterator(this.element); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.element); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementListIterable.java new file mode 100644 index 0000000000..c6e2100367 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementListIterable.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementListIterator; + +/** + * A SingleElementListIterable returns a {@link ListIterator} + * that holds a single element + * and returns it with the first call to {@link ListIterator#next()}, at + * which point it will return false to any subsequent + * call to {@link ListIterator#hasNext()}. Likewise, it will return false + * to a call to {@link ListIterator#hasPrevious()} until a call to {@link ListIterator#next()}, + * at which point a call to {@link ListIterator#previous()} will return the + * single element. + *

+ * A SingleElementListIterable is equivalent to the + * {@link Iterable} returned by: + * {@link java.util.Collections#singletonList(Object)}. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see SingleElementListIterator + * @see SingleElementIterable + */ +public class SingleElementListIterable + implements ListIterable +{ + private final E element; + + /** + * Construct a list iterable that contains only the specified element. + */ + public SingleElementListIterable(E element) { + super(); + this.element = element; + } + + public ListIterator iterator() { + return new SingleElementListIterator(this.element); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.element); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneIterable.java new file mode 100644 index 0000000000..72beebec70 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneIterable.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CloneIterator; + +/** + * A SnapshotCloneIterable returns an iterator on a "snapshot" of a + * collection, allowing for concurrent access to the original collection. A + * copy of the collection is created when the iterable is constructed. + * As a result, the contents of the collection will be the same with + * every call to {@link #iterator()}. + *

+ * The original collection passed to the SnapshotCloneIterable's + * constructor should be thread-safe (e.g. {@link java.util.Vector}); + * otherwise you run the risk of a corrupted collection. + *

+ * By default, the iterator returned by a SnapshotCloneIterable does not + * support the {@link Iterator#remove()} operation; this is because it does not + * have access to the original collection. But if the SnapshotCloneIterable + * is supplied with a {@link CloneIterator.Remover} it will delegate the + * {@link Iterator#remove()} operation to the Remover. + * Alternatively, a subclass can override the iterable's {@link #remove(Object)} + * method. + *

+ * This iterable is useful for multiple passes over a collection that should not + * be changed (e.g. by another thread) between passes. + * + * @param the type of elements returned by the iterable's iterator + * + * @see CloneIterator + * @see LiveCloneIterable + * @see SnapshotCloneListIterable + */ +public class SnapshotCloneIterable + extends CloneIterable +{ + private final Object[] array; + + + // ********** constructors ********** + + /** + * Construct a "snapshot" iterable for the specified iterator. + * The {@link Iterator#remove()} operation will not be supported + * by the iterator returned by {@link #iterator()} + * unless a subclass overrides the iterable's {@link #remove(Object)} + * method. + */ + public SnapshotCloneIterable(Iterator iterator) { + super(); + this.array = ArrayTools.array(iterator); + } + + /** + * Construct a "snapshot" iterable for the specified iterator. + * The specified remover will be used by any generated iterators to + * remove objects from the original collection. + */ + public SnapshotCloneIterable(Iterator iterator, CloneIterator.Remover remover) { + super(remover); + this.array = ArrayTools.array(iterator); + } + + /** + * Construct a "snapshot" iterable for the specified collection. + * The {@link Iterator#remove()} operation will not be supported + * by the iterator returned by {@link #iterator()} + * unless a subclass overrides the iterable's {@link #remove(Object)} + * method. + */ + public SnapshotCloneIterable(Collection collection) { + super(); + this.array = collection.toArray(); + } + + /** + * Construct a "snapshot" iterable for the specified collection. + * The specified remover will be used by any generated iterators to + * remove objects from the original collection. + */ + public SnapshotCloneIterable(Collection collection, CloneIterator.Remover remover) { + super(remover); + this.array = collection.toArray(); + } + + + // ********** Iterable implementation ********** + + public Iterator iterator() { + return new LocalCloneIterator(this.remover, this.array); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, Arrays.toString(this.array)); + } + + + // ********** clone iterator ********** + + /** + * provide access to "internal" constructor + */ + protected static class LocalCloneIterator extends CloneIterator { + protected LocalCloneIterator(Remover remover, Object[] array) { + super(remover, array); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneListIterable.java new file mode 100644 index 0000000000..f242ab70f5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneListIterable.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.CloneListIterator; + +/** + * A SnapshotCloneListIterable returns a list iterator on a + * "snapshot" of a list, allowing for concurrent access to the original list. + * A copy of the list is created when the list iterable is constructed. + * As a result, the contents of the list will be the same with + * every call to {@link #iterator()}, even if the original list is modified via + * the list iterator's mutation methods. + *

+ * The original list passed to the SnapshotCloneListIterable's + * constructor should be thread-safe (e.g. {@link java.util.Vector}); + * otherwise you run the risk of a corrupted list. + *

+ * By default, the list iterator returned by a SnapshotCloneListIterable does not + * support the {@link ListIterator} mutation operations; this is because it does not + * have access to the original list. But if the SnapshotCloneListIterable + * is supplied with a {@link CloneListIterator.Mutator} it will delegate the + * {@link ListIterator} mutation operations to the Mutator. + * Alternatively, a subclass can override the list iterable's mutation + * methods. + *

+ * This list iterable is useful for multiple passes over a list that should not + * be changed (e.g. by another thread) between passes. + * + * @param the type of elements returned by the list iterable's list iterator + * + * @see CloneListIterator + * @see LiveCloneListIterable + * @see SnapshotCloneIterable + */ +public class SnapshotCloneListIterable + extends CloneListIterable +{ + private final Object[] array; + + + // ********** constructors ********** + + /** + * Construct a "snapshot" list iterable for the specified list. + * The {@link ListIterator} modify operations will not be supported + * by the list iterator returned by {@link #iterator()} + * unless a subclass overrides the list iterable's modify + * method. + */ + public SnapshotCloneListIterable(List list) { + super(); + this.array = list.toArray(); + } + + /** + * Construct a "snapshot" list iterable for the specified list. + * The specified mutator will be used by any generated list iterators to + * modify the original list. + */ + public SnapshotCloneListIterable(List list, CloneListIterator.Mutator mutator) { + super(mutator); + this.array = list.toArray(); + } + + + // ********** ListIterable implementation ********** + + public ListIterator iterator() { + return new LocalCloneListIterator(this.mutator, this.array); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, Arrays.toString(this.array)); + } + + + // ********** clone iterator ********** + + /** + * provide access to "internal" constructor + */ + protected static class LocalCloneListIterator extends CloneListIterator { + protected LocalCloneListIterator(Mutator mutator, Object[] array) { + super(mutator, array); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/StackIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/StackIterable.java new file mode 100644 index 0000000000..211fe3f61a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/StackIterable.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.Stack; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.StackIterator; + +/** + * A StackIterable provides an {@link Iterable} + * for a {@link Stack} of objects of type E. The stack's elements + * are {@link Stack#pop() "popped"} as the iterable's iterator returns + * them with calls to {@link Iterator#next()}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see Stack + * @see StackIterator + */ +public class StackIterable + implements Iterable +{ + private final Stack stack; + + /** + * Construct an iterable for the specified stack. + */ + public StackIterable(Stack stack) { + super(); + this.stack = stack; + } + + public Iterator iterator() { + return new StackIterator(this.stack); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.stack); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubIterableWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubIterableWrapper.java new file mode 100644 index 0000000000..c6108d5c22 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubIterableWrapper.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.SubIteratorWrapper; + +/** + * Wrap an iterable of elements of type E1, converting it into an + * iterable of elements of type E2. Assume the wrapped + * iterable contains only elements of type E2. + * + * @param input: the type of elements contained by the wrapped iterable + * @param output: the type of elements returned by the iterable's iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterators.SubIteratorWrapper + */ +public class SubIterableWrapper + implements Iterable +{ + private final Iterable iterable; + + + public SubIterableWrapper(Iterable iterable) { + super(); + this.iterable = iterable; + } + + public Iterator iterator() { + return new SubIteratorWrapper(this.iterable.iterator()); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubListIterableWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubListIterableWrapper.java new file mode 100644 index 0000000000..2213ce0559 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubListIterableWrapper.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.SubListIteratorWrapper; + +/** + * Wrap a list iterable of elements of type E1, converting it into + * a list iterable of elements of type E2. Assume the wrapped + * iterable contains only elements of type E2. + * + * @param input: the type of elements contained by the wrapped list iterable + * @param output: the type of elements returned by the iterable's list iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterators.SubListIteratorWrapper + */ +public class SubListIterableWrapper + implements ListIterable +{ + private final ListIterable iterable; + + + public SubListIterableWrapper(List list) { + this(new ListListIterable(list)); + } + + public SubListIterableWrapper(ListIterable iterable) { + super(); + this.iterable = iterable; + } + + public ListIterator iterator() { + return new SubListIteratorWrapper(this.iterable.iterator()); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperIterableWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperIterableWrapper.java new file mode 100644 index 0000000000..bae5e1e52a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperIterableWrapper.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * Wrap an iterable of elements of any sub-type of E, converting it into an + * iterable of elements of type E. This shouldn't be a problem since there + * is no way to add invalid elements to the iterable. + * + * @param the type of elements returned by the iterable's iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterators.SuperIteratorWrapper + */ +public class SuperIterableWrapper + implements Iterable +{ + private final Iterable iterable; + + + @SuppressWarnings("unchecked") + public SuperIterableWrapper(Iterable iterable) { + super(); + // this should be a safe cast - the iterator will only ever + // return E (or a sub-type) from #next() + this.iterable = (Iterable) iterable; + } + + public Iterator iterator() { + return this.iterable.iterator(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperListIterableWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperListIterableWrapper.java new file mode 100644 index 0000000000..2d5fa24f03 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperListIterableWrapper.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.SuperListIteratorWrapper; + +/** + * Wrap a list iterable of elements of any sub-type of E, + * converting it into a list iterable of elements of type E. + * This shouldn't be a problem since the resulting list iterable's list + * iterator disables the methods that would put invalid elements in the list + * iterator's backing list (i.e. {@link SuperListIteratorWrapper#set(Object)} + * and {@link SuperListIteratorWrapper#add(Object)}). + * + * @param the type of elements returned by the iterable's iterators + * + * @see org.eclipse.jpt.common.utility.internal.iterators.SuperListIteratorWrapper + */ +public class SuperListIterableWrapper + implements ListIterable +{ + private final ListIterable iterable; + + + public SuperListIterableWrapper(List list) { + this(new ListListIterable(list)); + } + + public SuperListIterableWrapper(ListIterable iterable) { + super(); + this.iterable = iterable; + } + + public ListIterator iterator() { + return new SuperListIteratorWrapper(this.iterable.iterator()); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationIterable.java new file mode 100644 index 0000000000..124c15d07d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationIterable.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; + +/** + * A TransformationIterable wraps another {@link Iterable} + * and transforms its elements for client consumption. To use, supply a + * {@link Transformer} or subclass TransformationIterable + * and override the {@link #transform(Object)} method. + * Objects of type E1 are transformed into objects of type E2; + * i.e. the iterable's iterator returns objects of type E2. + * + * @param input: the type of elements to be transformed + * @param output: the type of elements returned by the iterable's iterator + * + * @see TransformationIterator + * @see TransformationListIterable + */ +public class TransformationIterable + implements Iterable +{ + private final Iterable iterable; + private final Transformer transformer; + + + /** + * Construct an iterable with the specified nested iterable + * and a default transformer that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationIterable(Iterable iterable) { + super(); + this.iterable = iterable; + this.transformer = this.buildDefaultTransformer(); + } + + /** + * Construct an iterable with the specified nested iterable + * and transformer. + */ + public TransformationIterable(Iterable iterable, Transformer transformer) { + super(); + this.iterable = iterable; + this.transformer = transformer; + } + + protected Transformer buildDefaultTransformer() { + return new DefaultTransformer(); + } + + public Iterator iterator() { + return new TransformationIterator(this.iterable.iterator(), this.transformer); + } + + /** + * Transform the specified object and return the result. + */ + protected E2 transform(@SuppressWarnings("unused") E1 o) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + + + //********** default linker ********** + + protected class DefaultTransformer implements Transformer { + public E2 transform(E1 o) { + return TransformationIterable.this.transform(o); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationListIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationListIterable.java new file mode 100644 index 0000000000..0f2d058f57 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationListIterable.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationListIterator; + +/** + * A TransformationListIterable wraps another {@link ListIterable} + * and transforms its elements for client consumption. To use, supply a + * {@link Transformer} or subclass TransformationListIterable + * and override the {@link #transform(Object)} method. + * Objects of type E1 are transformed into objects of type E2; + * i.e. the list iterable's list iterator returns objects of type E2. + * + * @param input: the type of elements to be transformed + * @param output: the type of elements returned by the iterable's iterator + * + * @see TransformationListIterator + * @see TransformationIterable + */ +public class TransformationListIterable + implements ListIterable +{ + private final ListIterable iterable; + private final Transformer transformer; + + + /** + * Construct a list iterable with the specified nested list + * and a default transformer that calls back to the list iterable. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationListIterable(List list) { + this(new ListListIterable(list)); + } + + /** + * Construct a list iterable with the specified nested list iterable + * and a default transformer that calls back to the list iterable. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationListIterable(ListIterable iterable) { + super(); + this.iterable = iterable; + this.transformer = this.buildDefaultTransformer(); + } + + /** + * Construct a list iterable with the specified nested list + * and transformer. + */ + public TransformationListIterable(List list, Transformer transformer) { + this(new ListListIterable(list), transformer); + } + + /** + * Construct a list iterable with the specified nested list iterable + * and transformer. + */ + public TransformationListIterable(ListIterable iterable, Transformer transformer) { + super(); + this.iterable = iterable; + this.transformer = transformer; + } + + protected Transformer buildDefaultTransformer() { + return new DefaultTransformer(); + } + + public ListIterator iterator() { + return new TransformationListIterator(this.iterable.iterator(), this.transformer); + } + + /** + * Transform the specified object and return the result. + */ + protected E2 transform(@SuppressWarnings("unused") E1 o) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterable); + } + + + //********** default linker ********** + + protected class DefaultTransformer implements Transformer { + public E2 transform(E1 o) { + return TransformationListIterable.this.transform(o); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TreeIterable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TreeIterable.java new file mode 100644 index 0000000000..8e3e1ff336 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TreeIterable.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterables; + +import java.util.Arrays; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.TreeIterator; + +/** + * A TreeIterable simplifies the traversal of a + * tree of objects, where the objects' protocol(s) provides + * a method for getting the immediate children of the given + * node but does not provide a method for getting all the + * descendants (children, grandchildren, etc.) of the given node. + *

+ * To use, supply:

    + *
  • either the root element of the tree or, if the tree has + * multiple roots, an {@link Iterable} of the set of roots + *
  • a {@link TreeIterator.Midwife} that delivers the children of each child + * (alternatively, subclass TreeIterable + * and override the {@link #children(Object)} method) + *
+ * + * @param the type of elements returned by the iterable's iterator + * + * @see TreeIterator + */ +public class TreeIterable + implements Iterable +{ + private final Iterable roots; + private final TreeIterator.Midwife midwife; + + + /** + * Construct an iterable containing the nodes of a tree with the specified root + * and a default midwife that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link TreeIterator.Midwife}. + */ + public TreeIterable(E root) { + this(new SingleElementIterable(root)); + } + + /** + * Construct an iterable containing the nodes of a tree with the specified root + * and midwife. + */ + public TreeIterable(E root, TreeIterator.Midwife midwife) { + this(new SingleElementIterable(root), midwife); + } + + /** + * Construct an iterable containing the nodes of a tree with the specified roots + * and a default midwife that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link TreeIterator.Midwife}. + */ + public TreeIterable(E... roots) { + this(Arrays.asList(roots)); + } + + /** + * Construct an iterable containing the nodes of a tree with the specified roots + * and midwife. + */ + public TreeIterable(E[] roots, TreeIterator.Midwife midwife) { + this(Arrays.asList(roots), midwife); + } + + /** + * Construct an iterable containing the nodes of a tree with the specified roots + * and a default midwife that calls back to the iterable. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link TreeIterator.Midwife}. + */ + public TreeIterable(Iterable roots) { + super(); + this.roots = roots; + this.midwife = this.buildDefaultMidwife(); + } + + /** + * Construct an iterable containing the nodes of a tree with the specified roots + * and midwife. + */ + public TreeIterable(Iterable roots, TreeIterator.Midwife midwife) { + super(); + this.roots = roots; + this.midwife = midwife; + } + + protected TreeIterator.Midwife buildDefaultMidwife() { + return new DefaultMidwife(); + } + + public Iterator iterator() { + return new TreeIterator(this.roots, this.midwife); + } + + /** + * Return the immediate children of the specified object. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link TreeIterator.Midwife}. + */ + protected Iterator children(@SuppressWarnings("unused") E next) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.roots); + } + + + //********** default midwife ********** + + protected class DefaultMidwife implements TreeIterator.Midwife { + public Iterator children(E node) { + return TreeIterable.this.children(node); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayIterator.java new file mode 100644 index 0000000000..4b0e277339 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayIterator.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * An ArrayIterator provides an {@link Iterator} + * for an array of objects of type E. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable + */ +public class ArrayIterator + implements Iterator +{ + final E[] array; // private-protected + int cursor; // private-protected + private final int max; + + + /** + * Construct an iterator for the specified array. + */ + public ArrayIterator(E... array) { + this(array, 0, array.length); + } + + /** + * Construct an iterator for the specified array, + * starting at the specified start index and continuing for + * the rest of the array. + */ + public ArrayIterator(E[] array, int start) { + this(array, start, array.length - start); + } + + /** + * Construct an iterator for the specified array, + * starting at the specified start index and continuing for + * the specified length. + */ + public ArrayIterator(E[] array, int start, int length) { + super(); + if ((start < 0) || (start > array.length)) { + throw new IllegalArgumentException("start: " + start); //$NON-NLS-1$ + } + if ((length < 0) || (length > array.length - start)) { + throw new IllegalArgumentException("length: " + length); //$NON-NLS-1$ + } + this.array = array; + this.cursor = start; + this.max = start + length; + } + + public boolean hasNext() { + return this.cursor != this.max; + } + + public E next() { + if (this.hasNext()) { + return this.array[this.cursor++]; + } + throw new NoSuchElementException(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, Arrays.toString(this.array)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayListIterator.java new file mode 100644 index 0000000000..b08cfe9092 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayListIterator.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * An ArrayListIterator provides a {@link ListIterator} + * for an array of objects. + *

+ * The name might be a bit confusing: + * This is a {@link ListIterator} for an Array; + * not an {@link java.util.Iterator Iterator} for an + * {@link java.util.ArrayList ArrayList}. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ArrayListIterable + */ +public class ArrayListIterator + extends ArrayIterator + implements ListIterator +{ + private final int min; + + + /** + * Construct a list iterator for the specified array. + */ + public ArrayListIterator(E... array) { + this(array, 0, array.length); + } + + /** + * Construct a list iterator for the specified array, + * starting at the specified start index and continuing for + * the rest of the array. + */ + public ArrayListIterator(E[] array, int start) { + this(array, start, array.length - start); + } + + /** + * Construct a list iterator for the specified array, + * starting at the specified start index and continuing for + * the specified length. + */ + public ArrayListIterator(E[] array, int start, int length) { + super(array, start, length); + this.min = start; + } + + public int nextIndex() { + return this.cursor; + } + + public int previousIndex() { + return this.cursor - 1; + } + + public boolean hasPrevious() { + return this.cursor != this.min; + } + + public E previous() { + if (this.hasPrevious()) { + return this.array[--this.cursor]; + } + throw new NoSuchElementException(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ChainIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ChainIterator.java new file mode 100644 index 0000000000..baf6710cb7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ChainIterator.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A ChainIterator provides a pluggable {@link Iterator} + * that loops over a chain of arbitrarily linked objects. The chain + * should be null-terminated (i.e. a call to the {@link #nextLink(Object)} + * method should return null when it is passed the last + * link of the chain). + * To use, supply a starting link and supply a {@link Linker} or + * subclass ChainIterator and override the + * {@link #nextLink(Object)} method. + * The starting link will be the first object returned by the iterator. + * If the starting link is null, the iterator will be empty. + * Note this iterator does not support null elements. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ChainIterable + */ +public class ChainIterator + implements Iterator +{ + private E nextLink; + private final Linker linker; + + + /** + * Construct an iterator with the specified starting link + * and a disabled linker. + * Use this constructor if you want to override the + * {@link #nextLink(Object)} method instead of building + * a {@link Linker}. + */ + public ChainIterator(E startLink) { + this(startLink, Linker.Disabled.instance()); + } + + /** + * Construct an iterator with the specified starting link + * and linker. + */ + public ChainIterator(E startLink, Linker linker) { + super(); + this.nextLink = startLink; + this.linker = linker; + } + + public boolean hasNext() { + return this.nextLink != null; + } + + public E next() { + if (this.nextLink == null) { + throw new NoSuchElementException(); + } + E result = this.nextLink; + this.nextLink = this.nextLink(this.nextLink); + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Return the next link in the chain; null if there are no more links. + */ + protected E nextLink(E currentLink) { + return this.linker.nextLink(currentLink); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.nextLink); + } + + + //********** member interface ********** + + /** + * Used by {@link ChainIterator} to link + * the elements in the chain. + */ + public interface Linker { + + /** + * Return the next link in the chain; null if there are no more links. + */ + T nextLink(T currentLink); + + + final class Null implements Linker { + @SuppressWarnings("rawtypes") + public static final Linker INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static Linker instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // simply return null, indicating the chain is ended + public S nextLink(S currentLink) { + return null; + } + @Override + public String toString() { + return "ChainIterator.Linker.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + final class Disabled implements Linker { + @SuppressWarnings("rawtypes") + public static final Linker INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static Linker instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public S nextLink(S currentLink) { + throw new UnsupportedOperationException(); // ChainIterator.nextLink(Object) was not implemented + } + @Override + public String toString() { + return "ChainIterator.Linker.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneIterator.java new file mode 100644 index 0000000000..8e31ddea73 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneIterator.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A CloneIterator iterates over a copy of a collection, + * allowing for concurrent access to the original collection. + *

+ * The original collection passed to the CloneIterator's + * constructor should be synchronized (e.g. {@link java.util.Vector}); + * otherwise you run the risk of a corrupted collection. + *

+ * By default, a CloneIterator does not support the + * {@link #remove()} operation; this is because it does not have + * access to the original collection. But if the CloneIterator + * is supplied with an {@link Remover} it will delegate the + * {@link #remove()} operation to the {@link Remover}. + * Alternatively, a subclass can override the {@link #remove(Object)} + * method. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.LiveCloneIterable + * @see org.eclipse.jpt.common.utility.internal.iterables.SnapshotCloneIterable + */ +public class CloneIterator + implements Iterator +{ + private final Iterator iterator; + private E current; + private final Remover remover; + private boolean removeAllowed; + + + // ********** constructors ********** + + /** + * Construct an iterator on a copy of the specified collection. + * The {@link #remove()} method will not be supported, + * unless a subclass overrides the {@link #remove(Object)}. + */ + public CloneIterator(Collection collection) { + this(collection, Remover.ReadOnly.instance()); + } + + /** + * Construct an iterator on a copy of the specified array. + * The {@link #remove()} method will not be supported, + * unless a subclass overrides the {@link #remove(Object)}. + */ + public CloneIterator(E[] array) { + this(array, Remover.ReadOnly.instance()); + } + + /** + * Construct an iterator on a copy of the specified collection. + * Use the specified remover to remove objects from the + * original collection. + */ + public CloneIterator(Collection collection, Remover remover) { + this(remover, collection.toArray()); + } + + /** + * Construct an iterator on a copy of the specified array. + * Use the specified remover to remove objects from the + * original array. + */ + public CloneIterator(E[] array, Remover remover) { + this(remover, array.clone()); + } + + /** + * Internal constructor used by subclasses. + * Swap order of arguments to prevent collision with other constructor. + * The passed in array will *not* be cloned. + */ + protected CloneIterator(Remover remover, Object... array) { + super(); + this.iterator = new ArrayIterator(array); + this.current = null; + this.remover = remover; + this.removeAllowed = false; + } + + + // ********** Iterator implementation ********** + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public E next() { + this.current = this.nestedNext(); + this.removeAllowed = true; + return this.current; + } + + public void remove() { + if ( ! this.removeAllowed) { + throw new IllegalStateException(); + } + this.remove(this.current); + this.removeAllowed = false; + } + + + // ********** internal methods ********** + + /** + * The collection passed in during construction held elements of type E, + * so this cast is not a problem. We need this cast because + * all the elements of the original collection were copied into + * an object array (Object[]). + */ + @SuppressWarnings("unchecked") + protected E nestedNext() { + return (E) this.iterator.next(); + } + + /** + * Remove the specified element from the original collection. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Remover}. + */ + protected void remove(E e) { + this.remover.remove(e); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + + //********** member interface ********** + + /** + * Used by {@link CloneIterator} to remove + * elements from the original collection; since the iterator + * does not have direct access to the original collection. + */ + public interface Remover { + + /** + * Remove the specified object from the original collection. + */ + void remove(T element); + + + final class ReadOnly implements Remover { + @SuppressWarnings("rawtypes") + public static final Remover INSTANCE = new ReadOnly(); + @SuppressWarnings("unchecked") + public static Remover instance() { + return INSTANCE; + } + // ensure single instance + private ReadOnly() { + super(); + } + // remove is not supported + public void remove(Object element) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "CloneIterator.Remover.ReadOnly"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneListIterator.java new file mode 100644 index 0000000000..91536a0794 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneListIterator.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A CloneListIterator iterates over a copy of a list, + * allowing for concurrent access to the original list. + *

+ * The original list passed to the CloneListIterator's + * constructor should be synchronized; otherwise you run the risk of + * a corrupted list (e.g. {@link java.util.Vector}. + *

+ * By default, a CloneListIterator does not support the + * modification operations; this is because it does not have + * access to the original list. But if the CloneListIterator + * is supplied with a {@link Mutator} it will delegate the + * modification operations to the {@link Mutator}. + * Alternatively, a subclass can override the modification methods. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.LiveCloneListIterable + * @see org.eclipse.jpt.common.utility.internal.iterables.SnapshotCloneListIterable + */ +public class CloneListIterator + implements ListIterator +{ + private final ListIterator listIterator; + private int cursor; + private State state; + private final Mutator mutator; + + private enum State { + UNKNOWN, + PREVIOUS, + NEXT + } + + + // ********** constructors ********** + + /** + * Construct a list iterator on a copy of the specified list. + * The modification methods will not be supported, + * unless a subclass overrides them. + */ + public CloneListIterator(List list) { + this(list, Mutator.ReadOnly.instance()); + } + + /** + * Construct a list iterator on a copy of the specified array. + * The modification methods will not be supported, + * unless a subclass overrides them. + */ + public CloneListIterator(E[] array) { + this(array, Mutator.ReadOnly.instance()); + } + + /** + * Construct a list iterator on a copy of the specified list. + * Use the specified list mutator to modify the original list. + */ + public CloneListIterator(List list, Mutator mutator) { + this(mutator, list.toArray()); + } + + /** + * Construct a list iterator on a copy of the specified array. + * Use the specified list mutator to modify the original list. + */ + public CloneListIterator(E[] array, Mutator mutator) { + this(mutator, array.clone()); + } + + /** + * Internal constructor used by subclasses. + * Swap order of arguments to prevent collision with other constructor. + * The passed in array will *not* be cloned. + */ + protected CloneListIterator(Mutator mutator, Object... array) { + super(); + // build a copy of the list and keep it in synch with original (if the mutator allows changes) + // that way the nested list iterator will maintain some of our state + this.listIterator = CollectionTools.list(array).listIterator(); + this.mutator = mutator; + this.cursor = 0; + this.state = State.UNKNOWN; + } + + + // ********** ListIterator implementation ********** + + public boolean hasNext() { + return this.listIterator.hasNext(); + } + + public E next() { + // allow the nested iterator to throw an exception before we modify the index + E next = this.nestedNext(); + this.cursor++; + this.state = State.NEXT; + return next; + } + + public void remove() { + // allow the nested iterator to throw an exception before we modify the original list + this.listIterator.remove(); + if (this.state == State.PREVIOUS) { + this.remove(this.cursor); + } else { + this.cursor--; + this.remove(this.cursor); + } + } + + public int nextIndex() { + return this.listIterator.nextIndex(); + } + + public int previousIndex() { + return this.listIterator.previousIndex(); + } + + public boolean hasPrevious() { + return this.listIterator.hasPrevious(); + } + + public E previous() { + // allow the nested iterator to throw an exception before we modify the index + E previous = this.nestedPrevious(); + this.cursor--; + this.state = State.PREVIOUS; + return previous; + } + + public void add(E o) { + // allow the nested iterator to throw an exception before we modify the original list + this.listIterator.add(o); + this.add(this.cursor, o); + this.cursor++; + } + + public void set(E o) { + // allow the nested iterator to throw an exception before we modify the original list + this.listIterator.set(o); + if (this.state == State.PREVIOUS) { + this.set(this.cursor, o); + } else { + this.set(this.cursor - 1, o); + } + } + + + // ********** internal methods ********** + + /** + * The list passed in during construction held elements of type E, + * so this cast is not a problem. We need this cast because + * all the elements of the original collection were copied into + * an object array (Object[]). + */ + @SuppressWarnings("unchecked") + protected E nestedNext() { + return (E) this.listIterator.next(); + } + + /** + * The list passed in during construction held elements of type E, + * so this cast is not a problem. We need this cast because + * all the elements of the original collection were copied into + * an object array (Object[]). + */ + @SuppressWarnings("unchecked") + protected E nestedPrevious() { + return (E) this.listIterator.previous(); + } + + /** + * Add the specified element to the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Mutator}. + */ + protected void add(int index, E o) { + this.mutator.add(index, o); + } + + /** + * Remove the specified element from the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Mutator}. + */ + protected void remove(int index) { + this.mutator.remove(index); + } + + /** + * Set the specified element in the original list. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Mutator}. + */ + protected void set(int index, E o) { + this.mutator.set(index, o); + } + + + // ********** overrides ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + + //********** member interface ********** + + /** + * Used by {@link CloneListIterator} to remove + * elements from the original list; since the list iterator + * does not have direct access to the original list. + */ + public interface Mutator { + + /** + * Add the specified object to the original list. + */ + void add(int index, T o); + + /** + * Remove the specified object from the original list. + */ + void remove(int index); + + /** + * Set the specified object in the original list. + */ + void set(int index, T o); + + + final class ReadOnly implements Mutator { + @SuppressWarnings("rawtypes") + public static final Mutator INSTANCE = new ReadOnly(); + @SuppressWarnings("unchecked") + public static Mutator instance() { + return INSTANCE; + } + // ensure single instance + private ReadOnly() { + super(); + } + // add is not supported + public void add(int index, Object o) { + throw new UnsupportedOperationException(); + } + // remove is not supported + public void remove(int index) { + throw new UnsupportedOperationException(); + } + // set is not supported + public void set(int index, Object o) { + throw new UnsupportedOperationException(); + } + @Override + public String toString() { + return "CloneListIterator.Mutator.ReadOnly"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeIterator.java new file mode 100644 index 0000000000..02ad76d9d0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeIterator.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; + +/** + * A CompositeIterator wraps a collection + * of {@link Iterator}s and makes them appear to be a single + * {@link Iterator}. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.CompositeIterable + */ +public class CompositeIterator + implements Iterator +{ + private final Iterator> iterators; + private Iterator currentIterator; + private Iterator lastIteratorToReturnNext; + + + // ********** constructors ********** + + /** + * Construct an iterator that returns all the elements held by the + * specified iterables. + */ + public CompositeIterator(Iterable> iterables) { + this( + new TransformationIterator, Iterator>(iterables.iterator()) { + @Override + protected Iterator transform(Iterable iterable) { + return iterable.iterator(); + } + } + ); + } + + /** + * Construct an iterator with the specified collection of iterators. + */ + public CompositeIterator(Iterator> iterators) { + super(); + this.iterators = iterators; + } + + /** + * Construct an iterator with the specified object prepended + * to the specified iterable. + */ + public CompositeIterator(E object, Iterable iterable) { + this(object, iterable.iterator()); + } + + /** + * Construct an iterator with the specified object prepended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public CompositeIterator(E object, Iterator iterator) { + this(new SingleElementIterator(object), iterator); + } + + /** + * Construct an iterator with the specified object appended + * to the specified iterable. + */ + public CompositeIterator(Iterable iterable, E object) { + this(iterable.iterator(), object); + } + + /** + * Construct an iterator with the specified object appended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public CompositeIterator(Iterator iterator, E object) { + this(iterator, new SingleElementIterator(object)); + } + + /** + * Construct an iterator with the specified iterables. + */ + public CompositeIterator(Iterable... iterables) { + this(new ArrayIterable>(iterables)); + } + + /** + * Construct an iterator with the specified iterators. + */ + public CompositeIterator(Iterator... iterators) { + this(new ArrayIterator>(iterators)); + } + + + // ********** Iterator implementation ********** + + public boolean hasNext() { + try { + this.loadCurrentIterator(); + } catch (NoSuchElementException ex) { + // this occurs if there are no iterators at all + return false; + } + return this.currentIterator.hasNext(); + } + + public E next() { + this.loadCurrentIterator(); + E result = this.currentIterator.next(); + + // the statement above will throw a NoSuchElementException + // if the current iterator is at the end of the line; + // so if we get here, we can set 'lastIteratorToReturnNext' + this.lastIteratorToReturnNext = this.currentIterator; + + return result; + } + + public void remove() { + if (this.lastIteratorToReturnNext == null) { + // CompositeIterator#next() has never been called + throw new IllegalStateException(); + } + this.lastIteratorToReturnNext.remove(); + } + + /** + * Load {@link #currentIterator} with the first iterator that {@link Iterator#hasNext()} + * or the final iterator if all the elements have already been retrieved. + */ + private void loadCurrentIterator() { + if (this.currentIterator == null) { + this.currentIterator = this.iterators.next(); + } + while (( ! this.currentIterator.hasNext()) && this.iterators.hasNext()) { + this.currentIterator = this.iterators.next(); + } + } + + + // ********** overrides ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterators); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeListIterator.java new file mode 100644 index 0000000000..3111451a92 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeListIterator.java @@ -0,0 +1,270 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayListIterable; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * A CompositeListIterator wraps a list + * of {@link ListIterator}s and makes them appear to be a single + * {@link ListIterator}. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.CompositeListIterable + */ +public class CompositeListIterator + implements ListIterator +{ + private final ListIterator> iterators; + private ListIterator nextIterator; + private int nextIndex; + /** + * true if "next" was last returned; + * false if "previous" was last returned; + * this determines the effect of {@link #remove()} on {@link #nextIndex} + */ + private boolean nextReturned; + private ListIterator lastIteratorToReturnElement; + + + /** + * Construct a list iterator on the elements in the specified list of lists. + */ + public CompositeListIterator(List> lists) { + this( + new TransformationListIterator, ListIterator>(lists.listIterator()) { + @Override + protected ListIterator transform(List list) { + return list.listIterator(); + } + } + ); + } + + /** + * Construct a list iterator on the elements in the specified list of lists. + */ + public CompositeListIterator(ListIterable> listIterables) { + this( + new TransformationListIterator, ListIterator>(listIterables.iterator()) { + @Override + protected ListIterator transform(ListIterable list) { + return list.iterator(); + } + } + ); + } + + /** + * Construct a list iterator with the specified list of list iterators. + */ + public CompositeListIterator(ListIterator> iterators) { + super(); + this.iterators = iterators; + this.nextIndex = 0; + this.nextReturned = false; + } + + /** + * Construct a list iterator with the specified object prepended + * to the specified list. + */ + public CompositeListIterator(E object, List list) { + this(object, list.listIterator()); + } + + /** + * Construct a list iterator with the specified object prepended + * to the specified list. + */ + public CompositeListIterator(E object, ListIterable listIterable) { + this(object, listIterable.iterator()); + } + + /** + * Construct a list iterator with the specified object prepended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public CompositeListIterator(E object, ListIterator iterator) { + this(new SingleElementListIterator(object), iterator); + } + + /** + * Construct a list iterator with the specified object appended + * to the specified list. + */ + public CompositeListIterator(List list, E object) { + this(list.listIterator(), object); + } + + /** + * Construct a list iterator with the specified object appended + * to the specified list. + */ + public CompositeListIterator(ListIterable listIterable, E object) { + this(listIterable.iterator(), object); + } + + /** + * Construct a list iterator with the specified object appended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public CompositeListIterator(ListIterator iterator, E object) { + this(iterator, new SingleElementListIterator(object)); + } + + /** + * Construct a list iterator with the specified lists. + */ + public CompositeListIterator(List... lists) { + this(Arrays.asList(lists)); + } + + /** + * Construct a list iterator with the specified lists. + */ + public CompositeListIterator(ListIterable... listIterables) { + this(new ArrayListIterable>(listIterables)); + } + + /** + * Construct a list iterator with the specified list iterators. + */ + public CompositeListIterator(ListIterator... iterators) { + this(new ArrayListIterator>(iterators)); + } + + public void add(E o) { + this.checkNextIterator(); + this.nextIterator.add(o); + this.nextIndex++; + } + + public boolean hasNext() { + try { + this.loadNextIterator(); + } catch (NoSuchElementException ex) { + // this occurs if there are no iterators at all + return false; + } + return this.nextIterator.hasNext(); + } + + public boolean hasPrevious() { + try { + this.loadPreviousIterator(); + } catch (NoSuchElementException ex) { + // this occurs if there are no iterators at all + return false; + } + return this.nextIterator.hasPrevious(); + } + + public E next() { + this.loadNextIterator(); + E result = this.nextIterator.next(); + + // the statement above will throw a NoSuchElementException + // if the current iterator is at the end of the line; + // so if we get here, we can set the 'lastIteratorToReturnElement' + this.lastIteratorToReturnElement = this.nextIterator; + this.nextIndex++; + this.nextReturned = true; + + return result; + } + + public int nextIndex() { + return this.nextIndex; + } + + public E previous() { + this.loadPreviousIterator(); + E result = this.nextIterator.previous(); + + // the statement above will throw a NoSuchElementException + // if the current iterator is at the end of the line; + // so if we get here, we can set the 'lastIteratorToReturnElement' + this.lastIteratorToReturnElement = this.nextIterator; + this.nextIndex--; + this.nextReturned = false; + + return result; + } + + public int previousIndex() { + return this.nextIndex - 1; + } + + public void remove() { + if (this.lastIteratorToReturnElement == null) { + throw new IllegalStateException(); + } + this.lastIteratorToReturnElement.remove(); + if (this.nextReturned) { + // decrement the index because the "next" element has moved forward in the list + this.nextIndex--; + } + } + + public void set(E e) { + if (this.lastIteratorToReturnElement == null) { + throw new IllegalStateException(); + } + this.lastIteratorToReturnElement.set(e); + } + + /** + * Load 'nextIterator' with the first iterator that hasNext() + * or the final iterator if all the elements have already been retrieved. + */ + private void loadNextIterator() { + this.checkNextIterator(); + while (( ! this.nextIterator.hasNext()) && this.iterators.hasNext()) { + this.nextIterator = this.iterators.next(); + } + } + + /** + * Load 'nextIterator' with the first iterator that hasPrevious() + * or the first iterator if all the elements have already been retrieved. + */ + private void loadPreviousIterator() { + this.checkNextIterator(); + while (( ! this.nextIterator.hasPrevious()) && this.iterators.hasPrevious()) { + this.nextIterator = this.iterators.previous(); + } + } + + /** + * If 'nextIterator' is null, load it with the first iterator. + */ + private void checkNextIterator() { + if (this.nextIterator == null) { + this.nextIterator = this.iterators.next(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterators); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyIterator.java new file mode 100644 index 0000000000..f06e2e03d8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyIterator.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * An EmptyIterator is just that. + * + * @param the type of elements (not) returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.EmptyIterable + */ +public final class EmptyIterator + implements Iterator +{ + + // singleton + @SuppressWarnings("rawtypes") + private static final EmptyIterator INSTANCE = new EmptyIterator(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static Iterator instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private EmptyIterator() { + super(); + } + + public boolean hasNext() { + return false; + } + + public E next() { + throw new NoSuchElementException(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyListIterator.java new file mode 100644 index 0000000000..73e23766a1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyListIterator.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.ListIterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * An EmptyListIterator is just that. + * + * @param the type of elements (not) returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.EmptyListIterable + */ +public final class EmptyListIterator + implements ListIterator +{ + + // singleton + @SuppressWarnings("rawtypes") + private static final EmptyListIterator INSTANCE = new EmptyListIterator(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static ListIterator instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private EmptyListIterator() { + super(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() { + return false; + } + + public boolean hasPrevious() { + return false; + } + + public E next() { + throw new NoSuchElementException(); + } + + public int nextIndex() { + return 0; + } + + public E previous() { + throw new NoSuchElementException(); + } + + public int previousIndex() { + return -1; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EnumerationIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EnumerationIterator.java new file mode 100644 index 0000000000..d8e9d38e25 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EnumerationIterator.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Enumeration; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * An EnumerationIterator wraps an + * {@link Enumeration} so that it can be treated like an + * {@link Iterator}. + * + * @param the type of elements returned by the iterator + */ +public class EnumerationIterator + implements Iterator +{ + private final Enumeration enumeration; + + /** + * Construct an iterator that wraps the specified enumeration. + */ + public EnumerationIterator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + public boolean hasNext() { + return this.enumeration.hasMoreElements(); + } + + public E next() { + return this.enumeration.nextElement(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.enumeration); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/FilteringIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/FilteringIterator.java new file mode 100644 index 0000000000..4c3176c9d4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/FilteringIterator.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.Filter; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A FilteringIterator wraps another {@link Iterator} + * and uses a {@link Filter} to determine which elements in the + * nested iterator are to be returned by calls to {@link #next()}. + *

+ * As an alternative to building a {@link Filter}, a subclass + * of FilteringIterator can override the + * {@link #accept(Object)} method. + *

+ * One, possibly undesirable, side-effect of using this iterator is that + * the nested iterator's next() method will be invoked + * before the filtered iterator's {@link #next()} + * method is invoked. This is because the "next" element must be + * checked for whether it is to be accepted before the filtered iterator + * can determine whether it has a "next" element (i.e. that the + * {@link #hasNext()} method should return true). + * This also prevents a filtered iterator from supporting the optional + * remove() method. + * + * @param the type of elements to be filtered + * + * @see org.eclipse.jpt.common.utility.internal.iterables.FilteringIterable + */ +public class FilteringIterator + implements Iterator +{ + private final Iterator iterator; + private final Filter filter; + private E next; + private boolean done; + + + /** + * Construct an iterator with the specified + * iterable and a disabled filter. + * Use this constructor if you want to override the + * {@link #accept(Object)} method instead of building + * a {@link Filter}. + */ + public FilteringIterator(Iterable iterable) { + this(iterable.iterator()); + } + + /** + * Construct an iterator with the specified nested + * iterator and a disabled filter. + * Use this constructor if you want to override the + * {@link #accept(Object)} method instead of building + * a {@link Filter}. + */ + public FilteringIterator(Iterator iterator) { + this(iterator, Filter.Disabled.instance()); + } + + /** + * Construct an iterator with the specified + * iterable and filter. + */ + public FilteringIterator(Iterable iterable, Filter filter) { + this(iterable.iterator(), filter); + } + + /** + * Construct an iterator with the specified nested + * iterator and filter. + */ + public FilteringIterator(Iterator iterator, Filter filter) { + super(); + this.iterator = iterator; + this.filter = filter; + this.loadNext(); + } + + public boolean hasNext() { + return ! this.done; + } + + public E next() { + if (this.done) { + throw new NoSuchElementException(); + } + E result = this.next; + this.loadNext(); + return result; + } + + /** + * Because we need to pre-load the next element + * to be returned, we cannot support the remove() + * method. + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Load next with the next valid entry from the nested + * iterator. If there are none, next is set to END. + */ + private void loadNext() { + this.done = true; + while (this.iterator.hasNext() && (this.done)) { + E temp = this.iterator.next(); + if (this.accept(temp)) { + // assume that if the object was accepted it is of type E + this.next = temp; + this.done = false; + } else { + this.next = null; + this.done = true; + } + } + } + + /** + * Return whether the {@link FilteringIterator} + * should return the specified next element from a call to the + * {@link #next()} method. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Filter}. + */ + protected boolean accept(E o) { + return this.filter.accept(o); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/GraphIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/GraphIterator.java new file mode 100644 index 0000000000..ff50f16ce4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/GraphIterator.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A GraphIterator is similar to a {@link TreeIterator} + * except that it cannot be assumed that all nodes assume a strict tree + * structure. For instance, in a tree, a node cannot be a descendent of + * itself, but a graph may have a cyclical structure. + * + * A GraphIterator simplifies the traversal of a + * graph of objects, where the objects' protocol(s) provides + * a method for getting the next collection of nodes in the graph, + * (or neighbors), but does not provide a method for + * getting all of the nodes in the graph. + * (e.g. a neighbor can return his neighbors, and those neighbors + * can return their neighbors, which might also include the original + * neighbor, but you only want to visit the original neighbor once.) + *

+ * If a neighbor has already been visited (determined by using + * {@link #equals(Object)}), that neighbor is not visited again, + * nor are the neighbors of that object. + *

+ * It is up to the user of this class to ensure a complete graph. + *

+ * To use, supply:

    + *
  • either the initial node of the graph or an {@link Iterator} + * over an initial collection of graph nodes + *
  • a {@link MisterRogers} that tells who the neighbors are + * of each node + * (alternatively, subclass GraphIterator + * and override the {@link #neighbors(Object)} method) + *
+ * {@link #remove()} is not supported. This behavior, if + * desired, must be implemented by the user of this class. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.GraphIterable + */ +public class GraphIterator + implements Iterator +{ + // use a LinkedList since we will be pulling off the front and adding to the end + private final LinkedList> iterators = new LinkedList>(); + private final HashSet visitedNeighbors = new HashSet(); + private final MisterRogers misterRogers; + + private Iterator currentIterator; + + private E nextNeighbor; + private boolean done; + + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified collection of roots + * and a disabled Mr. Rogers. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link MisterRogers}. + */ + public GraphIterator(E... roots) { + this(new ArrayIterator(roots)); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified collection of roots + * and a disabled Mr. Rogers. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link MisterRogers}. + */ + public GraphIterator(Iterable roots) { + this(roots.iterator()); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified collection of roots + * and a disabled Mr. Rogers. + * Use this constructor if you want to override the + * {@link #neighbors(Object)} method instead of building + * a {@link MisterRogers}. + */ + public GraphIterator(Iterator roots) { + this(roots, MisterRogers.Disabled.instance()); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified root + * and a disabled Mr. Rogers. + * Use this constructor if you want to override the + * neighbors(Object) method instead of building + * a MisterRogers. + */ + public GraphIterator(E root) { + this(root, MisterRogers.Disabled.instance()); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified root and Mr. Rogers. + */ + public GraphIterator(E root, MisterRogers misterRogers) { + this(new SingleElementIterator(root), misterRogers); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified collection of roots and Mr. Rogers. + */ + public GraphIterator(E[] roots, MisterRogers misterRogers) { + this(new ArrayIterator(roots), misterRogers); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified roots and Mr. Rogers. + */ + public GraphIterator(Iterable roots, MisterRogers misterRogers) { + this(roots.iterator(), misterRogers); + } + + /** + * Construct an iterator that returns the nodes of a graph + * with the specified roots and Mr. Rogers. + */ + public GraphIterator(Iterator roots, MisterRogers misterRogers) { + super(); + this.currentIterator = roots; + this.misterRogers = misterRogers; + this.loadNextNeighbor(); + } + + /** + * Load next neighbor with the next entry from the current iterator. + * If the current iterator has none, load the next iterator. + * If there are no more, the {@link #done} flag is set. + */ + private void loadNextNeighbor() { + if (this.currentIterator == EmptyIterator.instance()) { + this.done = true; + } + else if (this.currentIterator.hasNext()) { + E nextPossibleNeighbor = this.currentIterator.next(); + if (this.visitedNeighbors.contains(nextPossibleNeighbor)) { + this.loadNextNeighbor(); // recurse + } else { + this.nextNeighbor = nextPossibleNeighbor; + this.visitedNeighbors.add(nextPossibleNeighbor); + this.iterators.add(this.neighbors(nextPossibleNeighbor)); + } + } + else { + for (Iterator> stream = this.iterators.iterator(); ! this.currentIterator.hasNext() && stream.hasNext(); ) { + this.currentIterator = stream.next(); + stream.remove(); + } + if ( ! this.currentIterator.hasNext()) { + this.currentIterator = EmptyIterator.instance(); + } + this.loadNextNeighbor(); // recurse + } + } + + public boolean hasNext() { + return ! this.done; + } + + public E next() { + if (this.done) { + throw new NoSuchElementException(); + } + E next = this.nextNeighbor; + this.loadNextNeighbor(); + return next; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Return the immediate neighbors of the specified object. + */ + protected Iterator neighbors(E next) { + return this.misterRogers.neighbors(next); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.currentIterator); + } + + + //********** inner classes ********** + + /** + * Used by {@link GraphIterator} to retrieve + * the immediate neighbors of a node in the graph. + * "These are the people in your neighborhood..." + */ + public interface MisterRogers { + + /** + * Return the immediate neighbors of the specified object. + */ + Iterator neighbors(T next); + + + final class Null implements MisterRogers { + @SuppressWarnings("rawtypes") + public static final MisterRogers INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static MisterRogers instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // return no neighbors + public Iterator neighbors(S next) { + return EmptyIterator.instance(); + } + @Override + public String toString() { + return "GraphIterator.MisterRogers.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** The Mr. Rogers used when the {@link GraphIterator#neighbors(Object)} method is overridden. */ + final class Disabled implements MisterRogers { + @SuppressWarnings("rawtypes") + public static final MisterRogers INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static MisterRogers instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public Iterator neighbors(S next) { + throw new UnsupportedOperationException(); // GraphIterator.neighbors(Object) was not implemented + } + @Override + public String toString() { + return "GraphIterator.MisterRogers.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/PeekableIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/PeekableIterator.java new file mode 100644 index 0000000000..1a1f6284ae --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/PeekableIterator.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A PeekableIterator wraps another {@link Iterator} + * and allows a {@link #peek()} at the next element to be + * returned by {@link #next()}. + *

+ * One, possibly undesirable, side-effect of using this iterator is that + * the nested iterator's next() method will be invoked + * before the peekable iterator's {@link #next()} + * method is invoked. This is because the "next" element must be + * pre-loaded for the {@link #peek()} method. + * This also prevents a peekable iterator from supporting the optional + * {@link #remove()} method. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.PeekableIterable + */ +public class PeekableIterator + implements Iterator +{ + private final Iterator iterator; + private E next; + private boolean done; + + + /** + * Construct a peekable iterator that wraps the specified + * iterable. + */ + public PeekableIterator(Iterable iterable) { + this(iterable.iterator()); + } + + /** + * Construct a peekable iterator that wraps the specified nested + * iterator. + */ + public PeekableIterator(Iterator iterator) { + super(); + this.iterator = iterator; + this.done = false; + this.loadNext(); + } + + public boolean hasNext() { + return ! this.done; + } + + public E next() { + if (this.done) { + throw new NoSuchElementException(); + } + E result = this.next; + this.loadNext(); + return result; + } + + /** + * Return the element that will be returned by the next call to the + * {@link #next()} method, without advancing past it. + */ + public E peek() { + if (this.done) { + throw new NoSuchElementException(); + } + return this.next; + } + + /** + * Because we need to pre-load the next element + * to be returned, we cannot support the {@link #remove()} + * method. + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Load next with the next entry from the nested + * iterator. If there are none, {@link #next} is set to null + * and {@link #done} is set to true. + */ + private void loadNext() { + if (this.iterator.hasNext()) { + this.next = this.iterator.next(); + } else { + this.next = null; + this.done = true; + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/QueueIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/QueueIterator.java new file mode 100644 index 0000000000..4f946237b0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/QueueIterator.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.Queue; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A QueueIterator provides an {@link Iterator} + * for a {@link Queue} of objects of type E. The queue's elements + * are {@link Queue#dequeue() dequeue}d" as the iterator returns them with + * calls to {@link #next()}. + * + * @param the type of elements returned by the iterator + * + * @see Queue + * @see org.eclipse.jpt.common.utility.internal.iterables.QueueIterable + */ +public class QueueIterator + implements Iterator +{ + private final Queue queue; + + + /** + * Construct an iterator for the specified queue. + */ + public QueueIterator(Queue queue) { + super(); + this.queue = queue; + } + + public boolean hasNext() { + return ! this.queue.isEmpty(); + } + + public E next() { + return this.queue.dequeue(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.queue); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyCompositeListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyCompositeListIterator.java new file mode 100644 index 0000000000..425788548e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyCompositeListIterator.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayListIterable; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * A ReadOnlyCompositeListIterator wraps a list + * of {@link ListIterator}s and makes them appear to be a single + * read-only {@link ListIterator}. A read-only composite list + * iterator is more flexible than a normal composite list iterator when it + * comes to the element types of the nested iterators. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyCompositeListIterable + */ +public class ReadOnlyCompositeListIterator + implements ListIterator +{ + private final ListIterator> iterators; + private ListIterator nextIterator; + private int nextIndex; + + + /** + * Construct a read-only list iterator with the specified list of lists. + */ + public ReadOnlyCompositeListIterator(List> lists) { + this( + new TransformationListIterator, ListIterator>(lists.listIterator()) { + @Override + protected ListIterator transform(List list) { + return list.listIterator(); + } + } + ); + } + + /** + * Construct a read-only list iterator with the specified list of lists. + */ + public ReadOnlyCompositeListIterator(ListIterable> listIterables) { + this( + new TransformationListIterator, ListIterator>(listIterables.iterator()) { + @Override + protected ListIterator transform(ListIterable listIterable) { + return listIterable.iterator(); + } + } + ); + } + + /** + * Construct a read-only list iterator with the specified list of + * list iterators. + */ + public ReadOnlyCompositeListIterator(ListIterator> iterators) { + super(); + this.iterators = iterators; + this.nextIndex = 0; + } + + /** + * Construct a read-only list iterator with the specified object prepended + * to the specified list. + */ + public ReadOnlyCompositeListIterator(E object, List list) { + this(object, list.listIterator()); + } + + /** + * Construct a read-only list iterator with the specified object prepended + * to the specified list. + */ + public ReadOnlyCompositeListIterator(E object, ListIterable listIterable) { + this(object, listIterable.iterator()); + } + + /** + * Construct a read-only list iterator with the specified object prepended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public ReadOnlyCompositeListIterator(E object, ListIterator iterator) { + this(new SingleElementListIterator(object), iterator); + } + + /** + * Construct a read-only list iterator with the specified object appended + * to the specified list. + */ + public ReadOnlyCompositeListIterator(List list, E object) { + this(list.listIterator(), object); + } + + /** + * Construct a read-only list iterator with the specified object appended + * to the specified list. + */ + public ReadOnlyCompositeListIterator(ListIterable listIterable, E object) { + this(listIterable.iterator(), object); + } + + /** + * Construct a read-only list iterator with the specified object appended + * to the specified iterator. + */ + @SuppressWarnings("unchecked") + public ReadOnlyCompositeListIterator(ListIterator iterator, E object) { + this(iterator, new SingleElementListIterator(object)); + } + + /** + * Construct a read-only list iterator with the specified lists. + */ + public ReadOnlyCompositeListIterator(List... lists) { + this(Arrays.asList(lists)); + } + + /** + * Construct a read-only list iterator with the specified lists. + */ + public ReadOnlyCompositeListIterator(ListIterable... listIterables) { + this(new ArrayListIterable>(listIterables)); + } + + /** + * Construct a read-only list iterator with the specified list iterators. + */ + public ReadOnlyCompositeListIterator(ListIterator... iterators) { + this(new ArrayListIterator>(iterators)); + } + + public boolean hasNext() { + try { + this.loadNextIterator(); + } catch (NoSuchElementException ex) { + // this occurs if there are no iterators at all + return false; + } + return this.nextIterator.hasNext(); + } + + public boolean hasPrevious() { + try { + this.loadPreviousIterator(); + } catch (NoSuchElementException ex) { + // this occurs if there are no iterators at all + return false; + } + return this.nextIterator.hasPrevious(); + } + + public E next() { + this.loadNextIterator(); + E result = this.nextIterator.next(); + + // the statement above will throw a NoSuchElementException + // if the current iterator is at the end of the line; + // so if we get here, we can increment 'nextIndex' + this.nextIndex++; + + return result; + } + + public int nextIndex() { + return this.nextIndex; + } + + public E previous() { + this.loadPreviousIterator(); + E result = this.nextIterator.previous(); + + // the statement above will throw a NoSuchElementException + // if the current iterator is at the end of the line; + // so if we get here, we can decrement 'nextIndex' + this.nextIndex--; + + return result; + } + + public int previousIndex() { + return this.nextIndex - 1; + } + + public void add(E o) { + // the list iterator is read-only + throw new UnsupportedOperationException(); + } + + public void remove() { + // the list iterator is read-only + throw new UnsupportedOperationException(); + } + + public void set(E e) { + // the list iterator is read-only + throw new UnsupportedOperationException(); + } + + /** + * Load {@link #nextIterator} with the first iterator that {@link #hasNext()} + * or the final iterator if all the elements have already been retrieved. + */ + private void loadNextIterator() { + this.checkNextIterator(); + while (( ! this.nextIterator.hasNext()) && this.iterators.hasNext()) { + this.nextIterator = this.iterators.next(); + } + } + + /** + * Load {@link #nextIterator} with the first iterator that {@link #hasPrevious()} + * or the first iterator if all the elements have already been retrieved. + */ + private void loadPreviousIterator() { + this.checkNextIterator(); + while (( ! this.nextIterator.hasPrevious()) && this.iterators.hasPrevious()) { + this.nextIterator = this.iterators.previous(); + } + } + + /** + * If {@link #nextIterator} is null, load it with the first iterator. + */ + private void checkNextIterator() { + if (this.nextIterator == null) { + this.nextIterator = this.iterators.next(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterators); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyIterator.java new file mode 100644 index 0000000000..5673d3d190 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyIterator.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A ReadOnlyIterator wraps another {@link Iterator} + * and removes support for {@link #remove()}. + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyIterable + */ +public class ReadOnlyIterator + implements Iterator +{ + private final Iterator iterator; + + /** + * Construct an iterator on the specified collection that + * disallows removes. + */ + public ReadOnlyIterator(Iterable c) { + this(c.iterator()); + } + + /** + * Construct an iterator with the specified nested iterator + * and disallow removes. + */ + public ReadOnlyIterator(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + // delegate to the nested iterator + return this.iterator.hasNext(); + } + + public E next() { + // delegate to the nested iterator + return this.iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyListIterator.java new file mode 100644 index 0000000000..ac09133a5d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyListIterator.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * A ReadOnlyListIterator wraps another + * {@link ListIterator} and removes support for:

    + *
  • {@link #remove()} + *
  • {@link #set(Object)} + *
  • {@link #add(Object)} + *
+ * + * @param the type of elements returned by the list iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyListIterable + */ +public class ReadOnlyListIterator + implements ListIterator +{ + private final ListIterator listIterator; + + + /** + * Construct a list iterator on the specified list that + * disallows removes, sets, and adds. + */ + public ReadOnlyListIterator(List list) { + this(list.listIterator()); + } + + /** + * Construct a list iterator on the specified list that + * disallows removes, sets, and adds. + */ + public ReadOnlyListIterator(ListIterable listIterable) { + this(listIterable.iterator()); + } + + /** + * Construct a list iterator on the specified list iterator that + * disallows removes, sets, and adds. + */ + public ReadOnlyListIterator(ListIterator listIterator) { + super(); + this.listIterator = listIterator; + } + + public boolean hasNext() { + // delegate to the nested iterator + return this.listIterator.hasNext(); + } + + public E next() { + // delegate to the nested iterator + return this.listIterator.next(); + } + + public boolean hasPrevious() { + // delegate to the nested iterator + return this.listIterator.hasPrevious(); + } + + public E previous() { + // delegate to the nested iterator + return this.listIterator.previous(); + } + + public int nextIndex() { + // delegate to the nested iterator + return this.listIterator.nextIndex(); + } + + public int previousIndex() { + // delegate to the nested iterator + return this.listIterator.previousIndex(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void set(E o) { + throw new UnsupportedOperationException(); + } + + public void add(E o) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listIterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ResultSetIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ResultSetIterator.java new file mode 100644 index 0000000000..03cf8c8863 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ResultSetIterator.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A ResultSetIterator wraps an SQL {@link ResultSet} + * and transforms its rows for client consumption. Subclasses can override + * {@link #buildNext(ResultSet)} to build the expected object from + * the current row of the result set. + *

+ * To use, supply:

    + *
  • a {@link ResultSet} + *
  • an {@link Adapter} that converts a row in the {@link ResultSet} + * into the desired object + * (alternatively, subclass ResultSetIterator + * and override the {@link #buildNext(ResultSet)} method) + *
+ * + * @param the type of elements returned by the iterator + * + * @see java.sql.ResultSet + */ +public class ResultSetIterator + implements Iterator +{ + private final ResultSet resultSet; + private final Adapter adapter; + private E next; + private boolean done; + + + /** + * Construct an iterator on the specified result set that returns + * the objects produced by the specified adapter. + */ + public ResultSetIterator(ResultSet resultSet, Adapter adapter) { + super(); + this.resultSet = resultSet; + this.adapter = adapter; + this.done = false; + this.next = this.buildNext(); + } + + /** + * Construct an iterator on the specified result set that returns + * the first object in each row of the result set. + */ + public ResultSetIterator(ResultSet resultSet) { + this(resultSet, Adapter.Default.instance()); + } + + /** + * Build the next object for the iterator to return. + * Close the result set when we reach the end. + */ + private E buildNext() { + try { + if (this.resultSet.next()) { + return this.buildNext(this.resultSet); + } + this.resultSet.close(); + this.done = true; + return null; + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + } + + /** + * By default, return the first object in the current row + * of the result set. Any {@link SQLException}s will + * be caught and wrapped in a {@link RuntimeException}. + */ + protected E buildNext(ResultSet rs) throws SQLException { + return this.adapter.buildNext(rs); + } + + public boolean hasNext() { + return ! this.done; + } + + public E next() { + if (this.done) { + throw new NoSuchElementException(); + } + E temp = this.next; + this.next = this.buildNext(); + return temp; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.resultSet); + } + + + // ********** interface ********** + + /** + * Used by {@link ResultSetIterator} to convert a + * {@link ResultSet}'s current row into the next object + * to be returned by the {@link Iterator}. + */ + public interface Adapter { + + /** + * Return an object corresponding to the result set's + * "current" row. Any {@link SQLException}s will + * be caught and wrapped in a {@link RuntimeException}. + * @see java.sql.ResultSet + */ + T buildNext(ResultSet rs) throws SQLException; + + + final class Default implements Adapter { + @SuppressWarnings("rawtypes") + public static final Adapter INSTANCE = new Default(); + @SuppressWarnings("unchecked") + public static Adapter instance() { + return INSTANCE; + } + // ensure single instance + private Default() { + super(); + } + // return the first object in the current row of the result set + @SuppressWarnings("unchecked") + public S buildNext(ResultSet rs) throws SQLException { + // result set columns are indexed starting with 1 + return (S) rs.getObject(1); + } + @Override + public String toString() { + return "ResultSetIterator.Adapter.Default"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReverseIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReverseIterator.java new file mode 100644 index 0000000000..7b8bfb4a07 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReverseIterator.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.ArrayList; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A ReverseIterator wraps another {@link Iterator} and returns + * its elements in the reverse order in which the wrapped {@link Iterator} + * returns the elements. + * + * @param the type of elements returned by the iterator + */ +public class ReverseIterator + implements Iterator +{ + /** + * The elements in this iterator are already reversed. + */ + private final Iterator iterator; + + + /** + * Construct a reverse iterator for the specified iterator. + */ + public ReverseIterator(Iterator iterator) { + this(CollectionTools.reverseList(iterator)); + } + + /** + * Construct a reverse iterator for the specified iterator. + */ + public ReverseIterator(Iterator iterator, int size) { + this(CollectionTools.reverseList(iterator, size)); + } + + /** + * Construct a reverse iterator for the specified iterable. + */ + public ReverseIterator(Iterable iterable) { + this(CollectionTools.reverseList(iterable)); + } + + /** + * Construct a reverse iterator for the specified iterable. + */ + public ReverseIterator(Iterable iterable, int size) { + this(CollectionTools.reverseList(iterable, size)); + } + + private ReverseIterator(ArrayList reverseList) { + super(); + this.iterator = reverseList.iterator(); + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public E next() { + return this.iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementIterator.java new file mode 100644 index 0000000000..a6a743d1c7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementIterator.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A SingleElementIterator holds a single element + * and returns it with the first call to {@link #next()}, at + * which point it will return false to any subsequent + * call to {@link #hasNext()}. + *

+ * A SingleElementIterator is equivalent to the + * {@link Iterator} returned by: + * {@link java.util.Collections#singleton(Object element)}.iterator() + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SingleElementIterable + */ +public class SingleElementIterator + implements Iterator +{ + private final E element; + private boolean done; + + + /** + * Construct an iterator that returns only the specified element. + */ + public SingleElementIterator(E element) { + super(); + this.element = element; + this.done = false; + } + + public boolean hasNext() { + return ! this.done; + } + + public E next() { + if (this.done) { + throw new NoSuchElementException(); + } + this.done = true; + return this.element; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.element); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementListIterator.java new file mode 100644 index 0000000000..9363cd0be7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementListIterator.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.ListIterator; +import java.util.NoSuchElementException; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A SingleElementListIterator holds a single element + * and returns it with the first call to {@link #next()}, at + * which point it will return false to any subsequent + * call to {@link #hasNext()}. Likewise, it will return false + * to a call to {@link #hasPrevious()} until a call to {@link #next()}, + * at which point a call to {@link #previous()} will return the + * single element. + *

+ * A SingleElementListIterator is equivalent to the + * {@link ListIterator} returned by: + * {@link java.util.Collections#singletonList(Object element)}.listIterator() + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SingleElementListIterable + */ +public class SingleElementListIterator + implements ListIterator +{ + private final E element; + private boolean hasNext; + + + /** + * Construct a list iterator that returns only the specified element. + */ + public SingleElementListIterator(E element) { + super(); + this.element = element; + this.hasNext = true; + } + + public boolean hasNext() { + return this.hasNext; + } + + public E next() { + if (this.hasNext) { + this.hasNext = false; + return this.element; + } + throw new NoSuchElementException(); + } + + public int nextIndex() { + return this.hasNext ? 0 : 1; + } + + public boolean hasPrevious() { + return ! this.hasNext; + } + + public E previous() { + if (this.hasNext) { + throw new NoSuchElementException(); + } + this.hasNext = true; + return this.element; + } + + public int previousIndex() { + return this.hasNext ? -1 : 0; + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.element); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/StackIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/StackIterator.java new file mode 100644 index 0000000000..d8106d037a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/StackIterator.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.Stack; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A StackIterator provides an {@link Iterator} + * for a {@link Stack} of objects of type E. The stack's elements + * are {@link Stack#pop() pop}ped" as the iterator returns them with + * calls to {@link #next()}. + * + * @param the type of elements returned by the iterator + * + * @see Stack + * @see org.eclipse.jpt.common.utility.internal.iterables.StackIterable + */ +public class StackIterator + implements Iterator +{ + private final Stack stack; + + + /** + * Construct an iterator for the specified stack. + */ + public StackIterator(Stack stack) { + super(); + this.stack = stack; + } + + public boolean hasNext() { + return ! this.stack.isEmpty(); + } + + public E next() { + return this.stack.pop(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.stack); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubIteratorWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubIteratorWrapper.java new file mode 100644 index 0000000000..3f7b3447f6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubIteratorWrapper.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * Wrap an iterator on elements of type E1, converting it into an + * iterator on elements of type E2. Assume the wrapped + * iterator returns only elements of type E2. + * + * @param input: the type of elements returned by the wrapped iterator + * @param output: the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SubIterableWrapper + */ +public class SubIteratorWrapper + implements Iterator +{ + private final Iterator iterator; + + + public SubIteratorWrapper(Iterable iterable) { + this(iterable.iterator()); + } + + public SubIteratorWrapper(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @SuppressWarnings("unchecked") + public E2 next() { + return (E2) this.iterator.next(); + } + + public void remove() { + this.iterator.remove(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubListIteratorWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubListIteratorWrapper.java new file mode 100644 index 0000000000..534926384f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubListIteratorWrapper.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * Wrap a list iterator on elements of type E1, converting it into + * a list iterator on elements of type E2. Assume the wrapped + * list iterator returns only elements of type E2. + * + * @param input: the type of elements returned by the wrapped list iterator + * @param output: the type of elements returned by the list iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SubListIterableWrapper + */ +public class SubListIteratorWrapper + implements ListIterator +{ + private final ListIterator listIterator; + + + public SubListIteratorWrapper(List list) { + this(list.listIterator()); + } + + public SubListIteratorWrapper(ListIterable listIterable) { + this(listIterable.iterator()); + } + + public SubListIteratorWrapper(ListIterator iterator) { + super(); + this.listIterator = iterator; + } + + public boolean hasNext() { + return this.listIterator.hasNext(); + } + + @SuppressWarnings("unchecked") + public E2 next() { + return (E2) this.listIterator.next(); + } + + public int nextIndex() { + return this.listIterator.nextIndex(); + } + + public boolean hasPrevious() { + return this.listIterator.hasPrevious(); + } + + @SuppressWarnings("unchecked") + public E2 previous() { + return (E2) this.listIterator.previous(); + } + + public int previousIndex() { + return this.listIterator.previousIndex(); + } + + public void remove() { + this.listIterator.remove(); + } + + public void set(E2 e) { + throw new UnsupportedOperationException(); + } + + public void add(E2 e) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listIterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperIteratorWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperIteratorWrapper.java new file mode 100644 index 0000000000..2bd19a9d96 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperIteratorWrapper.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * Wrap an iterator on elements of any sub-type of E, converting + * it into an iterator on elements of type E. This shouldn't be a + * problem since there is no way to add invalid elements to the iterator's + * backing collection. (Note the lack of compiler warnings, suppressed or + * otherwise.) + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SuperIterableWrapper + */ +public class SuperIteratorWrapper + implements Iterator +{ + private final Iterator iterator; + + + public SuperIteratorWrapper(Iterable iterable) { + this(iterable.iterator()); + } + + public SuperIteratorWrapper(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public E next() { + return this.iterator.next(); + } + + public void remove() { + this.iterator.remove(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperListIteratorWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperListIteratorWrapper.java new file mode 100644 index 0000000000..d12773a57c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperListIteratorWrapper.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * Wrap a list iterator on elements of any sub-type of E, converting it into a + * list iterator on elements of type E. This shouldn't be a problem since the + * resulting list iterator disables the methods that would put invalid elements + * in the iterator's backing list (i.e. {@link #set(Object)} and {@link #add(Object)}). + * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.SuperListIterableWrapper + */ +public class SuperListIteratorWrapper + implements ListIterator +{ + private final ListIterator listIterator; + + + public SuperListIteratorWrapper(List list) { + this(list.listIterator()); + } + + public SuperListIteratorWrapper(ListIterable listIterable) { + this(listIterable.iterator()); + } + + public SuperListIteratorWrapper(ListIterator listIterator) { + super(); + this.listIterator = listIterator; + } + + public boolean hasNext() { + return this.listIterator.hasNext(); + } + + public E next() { + return this.listIterator.next(); + } + + public int nextIndex() { + return this.listIterator.nextIndex(); + } + + public boolean hasPrevious() { + return this.listIterator.hasPrevious(); + } + + public E previous() { + return this.listIterator.previous(); + } + + public int previousIndex() { + return this.listIterator.previousIndex(); + } + + public void remove() { + this.listIterator.remove(); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listIterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedIterator.java new file mode 100644 index 0000000000..8efef7eb2e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedIterator.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * Wrap an iterator and synchronize all its methods so it can be safely shared + * among multiple threads. + * + * @param the type of elements returned by the iterator + */ +public class SynchronizedIterator + implements Iterator +{ + private final Iterator iterator; + + /** Object to synchronize on. */ + private final Object mutex; + + + public SynchronizedIterator(Iterable iterable) { + this(iterable.iterator()); + } + + public SynchronizedIterator(Iterable iterable, Object mutex) { + this(iterable.iterator(), mutex); + } + + public SynchronizedIterator(Iterator iterator) { + super(); + this.iterator = iterator; + this.mutex = this; + } + + public SynchronizedIterator(Iterator iterator, Object mutex) { + super(); + this.iterator = iterator; + this.mutex = mutex; + } + + public synchronized boolean hasNext() { + synchronized (this.mutex) { + return this.iterator.hasNext(); + } + } + + public synchronized E next() { + synchronized (this.mutex) { + return this.iterator.next(); + } + } + + public synchronized void remove() { + synchronized (this.mutex) { + this.iterator.remove(); + } + } + + @Override + public String toString() { + synchronized (this.mutex) { + return StringTools.buildToStringFor(this, this.iterator); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedListIterator.java new file mode 100644 index 0000000000..87e6384bf5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedListIterator.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * Wrap a list iterator and synchronize all its methods so it can be safely shared + * among multiple threads. + * + * @param the type of elements returned by the iterator + */ +public class SynchronizedListIterator + implements ListIterator +{ + private final ListIterator listIterator; + + /** Object to synchronize on. */ + private final Object mutex; + + + public SynchronizedListIterator(List list) { + this(list.listIterator()); + } + + public SynchronizedListIterator(List list, Object mutex) { + this(list.listIterator(), mutex); + } + + public SynchronizedListIterator(ListIterable listIterable) { + this(listIterable.iterator()); + } + + public SynchronizedListIterator(ListIterable listIterable, Object mutex) { + this(listIterable.iterator(), mutex); + } + + public SynchronizedListIterator(ListIterator listIterator) { + super(); + this.listIterator = listIterator; + this.mutex = this; + } + + public SynchronizedListIterator(ListIterator listIterator, Object mutex) { + super(); + this.listIterator = listIterator; + this.mutex = mutex; + } + + public synchronized boolean hasNext() { + synchronized (this.mutex) { + return this.listIterator.hasNext(); + } + } + + public synchronized E next() { + synchronized (this.mutex) { + return this.listIterator.next(); + } + } + + public synchronized int nextIndex() { + synchronized (this.mutex) { + return this.listIterator.nextIndex(); + } + } + + public synchronized boolean hasPrevious() { + synchronized (this.mutex) { + return this.listIterator.hasPrevious(); + } + } + + public synchronized E previous() { + synchronized (this.mutex) { + return this.listIterator.previous(); + } + } + + public synchronized int previousIndex() { + synchronized (this.mutex) { + return this.listIterator.previousIndex(); + } + } + + public synchronized void remove() { + synchronized (this.mutex) { + this.listIterator.remove(); + } + } + + public synchronized void add(E e) { + synchronized (this.mutex) { + this.listIterator.add(e); + } + } + + public synchronized void set(E e) { + synchronized (this.mutex) { + this.listIterator.set(e); + } + } + + @Override + public String toString() { + synchronized (this.mutex) { + return StringTools.buildToStringFor(this, this.listIterator); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationIterator.java new file mode 100644 index 0000000000..7b6aa41bd2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationIterator.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; + + +/** + * A TransformationIterator wraps another {@link Iterator} + * and transforms its results for client consumption. To use, supply a + * {@link Transformer} or subclass TransformationIterator + * and override the {@link #transform(Object)} method. + * Objects of type E1 are transformed into objects of type E2; + * i.e. the iterator returns objects of type E2. + * + * @param input: the type of elements to be transformed + * @param output: the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable + */ +public class TransformationIterator + implements Iterator +{ + private final Iterator iterator; + private final Transformer transformer; + + + /** + * Construct an iterator with the specified iterable + * and a disabled transformer. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationIterator(Iterable iterable) { + this(iterable.iterator()); + } + + /** + * Construct an iterator with the specified nested iterator + * and a disabled transformer. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationIterator(Iterator iterator) { + this(iterator, Transformer.Disabled.instance()); + } + + /** + * Construct an iterator with the specified iterable and transformer. + */ + public TransformationIterator(Iterable iterable, Transformer transformer) { + this(iterable.iterator(), transformer); + } + + /** + * Construct an iterator with the specified nested iterator + * and transformer. + */ + public TransformationIterator(Iterator iterator, Transformer transformer) { + super(); + this.iterator = iterator; + this.transformer = transformer; + } + + public boolean hasNext() { + // delegate to the nested iterator + return this.iterator.hasNext(); + } + + public E2 next() { + // transform the object returned by the nested iterator before returning it + return this.transform(this.iterator.next()); + } + + public void remove() { + // delegate to the nested iterator + this.iterator.remove(); + } + + /** + * Transform the specified object and return the result. + */ + protected E2 transform(E1 next) { + return this.transformer.transform(next); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.iterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationListIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationListIterator.java new file mode 100644 index 0000000000..01455cd3d2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationListIterator.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; + +/** + * A TransformationListIterator wraps another {@link ListIterator} + * and transforms its results for client consumption. To use, supply a + * {@link Transformer} or subclass TransformationIterator + * and override the {@link #transform(Object)} method. + *

+ * The methods {@link #set(Object)} and {@link #add(Object)} + * are left unsupported in this class. + * + * @param input: the type of elements to be transformed + * @param output: the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.TransformationListIterable + */ +public class TransformationListIterator + implements ListIterator +{ + private final ListIterator listIterator; + private final Transformer transformer; + + + /** + * Construct an iterator with the specified list + * and a disabled transformer. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationListIterator(List list) { + this(list.listIterator()); + } + + /** + * Construct an iterator with the specified nested listed iterator + * and a disabled transformer. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationListIterator(ListIterator listIterator) { + this(listIterator, Transformer.Disabled.instance()); + } + + /** + * Construct an iterator with the specified list + * and a disabled transformer. + * Use this constructor if you want to override the + * {@link #transform(Object)} method instead of building + * a {@link Transformer}. + */ + public TransformationListIterator(ListIterable listIterable) { + this(listIterable.iterator()); + } + + /** + * Construct an iterator with the specified list and transformer. + */ + public TransformationListIterator(List list, Transformer transformer) { + this(list.listIterator(), transformer); + } + + /** + * Construct an iterator with the specified list and transformer. + */ + public TransformationListIterator(ListIterable listIterable, Transformer transformer) { + this(listIterable.iterator(), transformer); + } + + /** + * Construct an iterator with the specified nested iterator + * and transformer. + */ + public TransformationListIterator(ListIterator listIterator, Transformer transformer) { + super(); + this.listIterator = listIterator; + this.transformer = transformer; + } + + public boolean hasNext() { + // delegate to the nested iterator + return this.listIterator.hasNext(); + } + + public E2 next() { + // transform the object returned by the nested iterator before returning it + return this.transform(this.listIterator.next()); + } + + public int nextIndex() { + // delegate to the nested iterator + return this.listIterator.nextIndex(); + } + + public boolean hasPrevious() { + // delegate to the nested iterator + return this.listIterator.hasPrevious(); + } + + public E2 previous() { + // transform the object returned by the nested iterator before returning it + return this.transform(this.listIterator.previous()); + } + + public int previousIndex() { + // delegate to the nested iterator + return this.listIterator.previousIndex(); + } + + public void add(E2 o) { + throw new UnsupportedOperationException(); + } + + public void set(E2 o) { + throw new UnsupportedOperationException(); + } + + public void remove() { + // delegate to the nested iterator + this.listIterator.remove(); + } + + /** + * Transform the specified object and return the result. + */ + protected E2 transform(E1 next) { + return this.transformer.transform(next); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listIterator); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TreeIterator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TreeIterator.java new file mode 100644 index 0000000000..1103af97f4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TreeIterator.java @@ -0,0 +1,254 @@ +/******************************************************************************* + * Copyright (c) 2005, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.iterators; + +import java.util.Iterator; +import java.util.LinkedList; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * A TreeIterator simplifies the traversal of a + * tree of objects, where the objects' protocol(s) provides + * a method for getting the immediate children of the given + * node but does not provide a method for getting all the + * descendants (children, grandchildren, etc.) of the given node. + *

+ * To use, supply:

    + *
  • either the root element of the tree or, if the tree has + * multiple roots, an {@link Iterator} over the set of roots + *
  • a {@link Midwife} that delivers the children + * of each child + * (alternatively, subclass TreeIterator + * and override the {@link #children(Object)} method) + *
+ * + * @param the type of elements returned by the iterator + * + * @see org.eclipse.jpt.common.utility.internal.iterables.TreeIterable + */ +public class TreeIterator + implements Iterator +{ + private final LinkedList> iterators; + private final Midwife midwife; + private Iterator currentIterator; + + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified collection of roots + * and a disabled midwife. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link Midwife}. + */ + public TreeIterator(E... roots) { + this(new ArrayIterator(roots)); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified collection of roots + * and a disabled midwife. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link Midwife}. + */ + public TreeIterator(Iterable roots) { + this(roots.iterator()); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified collection of roots + * and a disabled midwife. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link Midwife}. + */ + public TreeIterator(Iterator roots) { + this(roots, Midwife.Disabled.instance()); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified root and a disabled midwife. + * Use this constructor if you want to override the + * {@link #children(Object)} method instead of building + * a {@link Midwife}. + */ + public TreeIterator(E root) { + this(root, Midwife.Disabled.instance()); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified root and midwife. + */ + public TreeIterator(E root, Midwife midwife) { + this(new SingleElementIterator(root), midwife); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified roots and midwife. + */ + public TreeIterator(E[] roots, Midwife midwife) { + this(new ArrayIterator(roots), midwife); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified roots and midwife. + */ + public TreeIterator(Iterable roots, Midwife midwife) { + this(roots.iterator(), midwife); + } + + /** + * Construct an iterator that returns the nodes of a tree + * with the specified roots and midwife. + */ + public TreeIterator(Iterator roots, Midwife midwife) { + super(); + this.currentIterator = roots; + // use a LinkedList since we will be pulling off the front and adding to the end + this.iterators = new LinkedList>(); + this.midwife = midwife; + } + + public boolean hasNext() { + if (this.currentIterator.hasNext()) { + return true; + } + for (Iterator iterator : this.iterators) { + if (iterator.hasNext()) { + return true; + } + } + return false; + } + + public E next() { + if (this.currentIterator.hasNext()) { + return this.nextInternal(); + } + for (Iterator> stream = this.iterators.iterator(); stream.hasNext(); ) { + this.currentIterator = stream.next(); + if (this.currentIterator.hasNext()) { + break; + } + stream.remove(); + } + return this.nextInternal(); + } + + /** + * Fetch the children of the next node before returning it. + */ + private E nextInternal() { + E next = this.currentIterator.next(); + this.iterators.add(this.children(next)); + return next; + } + + public void remove() { + this.currentIterator.remove(); + } + + /** + * Return the immediate children of the specified object. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Midwife}. + */ + protected Iterator children(E next) { + return this.midwife.children(next); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.currentIterator); + } + + + //********** inner classes ********** + + /** + * Used by {@link TreeIterator} to retrieve + * the immediate children of a node in the tree. + */ + public interface Midwife { + + /** + * Return the immediate children of the specified object. + */ + Iterator children(T o); + + + final class Null implements Midwife { + @SuppressWarnings("rawtypes") + public static final Midwife INSTANCE = new Null(); + @SuppressWarnings("unchecked") + public static Midwife instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + // return no neighbors + public Iterator children(S next) { + return EmptyIterator.instance(); + } + @Override + public String toString() { + return "TreeIterator.Midwife.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + /** + * The midwife used when the {@link TreeIterator#children(Object)} + * method is overridden. + */ + final class Disabled implements Midwife { + @SuppressWarnings("rawtypes") + public static final Midwife INSTANCE = new Disabled(); + @SuppressWarnings("unchecked") + public static Midwife instance() { + return INSTANCE; + } + // ensure single instance + private Disabled() { + super(); + } + // throw an exception + public Iterator children(S next) { + throw new UnsupportedOperationException(); // TreeIterator.children(Object) was not implemented + } + @Override + public String toString() { + return "TreeIterator.Midwife.Disabled"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AbstractModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AbstractModel.java new file mode 100644 index 0000000000..e880beb71f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AbstractModel.java @@ -0,0 +1,1007 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; + +/** + * Reasonable implementation of the {@link Model} protocol + * with numerous convenience methods. + * + * @see ChangeSupport + */ +public abstract class AbstractModel + implements Model +{ + /** + * Delegate state/property/collection/list/tree change support to this + * helper object. The change support object is lazily-initialized; + * so it may be null. The method {@link #getChangeSupport()} + * will initialize this field if it is null. + *

+ * NB: We instantiate this when we fire events, even when + * we do not have any listeners (which is be implied if this is null). + * This allows the change support to have behavior tied to events even when + * we have no listeners. + * + * @see AspectChangeSupport#aspectChanged(String) + */ + protected ChangeSupport changeSupport; + + + // ********** constructors/initialization ********** + + /** + * Default constructor. + */ + protected AbstractModel() { + super(); + } + + /** + * This accessor will build the change support when required. + * This only helps reduce the footprint of a model that neither has any + * listeners added to it nor ever changes (fires any events). + */ + protected synchronized ChangeSupport getChangeSupport() { + if (this.changeSupport == null) { + this.changeSupport = this.buildChangeSupport(); + } + return this.changeSupport; + } + + /** + * Allow subclasses to tweak the change support used. + */ + protected ChangeSupport buildChangeSupport() { + return new ChangeSupport(this); + } + + + // ********** change support ********** + + /** + * @see ChangeSupport#addChangeListener(ChangeListener) + */ + public void addChangeListener(ChangeListener listener) { + this.getChangeSupport().addChangeListener(listener); + } + + /** + * @see ChangeSupport#removeChangeListener(ChangeListener) + */ + public void removeChangeListener(ChangeListener listener) { + this.getChangeSupport().removeChangeListener(listener); + } + + /** + * @see ChangeSupport#hasAnyChangeListeners() + */ + public boolean hasAnyChangeListeners() { + return (this.changeSupport != null) && this.changeSupport.hasAnyChangeListeners(); + } + + /** + * Return whether the model has no change listeners. + */ + public boolean hasNoChangeListeners() { + return ! this.hasAnyChangeListeners(); + } + + + // ********** state change support ********** + + /** + * @see ChangeSupport#addStateChangeListener(StateChangeListener) + */ + public void addStateChangeListener(StateChangeListener listener) { + this.getChangeSupport().addStateChangeListener(listener); + } + + /** + * @see ChangeSupport#removeStateChangeListener(StateChangeListener) + */ + public void removeStateChangeListener(StateChangeListener listener) { + this.getChangeSupport().removeStateChangeListener(listener); + } + + /** + * @see ChangeSupport#hasAnyStateChangeListeners() + */ + public boolean hasAnyStateChangeListeners() { + return (this.changeSupport != null) && this.changeSupport.hasAnyStateChangeListeners(); + } + + /** + * Return whether the model has no state change listeners. + */ + public boolean hasNoStateChangeListeners() { + return ! this.hasAnyStateChangeListeners(); + } + + /** + * @see ChangeSupport#fireStateChanged(StateChangeEvent) + */ + protected final void fireStateChanged(StateChangeEvent event) { + this.getChangeSupport().fireStateChanged(event); + } + + /** + * @see ChangeSupport#fireStateChanged() + */ + protected final void fireStateChanged() { + this.getChangeSupport().fireStateChanged(); + } + + + // ********** property change support ********** + + /** + * @see ChangeSupport#addPropertyChangeListener(String, PropertyChangeListener) + */ + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + this.getChangeSupport().addPropertyChangeListener(propertyName, listener); + } + + /** + * @see ChangeSupport#removePropertyChangeListener(String, PropertyChangeListener) + */ + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + this.getChangeSupport().removePropertyChangeListener(propertyName, listener); + } + + /** + * @see ChangeSupport#hasAnyPropertyChangeListeners(String) + */ + public boolean hasAnyPropertyChangeListeners(String propertyName) { + return (this.changeSupport != null) && this.changeSupport.hasAnyPropertyChangeListeners(propertyName); + } + + /** + * Return whether the model has no property change listeners that will + * be notified when the specified property has changed. + */ + public boolean hasNoPropertyChangeListeners(String propertyName) { + return ! this.hasAnyPropertyChangeListeners(propertyName); + } + + /** + * @see ChangeSupport#firePropertyChanged(PropertyChangeEvent) + */ + protected final boolean firePropertyChanged(PropertyChangeEvent event) { + return this.getChangeSupport().firePropertyChanged(event); + } + + /** + * @see ChangeSupport#firePropertyChanged(String, Object, Object) + */ + protected final boolean firePropertyChanged(String propertyName, Object oldValue, Object newValue) { + return this.getChangeSupport().firePropertyChanged(propertyName, oldValue, newValue); + } + + /** + * @see ChangeSupport#firePropertyChanged(String, int, int) + */ + protected final boolean firePropertyChanged(String propertyName, int oldValue, int newValue) { + return this.getChangeSupport().firePropertyChanged(propertyName, oldValue, newValue); + } + + /** + * @see ChangeSupport#firePropertyChanged(String, boolean, boolean) + */ + protected final boolean firePropertyChanged(String propertyName, boolean oldValue, boolean newValue) { + return this.getChangeSupport().firePropertyChanged(propertyName, oldValue, newValue); + } + + /** + * Implied null "old" value. + * @see #firePropertyChanged(String, Object, Object) + */ + protected final boolean firePropertyChanged(String propertyName, Object newValue) { + return this.firePropertyChanged(propertyName, null, newValue); + } + + + // ********** collection change support ********** + + /** + * @see ChangeSupport#addCollectionChangeListener(String, CollectionChangeListener) + */ + public void addCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + this.getChangeSupport().addCollectionChangeListener(collectionName, listener); + } + + /** + * @see ChangeSupport#removeCollectionChangeListener(String, CollectionChangeListener) + */ + public void removeCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + this.getChangeSupport().removeCollectionChangeListener(collectionName, listener); + } + + /** + * @see ChangeSupport#hasAnyCollectionChangeListeners(String) + */ + public boolean hasAnyCollectionChangeListeners(String collectionName) { + return (this.changeSupport != null) && this.changeSupport.hasAnyCollectionChangeListeners(collectionName); + } + + /** + * Return whether the model has no collection change listeners that will + * be notified when the specified collection has changed. + */ + public boolean hasNoCollectionChangeListeners(String collectionName) { + return ! this.hasAnyCollectionChangeListeners(collectionName); + } + + /** + * @see ChangeSupport#fireItemsAdded(CollectionAddEvent) + */ + protected final boolean fireItemsAdded(CollectionAddEvent event) { + return this.getChangeSupport().fireItemsAdded(event); + } + + /** + * @see ChangeSupport#fireItemsAdded(String, Collection) + */ + protected final boolean fireItemsAdded(String collectionName, Collection addedItems) { + return this.getChangeSupport().fireItemsAdded(collectionName, addedItems); + } + + /** + * @see ChangeSupport#fireItemAdded(String, Object) + */ + protected final void fireItemAdded(String collectionName, Object addedItem) { + this.getChangeSupport().fireItemAdded(collectionName, addedItem); + } + + /** + * @see ChangeSupport#fireItemsRemoved(CollectionRemoveEvent) + */ + protected final boolean fireItemsRemoved(CollectionRemoveEvent event) { + return this.getChangeSupport().fireItemsRemoved(event); + } + + /** + * @see ChangeSupport#fireItemsRemoved(String, Collection) + */ + protected final boolean fireItemsRemoved(String collectionName, Collection removedItems) { + return this.getChangeSupport().fireItemsRemoved(collectionName, removedItems); + } + + /** + * @see ChangeSupport#fireItemRemoved(String, Object) + */ + protected final void fireItemRemoved(String collectionName, Object removedItem) { + this.getChangeSupport().fireItemRemoved(collectionName, removedItem); + } + + /** + * @see ChangeSupport#fireCollectionCleared(CollectionClearEvent) + */ + protected final void fireCollectionCleared(CollectionClearEvent event) { + this.getChangeSupport().fireCollectionCleared(event); + } + + /** + * @see ChangeSupport#fireCollectionCleared(String) + */ + protected final void fireCollectionCleared(String collectionName) { + this.getChangeSupport().fireCollectionCleared(collectionName); + } + + protected final void fireCollectionChanged(CollectionChangeEvent event) { + this.getChangeSupport().fireCollectionChanged(event); + } + + protected final void fireCollectionChanged(String collectionName, Collection collection) { + this.getChangeSupport().fireCollectionChanged(collectionName, collection); + } + + /** + * @see ChangeSupport#addItemToCollection(Object, Collection, String) + */ + protected boolean addItemToCollection(E item, Collection collection, String collectionName) { + return this.getChangeSupport().addItemToCollection(item, collection, collectionName); + } + + /** + * @see ChangeSupport#addItemsToCollection(Object[], Collection, String) + */ + protected boolean addItemsToCollection(E[] items, Collection collection, String collectionName) { + return this.getChangeSupport().addItemsToCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#addItemsToCollection(Collection, Collection, String) + */ + protected boolean addItemsToCollection(Collection items, Collection collection, String collectionName) { + return this.getChangeSupport().addItemsToCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#addItemsToCollection(Iterable, Collection, String) + */ + protected boolean addItemsToCollection(Iterable items, Collection collection, String collectionName) { + return this.getChangeSupport().addItemsToCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#addItemsToCollection(Iterator, Collection, String) + */ + protected boolean addItemsToCollection(Iterator items, Collection collection, String collectionName) { + return this.getChangeSupport().addItemsToCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#removeItemFromCollection(Object, Collection, String) + */ + protected boolean removeItemFromCollection(Object item, Collection collection, String collectionName) { + return this.getChangeSupport().removeItemFromCollection(item, collection, collectionName); + } + + /** + * @see ChangeSupport#removeItemsFromCollection(Object[], Collection, String) + */ + protected boolean removeItemsFromCollection(Object[] items, Collection collection, String collectionName) { + return this.getChangeSupport().removeItemsFromCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#removeItemsFromCollection(Collection, Collection, String) + */ + protected boolean removeItemsFromCollection(Collection items, Collection collection, String collectionName) { + return this.getChangeSupport().removeItemsFromCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#removeItemsFromCollection(Iterable, Collection, String) + */ + protected boolean removeItemsFromCollection(Iterable items, Collection collection, String collectionName) { + return this.getChangeSupport().removeItemsFromCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#removeItemsFromCollection(Iterator, Collection, String) + */ + protected boolean removeItemsFromCollection(Iterator items, Collection collection, String collectionName) { + return this.getChangeSupport().removeItemsFromCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#retainItemsInCollection(Object[], Collection, String) + */ + protected boolean retainItemsInCollection(Object[] items, Collection collection, String collectionName) { + return this.getChangeSupport().retainItemsInCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#retainItemsInCollection(Collection, Collection, String) + */ + protected boolean retainItemsInCollection(Collection items, Collection collection, String collectionName) { + return this.getChangeSupport().retainItemsInCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#retainItemsInCollection(Iterable, Collection, String) + */ + protected boolean retainItemsInCollection(Iterable items, Collection collection, String collectionName) { + return this.getChangeSupport().retainItemsInCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#retainItemsInCollection(Iterator, Collection, String) + */ + protected boolean retainItemsInCollection(Iterator items, Collection collection, String collectionName) { + return this.getChangeSupport().retainItemsInCollection(items, collection, collectionName); + } + + /** + * @see ChangeSupport#clearCollection(Collection, String) + */ + protected boolean clearCollection(Collection collection, String collectionName) { + return this.getChangeSupport().clearCollection(collection, collectionName); + } + + /** + * @see ChangeSupport#synchronizeCollection(Collection, Collection, String) + */ + protected boolean synchronizeCollection(Collection newCollection, Collection collection, String collectionName) { + return this.getChangeSupport().synchronizeCollection(newCollection, collection, collectionName); + } + + /** + * @see ChangeSupport#synchronizeCollection(Iterable, Collection, String) + */ + protected boolean synchronizeCollection(Iterable newCollection, Collection collection, String collectionName) { + return this.getChangeSupport().synchronizeCollection(newCollection, collection, collectionName); + } + + /** + * @see ChangeSupport#synchronizeCollection(Iterator, Collection, String) + */ + protected boolean synchronizeCollection(Iterator newCollection, Collection collection, String collectionName) { + return this.getChangeSupport().synchronizeCollection(newCollection, collection, collectionName); + } + + + // ********** list change support ********** + + /** + * @see ChangeSupport#addListChangeListener(String, ListChangeListener) + */ + public void addListChangeListener(String listName, ListChangeListener listener) { + this.getChangeSupport().addListChangeListener(listName, listener); + } + + /** + * @see ChangeSupport#removeListChangeListener(String, ListChangeListener) + */ + public void removeListChangeListener(String listName, ListChangeListener listener) { + this.getChangeSupport().removeListChangeListener(listName, listener); + } + + /** + * @see ChangeSupport#hasAnyListChangeListeners(String) + */ + public boolean hasAnyListChangeListeners(String listName) { + return (this.changeSupport != null) && this.changeSupport.hasAnyListChangeListeners(listName); + } + + /** + * Return whether the model has no list change listeners that will + * be notified when the specified list has changed. + */ + public boolean hasNoListChangeListeners(String listName) { + return ! this.hasAnyListChangeListeners(listName); + } + + /** + * @see ChangeSupport#fireItemsAdded(ListAddEvent) + */ + protected final boolean fireItemsAdded(ListAddEvent event) { + return this.getChangeSupport().fireItemsAdded(event); + } + + /** + * @see ChangeSupport#fireItemsAdded(String, int, List) + */ + protected final boolean fireItemsAdded(String listName, int index, List addedItems) { + return this.getChangeSupport().fireItemsAdded(listName, index, addedItems); + } + + /** + * @see ChangeSupport#fireItemAdded(String, int, Object) + */ + protected final void fireItemAdded(String listName, int index, Object addedItem) { + this.getChangeSupport().fireItemAdded(listName, index, addedItem); + } + + /** + * @see ChangeSupport#fireItemsRemoved(ListRemoveEvent) + */ + protected final boolean fireItemsRemoved(ListRemoveEvent event) { + return this.getChangeSupport().fireItemsRemoved(event); + } + + /** + * @see ChangeSupport#fireItemsRemoved(String, int, List) + */ + protected final boolean fireItemsRemoved(String listName, int index, List removedItems) { + return this.getChangeSupport().fireItemsRemoved(listName, index, removedItems); + } + + /** + * @see ChangeSupport#fireItemRemoved(String, int, Object) + */ + protected final void fireItemRemoved(String listName, int index, Object removedItem) { + this.getChangeSupport().fireItemRemoved(listName, index, removedItem); + } + + /** + * @see ChangeSupport#fireItemsReplaced(ListReplaceEvent) + */ + protected final boolean fireItemsReplaced(ListReplaceEvent event) { + return this.getChangeSupport().fireItemsReplaced(event); + } + + /** + * @see ChangeSupport#fireItemsReplaced(String, int, List, List) + */ + protected final boolean fireItemsReplaced(String listName, int index, List newItems, List replacedItems) { + return this.getChangeSupport().fireItemsReplaced(listName, index, newItems, replacedItems); + } + + /** + * @see ChangeSupport#fireItemReplaced(String, int, Object, Object) + */ + protected final boolean fireItemReplaced(String listName, int index, Object newItem, Object replacedItem) { + return this.getChangeSupport().fireItemReplaced(listName, index, newItem, replacedItem); + } + + /** + * @see ChangeSupport#fireItemsMoved(ListMoveEvent) + */ + protected final boolean fireItemsMoved(ListMoveEvent event) { + return this.getChangeSupport().fireItemsMoved(event); + } + + /** + * @see ChangeSupport#fireItemsMoved(String, int, int, int) + */ + protected final boolean fireItemsMoved(String listName, int targetIndex, int sourceIndex, int length) { + return this.getChangeSupport().fireItemsMoved(listName, targetIndex, sourceIndex, length); + } + + /** + * @see ChangeSupport#fireItemMoved(String, int, int) + */ + protected final boolean fireItemMoved(String listName, int targetIndex, int sourceIndex) { + return this.getChangeSupport().fireItemMoved(listName, targetIndex, sourceIndex); + } + + /** + * @see ChangeSupport#fireListCleared(ListClearEvent) + */ + protected final void fireListCleared(ListClearEvent event) { + this.getChangeSupport().fireListCleared(event); + } + + /** + * @see ChangeSupport#fireListCleared(String) + */ + protected final void fireListCleared(String listName) { + this.getChangeSupport().fireListCleared(listName); + } + + protected final void fireListChanged(ListChangeEvent event) { + this.getChangeSupport().fireListChanged(event); + } + + protected final void fireListChanged(String listName, List list) { + this.getChangeSupport().fireListChanged(listName, list); + } + + /** + * @see ChangeSupport#addItemToList(int, Object, List, String) + */ + protected void addItemToList(int index, E item, List list, String listName) { + this.getChangeSupport().addItemToList(index, item, list, listName); + } + + /** + * @see ChangeSupport#addItemToList(Object, List, String) + */ + protected boolean addItemToList(E item, List list, String listName) { + return this.getChangeSupport().addItemToList(item, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(int, Object[], List, String) + */ + protected boolean addItemsToList(int index, E[] items, List list, String listName) { + return this.getChangeSupport().addItemsToList(index, items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(int, Collection, List, String) + */ + protected boolean addItemsToList(int index, Collection items, List list, String listName) { + return this.getChangeSupport().addItemsToList(index, items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(int, Iterable, List, String) + */ + protected boolean addItemsToList(int index, Iterable items, List list, String listName) { + return this.getChangeSupport().addItemsToList(index, items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(int, Iterator, List, String) + */ + protected boolean addItemsToList(int index, Iterator items, List list, String listName) { + return this.getChangeSupport().addItemsToList(index, items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(Object[], List, String) + */ + protected boolean addItemsToList(E[] items, List list, String listName) { + return this.getChangeSupport().addItemsToList(items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(Collection, List, String) + */ + protected boolean addItemsToList(Collection items, List list, String listName) { + return this.getChangeSupport().addItemsToList(items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(Iterable, List, String) + */ + protected boolean addItemsToList(Iterable items, List list, String listName) { + return this.getChangeSupport().addItemsToList(items, list, listName); + } + + /** + * @see ChangeSupport#addItemsToList(Iterator, List, String) + */ + protected boolean addItemsToList(Iterator items, List list, String listName) { + return this.getChangeSupport().addItemsToList(items, list, listName); + } + + /** + * @see ChangeSupport#removeItemFromList(int, List, String) + */ + protected E removeItemFromList(int index, List list, String listName) { + return this.getChangeSupport().removeItemFromList(index, list, listName); + } + + /** + * @see ChangeSupport#removeItemFromList(Object, List, String) + */ + protected boolean removeItemFromList(Object item, List list, String listName) { + return this.getChangeSupport().removeItemFromList(item, list, listName); + } + + /** + * @see ChangeSupport#removeRangeFromList(int, int, List, String) + */ + protected List removeRangeFromList(int beginIndex, int endIndex, List list, String listName) { + return this.getChangeSupport().removeRangeFromList(beginIndex, endIndex, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(int, List, String) + */ + protected List removeItemsFromList(int index, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(index, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(int, int, List, String) + */ + protected List removeItemsFromList(int index, int length, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(index, length, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(Object[], List, String) + */ + protected boolean removeItemsFromList(Object[] items, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(items, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(Collection, List, String) + */ + protected boolean removeItemsFromList(Collection items, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(items, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(Iterable, List, String) + */ + protected boolean removeItemsFromList(Iterable items, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(items, list, listName); + } + + /** + * @see ChangeSupport#removeItemsFromList(Iterator, List, String) + */ + protected boolean removeItemsFromList(Iterator items, List list, String listName) { + return this.getChangeSupport().removeItemsFromList(items, list, listName); + } + + /** + * @see ChangeSupport#retainItemsInList(Object[], List, String) + */ + protected boolean retainItemsInList(Object[] items, List list, String listName) { + return this.getChangeSupport().retainItemsInList(items, list, listName); + } + + /** + * @see ChangeSupport#retainItemsInList(Collection, List, String) + */ + protected boolean retainItemsInList(Collection items, List list, String listName) { + return this.getChangeSupport().retainItemsInList(items, list, listName); + } + + /** + * @see ChangeSupport#retainItemsInList(Iterable, List, String) + */ + protected boolean retainItemsInList(Iterable items, List list, String listName) { + return this.getChangeSupport().retainItemsInList(items, list, listName); + } + + /** + * @see ChangeSupport#retainItemsInList(Iterator, List, String) + */ + protected boolean retainItemsInList(Iterator items, List list, String listName) { + return this.getChangeSupport().retainItemsInList(items, list, listName); + } + + /** + * @see ChangeSupport#setItemInList(int, Object, List, String) + */ + protected E setItemInList(int index, E item, List list, String listName) { + return this.getChangeSupport().setItemInList(index, item, list, listName); + } + + /** + * @see ChangeSupport#replaceItemInList(Object, Object, List, String) + */ + protected int replaceItemInList(E oldItem, E newItem, List list, String listName) { + return this.getChangeSupport().replaceItemInList(oldItem, newItem, list, listName); + } + + /** + * @see ChangeSupport#setItemsInList(int, Object[], List, String) + */ + protected List setItemsInList(int index, E[] items, List list, String listName) { + return this.getChangeSupport().setItemsInList(index, items, list, listName); + } + + /** + * @see ChangeSupport#setItemsInList(int, List, List, String) + */ + protected List setItemsInList(int index, List items, List list, String listName) { + return this.getChangeSupport().setItemsInList(index, items, list, listName); + } + + /** + * @see ChangeSupport#moveItemsInList(int, int, int, List, String) + */ + protected void moveItemsInList(int targetIndex, int sourceIndex, int length, List list, String listName) { + this.getChangeSupport().moveItemsInList(targetIndex, sourceIndex, length, list, listName); + } + + /** + * @see ChangeSupport#moveItemInList(int, int, List, String) + */ + protected void moveItemInList(int targetIndex, int sourceIndex, List list, String listName) { + this.getChangeSupport().moveItemInList(targetIndex, sourceIndex, list, listName); + } + + /** + * @see ChangeSupport#moveItemInList(int, Object, List, String) + */ + protected void moveItemInList(int targetIndex, E item, List list, String listName) { + this.getChangeSupport().moveItemInList(targetIndex, item, list, listName); + } + + /** + * @see ChangeSupport#clearList(List, String) + */ + protected boolean clearList(List list, String listName) { + return this.getChangeSupport().clearList(list, listName); + } + + /** + * @see ChangeSupport#synchronizeList(List, List, String) + */ + protected boolean synchronizeList(List newList, List list, String listName) { + return this.getChangeSupport().synchronizeList(newList, list, listName); + } + + /** + * @see ChangeSupport#synchronizeList(Iterable, List, String) + */ + protected boolean synchronizeList(Iterable newList, List list, String listName) { + return this.getChangeSupport().synchronizeList(newList, list, listName); + } + + /** + * @see ChangeSupport#synchronizeList(Iterator, List, String) + */ + protected boolean synchronizeList(Iterator newList, List list, String listName) { + return this.getChangeSupport().synchronizeList(newList, list, listName); + } + + + // ********** tree change support ********** + + /** + * @see ChangeSupport#addTreeChangeListener(String, TreeChangeListener) + */ + public void addTreeChangeListener(String treeName, TreeChangeListener listener) { + this.getChangeSupport().addTreeChangeListener(treeName, listener); + } + + /** + * @see ChangeSupport#removeTreeChangeListener(String, TreeChangeListener) + */ + public void removeTreeChangeListener(String treeName, TreeChangeListener listener) { + this.getChangeSupport().removeTreeChangeListener(treeName, listener); + } + + /** + * @see ChangeSupport#hasAnyTreeChangeListeners(String) + */ + public boolean hasAnyTreeChangeListeners(String treeName) { + return (this.changeSupport != null) && this.changeSupport.hasAnyTreeChangeListeners(treeName); + } + + /** + * Return whether the model has no tree change listeners that will + * be notified when the specified tree has changed. + */ + public boolean hasNoTreeChangeListeners(String treeName) { + return ! this.hasAnyTreeChangeListeners(treeName); + } + + /** + * @see ChangeSupport#fireNodeAdded(TreeAddEvent) + */ + protected final void fireNodeAdded(TreeAddEvent event) { + this.getChangeSupport().fireNodeAdded(event); + } + + /** + * @see ChangeSupport#fireNodeAdded(String, List) + */ + protected final void fireNodeAdded(String treeName, List path) { + this.getChangeSupport().fireNodeAdded(treeName, path); + } + + /** + * @see ChangeSupport#fireNodeRemoved(TreeRemoveEvent) + */ + protected final void fireNodeRemoved(TreeRemoveEvent event) { + this.getChangeSupport().fireNodeRemoved(event); + } + + /** + * @see ChangeSupport#fireNodeRemoved(String, List) + */ + protected final void fireNodeRemoved(String treeName, List path) { + this.getChangeSupport().fireNodeRemoved(treeName, path); + } + + /** + * @see ChangeSupport#fireTreeCleared(TreeClearEvent) + */ + protected final void fireTreeCleared(TreeClearEvent event) { + this.getChangeSupport().fireTreeCleared(event); + } + + /** + * @see ChangeSupport#fireTreeCleared(String) + */ + protected final void fireTreeCleared(String treeName) { + this.getChangeSupport().fireTreeCleared(treeName); + } + + /** + * @see ChangeSupport#fireTreeChanged(TreeChangeEvent) + */ + protected final void fireTreeChanged(TreeChangeEvent event) { + this.getChangeSupport().fireTreeChanged(event); + } + + /** + * @see ChangeSupport#fireTreeChanged(String, Collection) + */ + protected final void fireTreeChanged(String treeName, Collection nodes) { + this.getChangeSupport().fireTreeChanged(treeName, nodes); + } + + + // ********** convenience methods ********** + + /** + * Return whether the specified values are equal, with the appropriate null checks. + * Convenience method for checking whether an attribute value has changed. + *

+ * Do not use this to determine whether to fire a change notification, + * {@link ChangeSupport} already does that. + */ + protected final boolean valuesAreEqual(Object value1, Object value2) { + return this.getChangeSupport().valuesAreEqual(value1, value2); + } + + /** + * @see #valuesAreEqual(Object, Object) + */ + protected final boolean attributeValueHasNotChanged(Object oldValue, Object newValue) { + return this.valuesAreEqual(oldValue, newValue); + } + + + /** + * Do not use this to determine whether to fire a change notification, + * {@link ChangeSupport} already does that. + *

+ * For example, after firing the change notification, you can use this method + * to decide if some other, related, piece of state needs to be synchronized + * with the state that just changed. + * + * @see ChangeSupport#valuesAreDifferent(Object, Object) + */ + protected final boolean valuesAreDifferent(Object value1, Object value2) { + return this.getChangeSupport().valuesAreDifferent(value1, value2); + } + + /** + * @see #valuesAreDifferent(Object, Object) + */ + protected final boolean attributeValueHasChanged(Object oldValue, Object newValue) { + return this.valuesAreDifferent(oldValue, newValue); + } + + + // ********** Object overrides ********** + + /** + * Although cloning models is usually not a Good Idea, + * we should at least support it properly. + */ + @Override + protected AbstractModel clone() throws CloneNotSupportedException { + AbstractModel clone = (AbstractModel) super.clone(); + // clear out change support - models do not share listeners + clone.changeSupport = null; + return clone; + } + + /** + * e.g. "ClassName[00-F3-EE-42](add'l info)" + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + StringTools.buildSimpleToStringOn(this, sb); + sb.append('('); + int len = sb.length(); + this.toString(sb); + if (sb.length() == len) { + sb.deleteCharAt(len - 1); + } else { + sb.append(')'); + } + return sb.toString(); + } + + /** + * This method is public so one model can call a nested abstract model's + * #toString(StringBuilder). + */ + public void toString(@SuppressWarnings("unused") StringBuilder sb) { + // subclasses should override this to do something a bit more helpful + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AspectChangeSupport.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AspectChangeSupport.java new file mode 100644 index 0000000000..d98f4438fd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AspectChangeSupport.java @@ -0,0 +1,349 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model; + +import java.util.Collection; +import java.util.EventListener; +import java.util.List; +import org.eclipse.jpt.common.utility.internal.ListenerList; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * This change support class will notify listeners whenever one of the source's + * aspects has changed. Only the aspect name is passed to the listener; no + * event is generated. This allows the listeners to delegate to the change + * support object verification that an aspect as actually changed. This is + * useful for simple things like setting dirty flags, blanket validation, and + * blanket sychronization; i.e. things that might be interested in the name + * of the aspect that changed but not so much how the aspect changed. + */ +public class AspectChangeSupport + extends ChangeSupport +{ + private static final long serialVersionUID = 1L; + protected static final Class LISTENER_CLASS = Listener.class; + + + public AspectChangeSupport(Model source, Listener listener) { + this(source); + this.addListener(listener); + } + + public AspectChangeSupport(Model source) { + super(source); + } + + protected void aspectChanged(String aspectName) { + Iterable listeners = this.getListeners(); + if (listeners != null) { + for (Listener listener : listeners) { + listener.aspectChanged(aspectName); + } + } + } + + public void addListener(Listener listener) { + this.addListener(LISTENER_CLASS, listener); + } + + public void removeListener(Listener listener) { + this.removeListener(LISTENER_CLASS, listener); + } + + private Iterable getListeners() { + ListenerList listenerList = this.getListenerList(); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private ListenerList getListenerList() { + return this.getListenerList(LISTENER_CLASS); + } + + + // ********** listener interface ********** + + /** + * Listener that will be notified of any aspect changes. + */ + public interface Listener extends EventListener { + + /** + * The specified aspect changed. + */ + void aspectChanged(String aspectName); + + } + + + // ********** state change support ********** + + @Override + public void fireStateChanged(StateChangeEvent event) { + super.fireStateChanged(event); + this.aspectChanged(null); + } + + @Override + public void fireStateChanged() { + super.fireStateChanged(); + this.aspectChanged(null); + } + + + // ********** property change support ********** + + @Override + protected void firePropertyChanged_(PropertyChangeEvent event) { + super.firePropertyChanged_(event); + this.aspectChanged(event.getPropertyName()); + } + + @Override + protected void firePropertyChanged_(String propertyName, Object oldValue, Object newValue) { + super.firePropertyChanged_(propertyName, oldValue, newValue); + this.aspectChanged(propertyName); + } + + @Override + protected void firePropertyChanged_(String propertyName, int oldValue, int newValue) { + super.firePropertyChanged_(propertyName, oldValue, newValue); + this.aspectChanged(propertyName); + } + + @Override + protected void firePropertyChanged_(String propertyName, boolean oldValue, boolean newValue) { + super.firePropertyChanged_(propertyName, oldValue, newValue); + this.aspectChanged(propertyName); + } + + + // ********** collection change support ********** + + @Override + protected void fireItemsAdded_(CollectionAddEvent event) { + super.fireItemsAdded_(event); + this.aspectChanged(event.getCollectionName()); + } + + @Override + protected void fireItemsAdded_(String collectionName, Collection addedItems) { + super.fireItemsAdded_(collectionName, addedItems); + this.aspectChanged(collectionName); + } + + @Override + public void fireItemAdded(String collectionName, Object addedItem) { + super.fireItemAdded(collectionName, addedItem); + this.aspectChanged(collectionName); + } + + @Override + protected void fireItemsRemoved_(CollectionRemoveEvent event) { + super.fireItemsRemoved_(event); + this.aspectChanged(event.getCollectionName()); + } + + @Override + protected void fireItemsRemoved_(String collectionName, Collection removedItems) { + super.fireItemsRemoved_(collectionName, removedItems); + this.aspectChanged(collectionName); + } + + @Override + public void fireItemRemoved(String collectionName, Object removedItem) { + super.fireItemRemoved(collectionName, removedItem); + this.aspectChanged(collectionName); + } + + @Override + public void fireCollectionCleared(CollectionClearEvent event) { + super.fireCollectionCleared(event); + this.aspectChanged(event.getCollectionName()); + } + + @Override + public void fireCollectionCleared(String collectionName) { + super.fireCollectionCleared(collectionName); + this.aspectChanged(collectionName); + } + + @Override + public void fireCollectionChanged(CollectionChangeEvent event) { + super.fireCollectionChanged(event); + this.aspectChanged(event.getCollectionName()); + } + + @Override + public void fireCollectionChanged(String collectionName, Collection collection) { + super.fireCollectionChanged(collectionName, collection); + this.aspectChanged(collectionName); + } + + + // ********** list change support ********** + + @Override + protected void fireItemsAdded_(ListAddEvent event) { + super.fireItemsAdded_(event); + this.aspectChanged(event.getListName()); + } + + @Override + protected void fireItemsAdded_(String listName, int index, List addedItems) { + super.fireItemsAdded_(listName, index, addedItems); + this.aspectChanged(listName); + } + + @Override + public void fireItemAdded(String listName, int index, Object addedItem) { + super.fireItemAdded(listName, index, addedItem); + this.aspectChanged(listName); + } + + @Override + protected void fireItemsRemoved_(ListRemoveEvent event) { + super.fireItemsRemoved_(event); + this.aspectChanged(event.getListName()); + } + + @Override + protected void fireItemsRemoved_(String listName, int index, List removedItems) { + super.fireItemsRemoved_(listName, index, removedItems); + this.aspectChanged(listName); + } + + @Override + public void fireItemRemoved(String listName, int index, Object removedItem) { + super.fireItemRemoved(listName, index, removedItem); + this.aspectChanged(listName); + } + + @Override + protected void fireItemsReplaced_(ListReplaceEvent event) { + super.fireItemsReplaced_(event); + this.aspectChanged(event.getListName()); + } + + @Override + protected void fireItemsReplaced_(String listName, int index, List newItems, List replacedItems) { + super.fireItemsReplaced_(listName, index, newItems, replacedItems); + this.aspectChanged(listName); + } + + @Override + protected void fireItemReplaced_(String listName, int index, Object newItem, Object replacedItem) { + super.fireItemReplaced_(listName, index, newItem, replacedItem); + this.aspectChanged(listName); + } + + @Override + protected void fireItemsMoved_(ListMoveEvent event) { + super.fireItemsMoved_(event); + this.aspectChanged(event.getListName()); + } + + @Override + protected void fireItemsMoved_(String listName, int targetIndex, int sourceIndex, int length) { + super.fireItemsMoved_(listName, targetIndex, sourceIndex, length); + this.aspectChanged(listName); + } + + @Override + public void fireListCleared(ListClearEvent event) { + super.fireListCleared(event); + this.aspectChanged(event.getListName()); + } + + @Override + public void fireListCleared(String listName) { + super.fireListCleared(listName); + this.aspectChanged(listName); + } + + @Override + public void fireListChanged(ListChangeEvent event) { + super.fireListChanged(event); + this.aspectChanged(event.getListName()); + } + + @Override + public void fireListChanged(String listName, List list) { + super.fireListChanged(listName, list); + this.aspectChanged(listName); + } + + + // ********** tree change support ********** + + @Override + public void fireNodeAdded(TreeAddEvent event) { + super.fireNodeAdded(event); + this.aspectChanged(event.getTreeName()); + } + + @Override + public void fireNodeAdded(String treeName, List path) { + super.fireNodeAdded(treeName, path); + this.aspectChanged(treeName); + } + + @Override + public void fireNodeRemoved(TreeRemoveEvent event) { + super.fireNodeRemoved(event); + this.aspectChanged(event.getTreeName()); + } + + @Override + public void fireNodeRemoved(String treeName, List path) { + super.fireNodeRemoved(treeName, path); + this.aspectChanged(treeName); + } + + @Override + public void fireTreeCleared(TreeClearEvent event) { + super.fireTreeCleared(event); + this.aspectChanged(event.getTreeName()); + } + + @Override + public void fireTreeCleared(String treeName) { + super.fireTreeCleared(treeName); + this.aspectChanged(treeName); + } + + @Override + public void fireTreeChanged(TreeChangeEvent event) { + super.fireTreeChanged(event); + this.aspectChanged(event.getTreeName()); + } + + @Override + public void fireTreeChanged(String treeName, Collection nodes) { + super.fireTreeChanged(treeName, nodes); + this.aspectChanged(treeName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/ChangeSupport.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/ChangeSupport.java new file mode 100644 index 0000000000..af4b952a2b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/ChangeSupport.java @@ -0,0 +1,2844 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EventListener; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.HashBag; +import org.eclipse.jpt.common.utility.internal.ListenerList; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Tools; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; + +/** + * Support object that can be used by implementors of the {@link Model} interface. + * It provides for state, property, collection, list, and tree change notifications to + * listeners. + *

+ * NB1: There is lots of copy-n-paste code in this class. Nearly all of this duplication + * is an effort to prevent the unnecessary creation of new objects (typically event + * objects). Since many events are fired when there are no listeners, we postpone + * the creation of event objects until we know we have interested listeners. + * Most methods have the "non-duplicated" version of the method body commented + * out at the top of the current method body. + * The hope was that this class would prove to be fairly static and the duplicated + * code would not prove onerous; but that has not proven to be + * the case, as we have added support for "state" changes, "dirty" notification, + * and custom "notifiers", with more to come, I'm sure.... ~bjv + *

+ * NB2: This class will check to see if, during the firing of events, a listener + * on the original, cloned, list of listeners has been removed from the master + * list of listeners before it is notified. If the listener has been removed + * "concurrently" it will not be notified. + *

+ * NB3: Any listener that is added during the firing of events will not be + * also notified. This is a bit inconsistent with NB2, but seems reasonable + * since any added listener should already be in synch with the model. + *

+ * NB4: This class is serializable, but it will only write out listeners that + * are also serializable while silently leaving behind listeners that are not. + * + * @see Model + * @see AbstractModel + */ +public class ChangeSupport + implements Serializable +{ + /** The object to be provided as the "source" for any generated events. */ + protected final Model source; + + /** Associate aspect names to class-specific listener lists. */ + private AspectListenerListPair[] aspectListenerListPairs = EMPTY_ASPECT_LISTENER_LIST_PAIR_ARRAY; + private static final AspectListenerListPair[] EMPTY_ASPECT_LISTENER_LIST_PAIR_ARRAY = new AspectListenerListPair[0]; + + private static final long serialVersionUID = 1L; + + + // ********** constructor ********** + + /** + * Construct support for the specified source of change events. + * The source cannot be null. + */ + public ChangeSupport(Model source) { + super(); + if (source == null) { + throw new NullPointerException(); + } + this.source = source; + } + + + // ********** internal implementation ********** + + /** + * Add a listener that listens to all the events of the specified type and + * carrying the specified aspect name. + * Neither the aspect name nor the listener can be null. + */ + protected synchronized void addListener(Class listenerClass, String aspectName, L listener) { + ListenerList aspectListenerList = this.getListenerList(listenerClass, aspectName); + if (aspectListenerList == null) { + this.aspectListenerListPairs = ArrayTools.add(this.aspectListenerListPairs, new SimpleAspectListenerListPair(listenerClass, aspectName, listener)); + } else { + aspectListenerList.add(listener); + } + } + + /** + * Add a listener that listens to all the events of the specified type. + * The listener cannot be null. + */ + protected synchronized void addListener(Class listenerClass, L listener) { + ListenerList listenerList = this.getListenerList(listenerClass); + if (listenerList == null) { + this.aspectListenerListPairs = ArrayTools.add(this.aspectListenerListPairs, new NullAspectListenerListPair(listenerClass, listener)); + } else { + listenerList.add(listener); + } + } + + /** + * Remove a listener that has been registered for all the + * events of the specified type and carrying the specified aspect name. + * Neither the aspect name nor the listener can be null. + */ + protected synchronized void removeListener(Class listenerClass, String aspectName, L listener) { + ListenerList aspectListenerList = this.getListenerList(listenerClass, aspectName); + if (aspectListenerList == null) { + throw new IllegalArgumentException("unregistered listener: " + listener); //$NON-NLS-1$ + } + aspectListenerList.remove(listener); // leave the pair, even if the listener list is empty? + } + + /** + * Remove a listener that has been registered for all the events of the specified type. + * The listener cannot be null. + */ + protected synchronized void removeListener(Class listenerClass, L listener) { + ListenerList listenerList = this.getListenerList(listenerClass); + if (listenerList == null) { + throw new IllegalArgumentException("unregistered listener: " + listener); //$NON-NLS-1$ + } + listenerList.remove(listener); // leave the pair, even if the listener list is empty? + } + + /** + * Return the listener list for the specified listener class and aspect name. + * Return null if the listener list is not present. + * The aspect name cannot be null. + */ + protected ListenerList getListenerList(Class listenerClass, String aspectName) { + // put in a null check to simplify calling code + if (aspectName == null) { + throw new NullPointerException(); + } + return this.getListenerList_(listenerClass, aspectName); + } + + /** + * Return the listener list for the specified listener class. + * Return null if the listener list is not present. + */ + protected ListenerList getListenerList(Class listenerClass) { + return this.getListenerList_(listenerClass, null); + } + + /** + * Return the listener list for the specified listener class and aspect name. + * Return null if the listener list is not present. + */ + protected synchronized ListenerList getListenerList_(Class listenerClass, String aspectName) { + for (AspectListenerListPair pair : this.aspectListenerListPairs) { + if (pair.matches(listenerClass, aspectName)) { + @SuppressWarnings("unchecked") ListenerList aspectListenerList = (ListenerList) pair.listenerList; + return aspectListenerList; + } + } + return null; + } + + /** + * Return whether there are any listeners for the specified listener class + * and aspect name. + */ + protected boolean hasAnyListeners(Class listenerClass, String aspectName) { + ListenerList aspectListenerList = this.getListenerList(listenerClass, aspectName); + if ((aspectListenerList != null) && ! aspectListenerList.isEmpty()) { + return true; + } + return this.hasAnyChangeListeners(); // check for any general purpose listeners + } + + /** + * Return whether there are no listeners for the specified listener class + * and aspect name. + */ + protected boolean hasNoListeners(Class listenerClass, String aspectName) { + return ! this.hasAnyListeners(listenerClass, aspectName); + } + + /** + * Return whether there are any listeners for the specified listener class. + */ + protected boolean hasAnyListeners(Class listenerClass) { + ListenerList aspectListenerList = this.getListenerList(listenerClass); + if ((aspectListenerList != null) && ! aspectListenerList.isEmpty()) { + return true; + } + // check for any general purpose listeners (unless that's what we're already doing) + return (listenerClass == this.getChangeListenerClass()) ? false : this.hasAnyChangeListeners(); + } + + /** + * Return whether there are no listeners for the specified listener class. + */ + protected boolean hasNoListeners(Class listenerClass) { + return ! this.hasAnyListeners(listenerClass); + } + + + // ********** general purpose change support ********** + + /** + * Subclasses that add other types of listeners should override this method + * to return the extension to ChangeListener that also extends whatever new + * listener types are supported. + */ + @SuppressWarnings("unchecked") + protected Class getChangeListenerClass() { + // not sure why I need to cast here... + return (Class) CHANGE_LISTENER_CLASS; + } + + protected static final Class CHANGE_LISTENER_CLASS = ChangeListener.class; + + /** + * Add a general purpose listener that listens to all events, + * regardless of the aspect name associated with that event. + * The listener cannot be null. + */ + public void addChangeListener(ChangeListener listener) { + this.addListener(this.getChangeListenerClass(), listener); + } + + /** + * Remove a general purpose listener. + * The listener cannot be null. + */ + public void removeChangeListener(ChangeListener listener) { + this.removeListener(this.getChangeListenerClass(), listener); + } + + /** + * Return whether there are any general purpose listeners that will be + * notified of any changes. + */ + public boolean hasAnyChangeListeners() { + return this.hasAnyListeners(this.getChangeListenerClass()); + } + + private ListenerList getChangeListenerList() { + return this.getListenerList(CHANGE_LISTENER_CLASS); + } + + private Iterable getChangeListeners() { + ListenerList listenerList = this.getChangeListenerList(); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasChangeListener(ChangeListener listener) { + return CollectionTools.contains(this.getChangeListeners(), listener); + } + + + // ********** state change support ********** + + protected static final Class STATE_CHANGE_LISTENER_CLASS = StateChangeListener.class; + + /** + * Add a state change listener. + */ + public void addStateChangeListener(StateChangeListener listener) { + this.addListener(STATE_CHANGE_LISTENER_CLASS, listener); + } + + /** + * Remove a state change listener. + */ + public void removeStateChangeListener(StateChangeListener listener) { + this.removeListener(STATE_CHANGE_LISTENER_CLASS, listener); + } + + /** + * Return whether there are any state change listeners. + */ + public boolean hasAnyStateChangeListeners() { + return this.hasAnyListeners(STATE_CHANGE_LISTENER_CLASS); + } + + private ListenerList getStateChangeListenerList() { + return this.getListenerList(STATE_CHANGE_LISTENER_CLASS); + } + + private Iterable getStateChangeListeners() { + ListenerList listenerList = this.getStateChangeListenerList(); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasStateChangeListener(StateChangeListener listener) { + return CollectionTools.contains(this.getStateChangeListeners(), listener); + } + + /** + * Fire the specified state change event to any registered listeners. + */ + public void fireStateChanged(StateChangeEvent event) { + Iterable listeners = this.getStateChangeListeners(); + if (listeners != null) { + for (StateChangeListener listener : listeners) { + if (this.hasStateChangeListener(listener)) { // verify listener is still listening + listener.stateChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.stateChanged(event); + } + } + } + } + + /** + * Report a generic state change event to any registered state change + * listeners. + */ + public void fireStateChanged() { +// this.fireStateChanged(new StateChangeEvent(this.source)); + StateChangeEvent event = null; + Iterable listeners = this.getStateChangeListeners(); + if (listeners != null) { + for (StateChangeListener listener : listeners) { + if (this.hasStateChangeListener(listener)) { // verify listener is still listening + if (event == null) { + event = new StateChangeEvent(this.source); + } + listener.stateChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new StateChangeEvent(this.source); + } + changeListener.stateChanged(event); + } + } + } + } + + + // ********** property change support ********** + + protected static final Class PROPERTY_CHANGE_LISTENER_CLASS = PropertyChangeListener.class; + + /** + * Add a property change listener for the specified property. The listener + * will be notified only for changes to the specified property. + */ + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + this.addListener(PROPERTY_CHANGE_LISTENER_CLASS, propertyName, listener); + } + + /** + * Remove a property change listener that was registered for a specific property. + */ + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + this.removeListener(PROPERTY_CHANGE_LISTENER_CLASS, propertyName, listener); + } + + /** + * Return whether there are any property change listeners that will + * be notified when the specified property has changed. + */ + public boolean hasAnyPropertyChangeListeners(String propertyName) { + return this.hasAnyListeners(PROPERTY_CHANGE_LISTENER_CLASS, propertyName); + } + + private ListenerList getPropertyChangeListenerList(String propertyName) { + return this.getListenerList(PROPERTY_CHANGE_LISTENER_CLASS, propertyName); + } + + private Iterable getPropertyChangeListeners(String propertyName) { + ListenerList listenerList = this.getPropertyChangeListenerList(propertyName); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + return CollectionTools.contains(this.getPropertyChangeListeners(propertyName), listener); + } + + /** + * Fire the specified property change event to any registered listeners. + * No event is fired if the specified event's old and new values are the same; + * this includes when both values are null. Use a state change event + * for general purpose notification of changes. + * Return whether the old and new values are different. + */ + public boolean firePropertyChanged(PropertyChangeEvent event) { + if (this.valuesAreDifferent(event.getOldValue(), event.getNewValue())) { + this.firePropertyChanged_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event's old and new values are different + */ + protected void firePropertyChanged_(PropertyChangeEvent event) { + String propertyName = event.getPropertyName(); + Iterable listeners = this.getPropertyChangeListeners(propertyName); + if (listeners != null) { + for (PropertyChangeListener listener : listeners) { + if (this.hasPropertyChangeListener(propertyName, listener)) { // verify listener is still listening + listener.propertyChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.propertyChanged(event); + } + } + } + } + + /** + * Report a bound property update to any registered property change listeners. + * No event is fired if the specified old and new values are the same; + * this includes when both values are null. Use a state change event + * for general purpose notification of changes. + * Return whether the old and new values are different. + */ + public boolean firePropertyChanged(String propertyName, Object oldValue, Object newValue) { +// return this.firePropertyChanged(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue)); + if (this.valuesAreDifferent(oldValue, newValue)) { + this.firePropertyChanged_(propertyName, oldValue, newValue); + return true; + } + return false; + } + + /** + * pre-condition: the specified old and new values are different + */ + protected void firePropertyChanged_(String propertyName, Object oldValue, Object newValue) { + PropertyChangeEvent event = null; + Iterable listeners = this.getPropertyChangeListeners(propertyName); + if (listeners != null) { + for (PropertyChangeListener listener : listeners) { + if (this.hasPropertyChangeListener(propertyName, listener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, oldValue, newValue); + } + listener.propertyChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, oldValue, newValue); + } + changeListener.propertyChanged(event); + } + } + } + } + + /** + * Report an int bound property update to any registered listeners. + * No event is fired if the specified old and new values are equal. + * Return whether the old and new values are different. + *

+ * This is merely a convenience wrapper around the more general method + * {@link #firePropertyChanged(String, Object, Object)}. + */ + public boolean firePropertyChanged(String propertyName, int oldValue, int newValue) { +// return this.firePropertyChanged(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); + if (oldValue != newValue) { + this.firePropertyChanged_(propertyName, oldValue, newValue); + return true; + } + return false; + } + + /** + * pre-condition: the specified old and new values are different + */ + protected void firePropertyChanged_(String propertyName, int oldValue, int newValue) { + PropertyChangeEvent event = null; + Iterable listeners = this.getPropertyChangeListeners(propertyName); + if (listeners != null) { + for (PropertyChangeListener listener : listeners) { + if (this.hasPropertyChangeListener(propertyName, listener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); + } + listener.propertyChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); + } + changeListener.propertyChanged(event); + } + } + } + } + + /** + * Report a boolean bound property update to any registered listeners. + * No event is fired if the specified old and new values are equal. + * Return whether the old and new values are different. + *

+ * This is merely a convenience wrapper around the more general method + * {@link #firePropertyChanged(String, Object, Object)}. + */ + public boolean firePropertyChanged(String propertyName, boolean oldValue, boolean newValue) { +// return this.firePropertyChanged(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); + if (oldValue != newValue) { + this.firePropertyChanged_(propertyName, oldValue, newValue); + return true; + } + return false; + } + + /** + * pre-condition: the specified old and new values are different + */ + protected void firePropertyChanged_(String propertyName, boolean oldValue, boolean newValue) { + PropertyChangeEvent event = null; + Iterable listeners = this.getPropertyChangeListeners(propertyName); + if (listeners != null) { + for (PropertyChangeListener listener : listeners) { + if (this.hasPropertyChangeListener(propertyName, listener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); + } + listener.propertyChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new PropertyChangeEvent(this.source, propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); + } + changeListener.propertyChanged(event); + } + } + } + } + + + // ********** collection change support ********** + + protected static final Class COLLECTION_CHANGE_LISTENER_CLASS = CollectionChangeListener.class; + + /** + * Add a collection change listener for the specified collection. The listener + * will be notified only for changes to the specified collection. + */ + public void addCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + this.addListener(COLLECTION_CHANGE_LISTENER_CLASS, collectionName, listener); + } + + /** + * Remove a collection change listener that was registered for a specific collection. + */ + public void removeCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + this.removeListener(COLLECTION_CHANGE_LISTENER_CLASS, collectionName, listener); + } + + /** + * Return whether there are any collection change listeners that will + * be notified when the specified collection has changed. + */ + public boolean hasAnyCollectionChangeListeners(String collectionName) { + return this.hasAnyListeners(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + } + + private ListenerList getCollectionChangeListenerList(String collectionName) { + return this.getListenerList(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + } + + private Iterable getCollectionChangeListeners(String collectionName) { + ListenerList listenerList = this.getCollectionChangeListenerList(collectionName); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + return CollectionTools.contains(this.getCollectionChangeListeners(collectionName), listener); + } + + /** + * Report a bound collection update to any registered listeners. + * Return whether the event has any items. + */ + public boolean fireItemsAdded(CollectionAddEvent event) { + if (event.getItemsSize() != 0) { + this.fireItemsAdded_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event contains items + */ + protected void fireItemsAdded_(CollectionAddEvent event) { + String collectionName = event.getCollectionName(); + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + * Return whether there are any added items. + */ + public boolean fireItemsAdded(String collectionName, Collection addedItems) { +// return this.fireItemsAdded(new CollectionAddEvent(this.source, collectionName, addedItems)); + if ( ! addedItems.isEmpty()) { + this.fireItemsAdded_(collectionName, addedItems); + return true; + } + return false; + } + + /** + * pre-condition: 'addedItems' is not empty + */ + protected void fireItemsAdded_(String collectionName, Collection addedItems) { + CollectionAddEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionAddEvent(this.source, collectionName, addedItems); + } + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionAddEvent(this.source, collectionName, addedItems); + } + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireItemAdded(String collectionName, Object addedItem) { +// this.fireItemsAdded(collectionName, Collections.singleton(addedItem)); + + CollectionAddEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionAddEvent(this.source, collectionName, addedItem); + } + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionAddEvent(this.source, collectionName, addedItem); + } + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + * Return whether the event has any items. + */ + public boolean fireItemsRemoved(CollectionRemoveEvent event) { + if (event.getItemsSize() != 0) { + this.fireItemsRemoved_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event contains items + */ + protected void fireItemsRemoved_(CollectionRemoveEvent event) { + String collectionName = event.getCollectionName(); + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + * Return whether there are any removed items. + */ + public boolean fireItemsRemoved(String collectionName, Collection removedItems) { +// return this.fireItemsRemoved(new CollectionRemoveEvent(this.source, collectionName, removedItems)); + if ( ! removedItems.isEmpty()) { + this.fireItemsRemoved_(collectionName, removedItems); + return true; + } + return false; + } + + /** + * pre-condition: 'removedItems' is not empty + */ + protected void fireItemsRemoved_(String collectionName, Collection removedItems) { + CollectionRemoveEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionRemoveEvent(this.source, collectionName, removedItems); + } + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionRemoveEvent(this.source, collectionName, removedItems); + } + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireItemRemoved(String collectionName, Object removedItem) { +// this.fireItemsRemoved(collectionName, Collections.singleton(removedItem)); + + CollectionRemoveEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionRemoveEvent(this.source, collectionName, removedItem); + } + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionRemoveEvent(this.source, collectionName, removedItem); + } + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireCollectionCleared(CollectionClearEvent event) { + String collectionName = event.getCollectionName(); + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + listener.collectionCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.collectionCleared(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireCollectionCleared(String collectionName) { +// this.fireCollectionCleared(new CollectionClearEvent(this.source, collectionName)); + + CollectionClearEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionClearEvent(this.source, collectionName); + } + listener.collectionCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionClearEvent(this.source, collectionName); + } + changeListener.collectionCleared(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireCollectionChanged(CollectionChangeEvent event) { + String collectionName = event.getCollectionName(); + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + listener.collectionChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.collectionChanged(event); + } + } + } + } + + /** + * Report a bound collection update to any registered listeners. + */ + public void fireCollectionChanged(String collectionName, Collection collection) { +// this.fireCollectionChanged(new CollectionChangeEvent(this.source, collectionName, collection)); + + CollectionChangeEvent event = null; + Iterable listeners = this.getCollectionChangeListeners(collectionName); + if (listeners != null) { + for (CollectionChangeListener listener : listeners) { + if (this.hasCollectionChangeListener(collectionName, listener)) { // verify listener is still listening + if (event == null) { + event = new CollectionChangeEvent(this.source, collectionName, collection); + } + listener.collectionChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new CollectionChangeEvent(this.source, collectionName, collection); + } + changeListener.collectionChanged(event); + } + } + } + } + + /** + * Add the specified item to the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#add(Object) + */ + public boolean addItemToCollection(E item, Collection collection, String collectionName) { + if (collection.add(item)) { + this.fireItemAdded(collectionName, item); + return true; + } + return false; + } + + /** + * Add the specified items to the specified bound collection + * and fire the appropriate event if necessary. + * Return whether collection changed. + * @see Collection#addAll(Collection) + */ + public boolean addItemsToCollection(E[] items, Collection collection, String collectionName) { + return (items.length != 0) + && this.addItemsToCollection_(new ArrayIterator(items), collection, collectionName); + } + + /** + * Add the specified items to the specified bound collection + * and fire the appropriate event if necessary. + * Return whether collection changed. + * @see Collection#addAll(Collection) + */ + public boolean addItemsToCollection(Collection items, Collection collection, String collectionName) { + return ( ! items.isEmpty()) + && this.addItemsToCollection_(items.iterator(), collection, collectionName); + } + + /** + * Add the specified items to the specified bound collection + * and fire the appropriate event if necessary. + * Return whether collection changed. + * @see Collection#addAll(Collection) + */ + public boolean addItemsToCollection(Iterable items, Collection collection, String collectionName) { + return this.addItemsToCollection(items.iterator(), collection, collectionName); + } + + /** + * Add the specified items to the specified bound collection + * and fire the appropriate event if necessary. + * Return whether collection changed. + * @see Collection#addAll(Collection) + */ + public boolean addItemsToCollection(Iterator items, Collection collection, String collectionName) { + return items.hasNext() + && this.addItemsToCollection_(items, collection, collectionName); + } + + /** + * no empty check + */ + protected boolean addItemsToCollection_(Iterator items, Collection collection, String collectionName) { + Collection addedItems = null; + while (items.hasNext()) { + E item = items.next(); + if (collection.add(item)) { + if (addedItems == null) { + addedItems = new ArrayList(); + } + addedItems.add(item); + } + } + if (addedItems != null) { + this.fireItemsAdded_(collectionName, addedItems); + return true; + } + return false; + } + + /** + * Remove the specified item from the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#remove(Object) + */ + public boolean removeItemFromCollection(Object item, Collection collection, String collectionName) { + if (collection.remove(item)) { + this.fireItemRemoved(collectionName, item); + return true; + } + return false; + } + + /** + * Remove the specified items from the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#removeAll(Collection) + */ + public boolean removeItemsFromCollection(Object[] items, Collection collection, String collectionName) { + return (items.length != 0) + && ( ! collection.isEmpty()) + && this.removeItemsFromCollection_(new ArrayIterator(items), collection, collectionName); + } + + /** + * Remove the specified items from the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#removeAll(Collection) + */ + public boolean removeItemsFromCollection(Collection items, Collection collection, String collectionName) { + return ( ! items.isEmpty()) + && ( ! collection.isEmpty()) + && this.removeItemsFromCollection_(items.iterator(), collection, collectionName); + } + + /** + * Remove the specified items from the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#removeAll(Collection) + */ + public boolean removeItemsFromCollection(Iterable items, Collection collection, String collectionName) { + return this.removeItemsFromCollection(items.iterator(), collection, collectionName); + } + + /** + * Remove the specified items from the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#removeAll(Collection) + */ + public boolean removeItemsFromCollection(Iterator items, Collection collection, String collectionName) { + return items.hasNext() + && ( ! collection.isEmpty()) + && this.removeItemsFromCollection_(items, collection, collectionName); + } + + /** + * no empty checks + */ + protected boolean removeItemsFromCollection_(Iterator items, Collection collection, String collectionName) { + HashBag removedItems = CollectionTools.collection(items); + removedItems.retainAll(collection); + boolean changed = collection.removeAll(removedItems); + + if ( ! removedItems.isEmpty()) { + this.fireItemsRemoved_(collectionName, removedItems); + } + return changed; + } + + /** + * Retain the specified items in the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#retainAll(Collection) + */ + public boolean retainItemsInCollection(Object[] items, Collection collection, String collectionName) { + if (collection.isEmpty()) { + return false; + } + if (items.length == 0) { + return this.clearCollection_(collection, collectionName); + } + return this.retainItemsInCollection_(new ArrayIterator(items), collection, collectionName); + } + + /** + * Retain the specified items in the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#retainAll(Collection) + */ + public boolean retainItemsInCollection(Collection items, Collection collection, String collectionName) { + if (collection.isEmpty()) { + return false; + } + if (items.isEmpty()) { + return this.clearCollection_(collection, collectionName); + } + return this.retainItemsInCollection_(items.iterator(), collection, collectionName); + } + + /** + * Retain the specified items in the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#retainAll(Collection) + */ + public boolean retainItemsInCollection(Iterable items, Collection collection, String collectionName) { + return this.retainItemsInCollection(items.iterator(), collection, collectionName); + } + + /** + * Retain the specified items in the specified bound collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#retainAll(Collection) + */ + public boolean retainItemsInCollection(Iterator items, Collection collection, String collectionName) { + if (collection.isEmpty()) { + return false; + } + if ( ! items.hasNext()) { + return this.clearCollection_(collection, collectionName); + } + return this.retainItemsInCollection_(items, collection, collectionName); + } + + /** + * no empty checks + */ + protected boolean retainItemsInCollection_(Iterator items, Collection collection, String collectionName) { + HashBag retainedItems = CollectionTools.collection(items); + HashBag removedItems = CollectionTools.collection(collection); + removedItems.removeAll(retainedItems); + boolean changed = collection.retainAll(retainedItems); + + if ( ! removedItems.isEmpty()) { + this.fireItemsRemoved_(collectionName, removedItems); + } + return changed; + } + + /** + * Clear the entire collection + * and fire the appropriate event if necessary. + * Return whether the collection changed. + * @see Collection#clear() + */ + public boolean clearCollection(Collection collection, String collectionName) { + if (collection.isEmpty()) { + return false; + } + return this.clearCollection_(collection, collectionName); + } + + /** + * no empty check + */ + protected boolean clearCollection_(Collection collection, String collectionName) { + collection.clear(); + this.fireCollectionCleared(collectionName); + return true; + } + + /** + * Synchronize the collection with the specified new collection, + * making a minimum number of removes and adds. + * Return whether the collection changed. + */ + public boolean synchronizeCollection(Collection newCollection, Collection collection, String collectionName) { + if (newCollection.isEmpty()) { + return this.clearCollection(collection, collectionName); + } + + if (collection.isEmpty()) { + return this.addItemsToCollection_(newCollection.iterator(), collection, collectionName); + } + + return this.synchronizeCollection_(newCollection, collection, collectionName); + } + + /** + * Synchronize the collection with the specified new collection, + * making a minimum number of removes and adds. + * Return whether the collection changed. + */ + public boolean synchronizeCollection(Iterable newCollection, Collection collection, String collectionName) { + return this.synchronizeCollection(newCollection.iterator(), collection, collectionName); + } + + /** + * Synchronize the collection with the specified new collection, + * making a minimum number of removes and adds. + * Return whether the collection changed. + */ + public boolean synchronizeCollection(Iterator newCollection, Collection collection, String collectionName) { + if ( ! newCollection.hasNext()) { + return this.clearCollection(collection, collectionName); + } + + if (collection.isEmpty()) { + return this.addItemsToCollection_(newCollection, collection, collectionName); + } + + return this.synchronizeCollection_(CollectionTools.collection(newCollection), collection, collectionName); + } + + /** + * no empty checks + */ + protected boolean synchronizeCollection_(Collection newCollection, Collection collection, String collectionName) { + boolean changed = false; + Collection removeItems = new HashBag(collection); + removeItems.removeAll(newCollection); + changed |= this.removeItemsFromCollection(removeItems, collection, collectionName); + + Collection addItems = new HashBag(newCollection); + addItems.removeAll(collection); + changed |= this.addItemsToCollection(addItems, collection, collectionName); + + return changed; + } + + + // ********** list change support ********** + + protected static final Class LIST_CHANGE_LISTENER_CLASS = ListChangeListener.class; + + /** + * Add a list change listener for the specified list. The listener + * will be notified only for changes to the specified list. + */ + public void addListChangeListener(String listName, ListChangeListener listener) { + this.addListener(LIST_CHANGE_LISTENER_CLASS, listName, listener); + } + + /** + * Remove a list change listener that was registered for a specific list. + */ + public void removeListChangeListener(String listName, ListChangeListener listener) { + this.removeListener(LIST_CHANGE_LISTENER_CLASS, listName, listener); + } + + /** + * Return whether there are any list change listeners that will + * be notified when the specified list has changed. + */ + public boolean hasAnyListChangeListeners(String listName) { + return this.hasAnyListeners(LIST_CHANGE_LISTENER_CLASS, listName); + } + + private ListenerList getListChangeListenerList(String listName) { + return this.getListenerList(LIST_CHANGE_LISTENER_CLASS, listName); + } + + private Iterable getListChangeListeners(String listName) { + ListenerList listenerList = this.getListChangeListenerList(listName); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasListChangeListener(String listName, ListChangeListener listener) { + return CollectionTools.contains(this.getListChangeListeners(listName), listener); + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any added items. + */ + public boolean fireItemsAdded(ListAddEvent event) { + if (event.getItemsSize() != 0) { + this.fireItemsAdded_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event contains items + */ + protected void fireItemsAdded_(ListAddEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any added items. + */ + public boolean fireItemsAdded(String listName, int index, List addedItems) { +// return this.fireItemsAdded(new ListAddEvent(this.source, listName, index, addedItems)); + if ( ! addedItems.isEmpty()) { + this.fireItemsAdded_(listName, index, addedItems); + return true; + } + return false; + } + + /** + * pre-condition: 'addedItems' is not empty + */ + protected void fireItemsAdded_(String listName, int index, List addedItems) { + ListAddEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListAddEvent(this.source, listName, index, addedItems); + } + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListAddEvent(this.source, listName, index, addedItems); + } + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireItemAdded(String listName, int index, Object addedItem) { +// this.fireItemsAdded(listName, index, Collections.singletonList(addedItem)); + + ListAddEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListAddEvent(this.source, listName, index, addedItem); + } + listener.itemsAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListAddEvent(this.source, listName, index, addedItem); + } + changeListener.itemsAdded(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any removed items. + */ + public boolean fireItemsRemoved(ListRemoveEvent event) { + if (event.getItemsSize() != 0) { + this.fireItemsRemoved_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event contains items + */ + protected void fireItemsRemoved_(ListRemoveEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any removed items. + */ + public boolean fireItemsRemoved(String listName, int index, List removedItems) { +// return this.fireItemsRemoved(new ListRemoveEvent(this.source, listName, index, removedItems)); + if ( ! removedItems.isEmpty()) { + this.fireItemsRemoved_(listName, index, removedItems); + return true; + } + return false; + } + + /** + * pre-condition: 'removedItems' is not empty + */ + protected void fireItemsRemoved_(String listName, int index, List removedItems) { + ListRemoveEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListRemoveEvent(this.source, listName, index, removedItems); + } + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListRemoveEvent(this.source, listName, index, removedItems); + } + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireItemRemoved(String listName, int index, Object removedItem) { +// this.fireItemsRemoved(listName, index, Collections.singletonList(removedItem)); + + ListRemoveEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListRemoveEvent(this.source, listName, index, removedItem); + } + listener.itemsRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListRemoveEvent(this.source, listName, index, removedItem); + } + changeListener.itemsRemoved(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any replaced items. + */ + public boolean fireItemsReplaced(ListReplaceEvent event) { + if ((event.getItemsSize() != 0) && this.elementsAreDifferent(event.getNewItems(), event.getOldItems())) { + this.fireItemsReplaced_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event contains new items + */ + protected void fireItemsReplaced_(ListReplaceEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.itemsReplaced(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsReplaced(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any replaced items. + */ + public boolean fireItemsReplaced(String listName, int index, List newItems, List oldItems) { +// return this.fireItemsReplaced(new ListReplaceEvent(this.source, listName, index, newItems, oldItems)); + if (( ! newItems.isEmpty()) && this.elementsAreDifferent(newItems, oldItems)) { + this.fireItemsReplaced_(listName, index, newItems, oldItems); + return true; + } + return false; + } + + /** + * pre-condition: 'newItems' is not empty and unequal to 'oldItems' + */ + protected void fireItemsReplaced_(String listName, int index, List newItems, List oldItems) { + ListReplaceEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListReplaceEvent(this.source, listName, index, newItems, oldItems); + } + listener.itemsReplaced(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListReplaceEvent(this.source, listName, index, newItems, oldItems); + } + changeListener.itemsReplaced(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether the item changed. + */ + public boolean fireItemReplaced(String listName, int index, Object newItem, Object oldItem) { +// return this.fireItemsReplaced(listName, index, Collections.singletonList(newItem), Collections.singletonList(oldItem)); + if (this.valuesAreDifferent(newItem, oldItem)) { + this.fireItemReplaced_(listName, index, newItem, oldItem); + return true; + } + return false; + } + + /** + * pre-condition: the specified old and new items are different + */ + protected void fireItemReplaced_(String listName, int index, Object newItem, Object oldItem) { + ListReplaceEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListReplaceEvent(this.source, listName, index, newItem, oldItem); + } + listener.itemsReplaced(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListReplaceEvent(this.source, listName, index, newItem, oldItem); + } + changeListener.itemsReplaced(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any moved items. + */ + // it's unlikely but possible the list is unchanged by the move... + // e.g. any moves within ["foo", "foo", "foo"] + public boolean fireItemsMoved(ListMoveEvent event) { + if (event.getTargetIndex() != event.getSourceIndex()) { + this.fireItemsMoved_(event); + return true; + } + return false; + } + + /** + * pre-condition: the specified event indicates a move + */ + protected void fireItemsMoved_(ListMoveEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.itemsMoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.itemsMoved(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any moved items. + */ + // it's unlikely but possible the list is unchanged by the move... + // e.g. any moves within ["foo", "foo", "foo"] + public boolean fireItemsMoved(String listName, int targetIndex, int sourceIndex, int length) { +// return this.fireItemsMoved(new ListMoveEvent(this.source, listName, targetIndex, sourceIndex, length)); + if (targetIndex != sourceIndex) { + this.fireItemsMoved_(listName, targetIndex, sourceIndex, length); + return true; + } + return false; + } + + /** + * pre-condition: the specified indices indicate a move + */ + protected void fireItemsMoved_(String listName, int targetIndex, int sourceIndex, int length) { + ListMoveEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListMoveEvent(this.source, listName, targetIndex, sourceIndex, length); + } + listener.itemsMoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListMoveEvent(this.source, listName, targetIndex, sourceIndex, length); + } + changeListener.itemsMoved(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + * Return whether there are any moved items. + */ + // it's unlikely but possible the list is unchanged by the move... + // e.g. any moves within ["foo", "foo", "foo"] + public boolean fireItemMoved(String listName, int targetIndex, int sourceIndex) { + if (targetIndex != sourceIndex) { + this.fireItemMoved_(listName, targetIndex, sourceIndex); + return true; + } + return false; + } + + /** + * pre-condition: the specified indices indicate a move + */ + protected void fireItemMoved_(String listName, int targetIndex, int sourceIndex) { + this.fireItemsMoved_(listName, targetIndex, sourceIndex, 1); + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireListCleared(ListClearEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.listCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.listCleared(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireListCleared(String listName) { +// this.fireListCleared(new ListClearEvent(this.source, listName)); + + ListClearEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListClearEvent(this.source, listName); + } + listener.listCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListClearEvent(this.source, listName); + } + changeListener.listCleared(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireListChanged(ListChangeEvent event) { + String listName = event.getListName(); + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + listener.listChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.listChanged(event); + } + } + } + } + + /** + * Report a bound list update to any registered listeners. + */ + public void fireListChanged(String listName, List list) { +// this.fireListChanged(new ListChangeEvent(this.source, listName)); + + ListChangeEvent event = null; + Iterable listeners = this.getListChangeListeners(listName); + if (listeners != null) { + for (ListChangeListener listener : listeners) { + if (this.hasListChangeListener(listName, listener)) { // verify listener is still listening + if (event == null) { + event = new ListChangeEvent(this.source, listName, list); + } + listener.listChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new ListChangeEvent(this.source, listName, list); + } + changeListener.listChanged(event); + } + } + } + } + + /** + * Add the specified item to the specified bound list at the specified index + * and fire the appropriate event. + * @see List#add(int, Object) + */ + public void addItemToList(int index, E item, List list, String listName) { + list.add(index, item); + this.fireItemAdded(listName, index, item); + } + + /** + * Add the specified item to the end of the specified bound list + * and fire the appropriate event. + * Return whether the list changed (i.e. 'true'). + * @see List#add(Object) + */ + public boolean addItemToList(E item, List list, String listName) { + if (list.add(item)) { + this.fireItemAdded(listName, list.size() - 1, item); + return true; + } + return false; // List#add(Object) should always return 'true', so we should never get here... + } + + /** + * Add the specified items to the specified bound list at the specified index + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(int, Collection) + */ + public boolean addItemsToList(int index, E[] items, List list, String listName) { + return (items.length != 0) + && this.addItemsToList_(index, Arrays.asList(items), list, listName); + } + + /** + * Add the specified items to the specified bound list at the specified index + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(int, Collection) + */ + public boolean addItemsToList(int index, Collection items, List list, String listName) { + return ( ! items.isEmpty()) + && this.addItemsToList_(index, this.convertToList(items), list, listName); + } + + /** + * no empty check + */ + protected boolean addItemsToList_(int index, List items, List list, String listName) { + if (list.addAll(index, items)) { + this.fireItemsAdded(listName, index, items); + return true; + } + return false; // 'items' should not be empty, so we should never get here... + } + + /** + * Add the specified items to the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(int, Collection) + */ + public boolean addItemsToList(int index, Iterable items, List list, String listName) { + return this.addItemsToList(index, items.iterator(), list, listName); + } + + /** + * Add the specified items to the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(int, Collection) + */ + public boolean addItemsToList(int index, Iterator items, List list, String listName) { + if ( ! items.hasNext()) { + return false; + } + + ArrayList addedItems = CollectionTools.list(items); + if (list.addAll(index, addedItems)) { + this.fireItemsAdded(listName, index, addedItems); + return true; + } + return false; // 'items' should not be empty, so we should never get here... + } + + /** + * Add the specified items to the end of to the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(Collection) + */ + public boolean addItemsToList(E[] items, List list, String listName) { + return (items.length != 0) + && this.addItemsToList_(Arrays.asList(items), list, listName); + } + + /** + * Add the specified items to the end of the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(int, Collection) + */ + public boolean addItemsToList(Collection items, List list, String listName) { + return ( ! items.isEmpty()) + && this.addItemsToList_(this.convertToList(items), list, listName); + } + + protected List convertToList(Collection collection) { + return (collection instanceof List) ? (List) collection : new ArrayList(collection); + } + + /** + * no empty check + */ + protected boolean addItemsToList_(List items, List list, String listName) { + int index = list.size(); + if (list.addAll(items)) { + this.fireItemsAdded(listName, index, items); + return true; + } + return false; // 'items' should not be empty, so we should never get here... + } + + /** + * Add the specified items to the end of to the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(Collection) + */ + public boolean addItemsToList(Iterable items, List list, String listName) { + return this.addItemsToList(items.iterator(), list, listName); + } + + /** + * Add the specified items to the end of to the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#addAll(Collection) + */ + public boolean addItemsToList(Iterator items, List list, String listName) { + if ( ! items.hasNext()) { + return false; + } + return this.addItemsToList_(items, list, listName); + } + + /** + * no empty check + */ + protected boolean addItemsToList_(Iterator items, List list, String listName) { + ArrayList addedItems = CollectionTools.list(items); + int index = list.size(); + if (list.addAll(addedItems)) { + this.fireItemsAdded(listName, index, addedItems); + return true; + } + return false; // 'items' should not be empty, so we should never get here... + } + + /** + * Remove the specified item from the specified bound list + * and fire the appropriate event if necessary. + * Return the removed item. + * @see List#remove(int) + */ + public E removeItemFromList(int index, List list, String listName) { + E item = list.remove(index); + this.fireItemRemoved(listName, index, item); + return item; + } + + /** + * Remove the specified item from the specified bound list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#remove(Object) + */ + public boolean removeItemFromList(Object item, List list, String listName) { + int index = list.indexOf(item); + if (index == -1) { + return false; + } + list.remove(index); + this.fireItemRemoved(listName, index, item); + return true; + } + + /** + * Remove the items from the specified index to the end of the list + * from the specified bound list + * and fire the appropriate event if necessary. + * Return the removed items. + * @see List#remove(int) + */ + public List removeItemsFromList(int index, List list, String listName) { + return this.removeRangeFromList(index, list.size(), list, listName); + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event if necessary. + * Return the removed items. + * @see List#remove(int) + */ + public List removeItemsFromList(int index, int length, List list, String listName) { + return this.removeRangeFromList(index, index + length, list, listName); + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event if necessary. The begin index + * is inclusive, while the end index is exclusive. + * Return the removed items. + * @see List#remove(int) + * @see List#subList(int, int) + */ + public List removeRangeFromList(int beginIndex, int endIndex, List list, String listName) { + if (beginIndex == endIndex) { + return Collections.emptyList(); + } + return this.removeRangeFromList_(beginIndex, endIndex, list, listName); + } + + /** + * no empty check + */ + protected List removeRangeFromList_(int beginIndex, int endIndex, List list, String listName) { + List subList = list.subList(beginIndex, endIndex); + List removedItems = new ArrayList(subList); + subList.clear(); + this.fireItemsRemoved(listName, beginIndex, removedItems); + return removedItems; + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#removeAll(Collection) + */ + public boolean removeItemsFromList(Object[] items, List list, String listName) { + return (items.length != 0) + && ( ! list.isEmpty()) + && this.removeItemsFromList_(new ArrayIterator(items), list, listName); + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#removeAll(Collection) + */ + public boolean removeItemsFromList(Collection items, List list, String listName) { + return ( ! items.isEmpty()) + && ( ! list.isEmpty()) + && this.removeItemsFromList_(items.iterator(), list, listName); + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#removeAll(Collection) + */ + public boolean removeItemsFromList(Iterable items, List list, String listName) { + return this.removeItemsFromList(items.iterator(), list, listName); + } + + /** + * Remove the specified items from the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#removeAll(Collection) + */ + public boolean removeItemsFromList(Iterator items, List list, String listName) { + return (items.hasNext()) + && ( ! list.isEmpty()) + && this.removeItemsFromList_(items, list, listName); + } + + /** + * no empty checks + */ + protected boolean removeItemsFromList_(Iterator items, List list, String listName) { + boolean changed = false; + while (items.hasNext()) { + changed |= this.removeItemFromList(items.next(), list, listName); + } + return changed; + } + + /** + * Retain the specified items in the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#retainAll(Collection) + */ + public boolean retainItemsInList(Object[] items, List list, String listName) { + if (list.isEmpty()) { + return false; + } + if (items.length == 0) { + return this.clearList_(list, listName); + } + return this.retainItemsInList_(new ArrayIterator(items), list, listName); + } + + /** + * Retain the specified items in the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#retainAll(Collection) + */ + public boolean retainItemsInList(Collection items, List list, String listName) { + if (list.isEmpty()) { + return false; + } + if (items.isEmpty()) { + return this.clearList_(list, listName); + } + return this.retainItemsInList_(items.iterator(), list, listName); + } + + /** + * Retain the specified items in the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#retainAll(Collection) + */ + public boolean retainItemsInList(Iterable items, List list, String listName) { + return this.retainItemsInList(items.iterator(), list, listName); + } + + /** + * Retain the specified items in the specified bound list + * and fire the appropriate event(s) if necessary. + * Return whether the list changed. + * @see List#retainAll(Collection) + */ + public boolean retainItemsInList(Iterator items, List list, String listName) { + if (list.isEmpty()) { + return false; + } + if ( ! items.hasNext()) { + return this.clearList_(list, listName); + } + return this.retainItemsInList_(items, list, listName); + } + + /** + * no empty checks + */ + protected boolean retainItemsInList_(Iterator items, List list, String listName) { + HashBag retainedItems = CollectionTools.collection(items); + HashBag removedItems = CollectionTools.collection(list); + removedItems.removeAll(retainedItems); + return this.removeItemsFromList(removedItems, list, listName); + } + + /** + * Set the specified item in the specified bound list + * and fire the appropriate event if necessary. + * Return the replaced item. + * @see List#set(int, Object) + */ + public E setItemInList(int index, E item, List list, String listName) { + E oldItem = list.set(index, item); + this.fireItemReplaced(listName, index, item, oldItem); + return oldItem; + } + + /** + * Replace the first occurrence of the specified item + * in the specified bound list + * and fire the appropriate event if necessary. + * Return the index of the replaced item. + * Return -1 if the item was not found in the list. + * @see List#set(int, Object) + */ + public int replaceItemInList(E oldItem, E newItem, List list, String listName) { + if (list.isEmpty()) { + return -1; + } + + int index = list.indexOf(oldItem); + if ((index != -1) && this.valuesAreDifferent(oldItem, newItem)) { + list.set(index, newItem); + this.fireItemReplaced_(listName, index, newItem, oldItem); + } + return index; + } + + /** + * Set the specified items in the specified bound list + * and fire the appropriate event if necessary. + * Return the replaced items. + * @see List#set(int, Object) + */ + public List setItemsInList(int index, E[] items, List list, String listName) { + if (items.length == 0) { + return Collections.emptyList(); + } + return this.setItemsInList_(index, Arrays.asList(items), list, listName); + } + + /** + * Set the specified items in the specified bound list + * and fire the appropriate event if necessary. + * Return the replaced items. + * @see List#set(int, Object) + */ + public List setItemsInList(int index, List items, List list, String listName) { + if (items.isEmpty()) { + return Collections.emptyList(); + } + return this.setItemsInList_(index, items, list, listName); + } + + /** + * no empty check + */ + protected List setItemsInList_(int index, List items, List list, String listName) { + List subList = list.subList(index, index + items.size()); + List oldItems = new ArrayList(subList); + for (int i = 0; i < items.size(); i++) { + E newItem = items.get(i); + E oldItem = subList.set(i, newItem); + this.fireItemReplaced(listName, index + i, newItem, oldItem); + } + return oldItems; + } + + /** + * Move items in the specified list from the specified source index to the + * specified target index for the specified length. + * Return whether the list changed. + */ + public boolean moveItemsInList(int targetIndex, int sourceIndex, int length, List list, String listName) { + if ((targetIndex == sourceIndex) || (length == 0)) { + return false; + } + // it's unlikely but possible the list is unchanged by the move... (e.g. any moves within ["foo", "foo", "foo"]...) + CollectionTools.move(list, targetIndex, sourceIndex, length); + this.fireItemsMoved(listName, targetIndex, sourceIndex, length); + return true; + } + + /** + * Move the specified item in the specified list to the + * specified target index. + * Return whether the list changed. + */ + public boolean moveItemInList(int targetIndex, E item, List list, String listName) { + return this.moveItemInList(targetIndex, list.indexOf(item), list, listName); + } + + /** + * Move an item in the specified list from the specified source index to the + * specified target index. + * Return whether the list changed. + */ + public boolean moveItemInList(int targetIndex, int sourceIndex, List list, String listName) { + if (targetIndex == sourceIndex) { + return false; + } + if (this.valuesAreEqual(list.get(targetIndex), list.get(sourceIndex))) { + return false; + } + CollectionTools.move(list, targetIndex, sourceIndex); + this.fireItemMoved_(listName, targetIndex, sourceIndex); + return true; + } + + /** + * Clear the entire list + * and fire the appropriate event if necessary. + * Return whether the list changed. + * @see List#clear() + */ + public boolean clearList(List list, String listName) { + return ( ! list.isEmpty()) && this.clearList_(list, listName); + } + + /** + * no empty check + */ + protected boolean clearList_(List list, String listName) { + list.clear(); + this.fireListCleared(listName); + return true; + } + + /** + * Synchronize the list with the specified new list, + * making a minimum number of sets, removes, and/or adds. + * Return whether the list changed. + */ + public boolean synchronizeList(List newList, List list, String listName) { + if (newList.isEmpty()) { + return this.clearList(list, listName); + } + if (list.isEmpty()) { + return this.addItemsToList_(newList, list, listName); + } + return this.synchronizeList_(newList, list, listName); + } + + /** + * Synchronize the list with the specified new list, + * making a minimum number of sets, removes, and/or adds. + * Return whether the list changed. + */ + public boolean synchronizeList(Iterable newList, List list, String listName) { + return this.synchronizeList(newList.iterator(), list, listName); + } + + /** + * Synchronize the list with the specified new list, + * making a minimum number of sets, removes, and/or adds. + * Return whether the list changed. + */ + public boolean synchronizeList(Iterator newList, List list, String listName) { + if ( ! newList.hasNext()) { + return this.clearList(list, listName); + } + if (list.isEmpty()) { + return this.addItemsToList_(newList, list, listName); + } + return this.synchronizeList_(CollectionTools.list(newList), list, listName); + } + + /** + * no empty checks + */ + protected boolean synchronizeList_(List newList, List oldList, String listName) { + int newSize = newList.size(); + int oldSize = oldList.size(); + + boolean changed = false; + // TODO check for RandomAccess + int min = Math.min(newSize, oldSize); + for (int i = 0; i < min; i++) { + E newItem = newList.get(i); + E oldItem = oldList.set(i, newItem); + if (this.valuesAreDifferent(newItem, oldItem)) { + changed = true; + this.fireItemReplaced_(listName, i, newItem, oldItem); + } + } + + if (newSize == oldSize) { + return changed; + } + + if (newSize < oldSize) { + this.removeRangeFromList_(newSize, oldSize, oldList, listName); + return true; + } + + // newSize > oldSize + this.addItemsToList_(newList.subList(oldSize, newSize), oldList, listName); + return true; + } + + + // ********** tree change support ********** + + protected static final Class TREE_CHANGE_LISTENER_CLASS = TreeChangeListener.class; + + /** + * Add a tree change listener for the specified tree. The listener + * will be notified only for changes to the specified tree. + */ + public void addTreeChangeListener(String treeName, TreeChangeListener listener) { + this.addListener(TREE_CHANGE_LISTENER_CLASS, treeName, listener); + } + + /** + * Remove a tree change listener that was registered for a specific tree. + */ + public void removeTreeChangeListener(String treeName, TreeChangeListener listener) { + this.removeListener(TREE_CHANGE_LISTENER_CLASS, treeName, listener); + } + + /** + * Return whether there are any tree change listeners that will + * be notified when the specified tree has changed. + */ + public boolean hasAnyTreeChangeListeners(String treeName) { + return this.hasAnyListeners(TREE_CHANGE_LISTENER_CLASS, treeName); + } + + private ListenerList getTreeChangeListenerList(String treeName) { + return this.getListenerList(TREE_CHANGE_LISTENER_CLASS, treeName); + } + + private Iterable getTreeChangeListeners(String treeName) { + ListenerList listenerList = this.getTreeChangeListenerList(treeName); + return (listenerList == null) ? null : listenerList.getListeners(); + } + + private boolean hasTreeChangeListener(String treeName, TreeChangeListener listener) { + return CollectionTools.contains(this.getTreeChangeListeners(treeName), listener); + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireNodeAdded(TreeAddEvent event) { + String treeName = event.getTreeName(); + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + listener.nodeAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.nodeAdded(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireNodeAdded(String treeName, List path) { +// this.fireNodeAdded(new TreeAddEvent(this.source, treeName, path)); + TreeAddEvent event = null; + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + if (event == null) { + event = new TreeAddEvent(this.source, treeName, path); + } + listener.nodeAdded(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new TreeAddEvent(this.source, treeName, path); + } + changeListener.nodeAdded(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireNodeRemoved(TreeRemoveEvent event) { + String treeName = event.getTreeName(); + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + listener.nodeRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.nodeRemoved(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireNodeRemoved(String treeName, List path) { +// this.fireNodeRemoved(new TreeRemoveEvent(this.source, treeName, path)); + + TreeRemoveEvent event = null; + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + if (event == null) { + event = new TreeRemoveEvent(this.source, treeName, path); + } + listener.nodeRemoved(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new TreeRemoveEvent(this.source, treeName, path); + } + changeListener.nodeRemoved(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireTreeCleared(TreeClearEvent event) { + String treeName = event.getTreeName(); + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + listener.treeCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.treeCleared(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireTreeCleared(String treeName) { +// this.fireTreeCleared(new TreeClearEvent(this.source, treeName)); + + TreeClearEvent event = null; + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + if (event == null) { + event = new TreeClearEvent(this.source, treeName); + } + listener.treeCleared(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new TreeClearEvent(this.source, treeName); + } + changeListener.treeCleared(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireTreeChanged(TreeChangeEvent event) { + String treeName = event.getTreeName(); + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + listener.treeChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + changeListener.treeChanged(event); + } + } + } + } + + /** + * Report a bound tree update to any registered listeners. + */ + public void fireTreeChanged(String treeName, Collection nodes) { +// this.fireTreeChanged(new TreeChangeEvent(this.source, treeName, nodes)); + + TreeChangeEvent event = null; + Iterable listeners = this.getTreeChangeListeners(treeName); + if (listeners != null) { + for (TreeChangeListener listener : listeners) { + if (this.hasTreeChangeListener(treeName, listener)) { // verify listener is still listening + if (event == null) { + event = new TreeChangeEvent(this.source, treeName, nodes); + } + listener.treeChanged(event); + } + } + } + + Iterable changeListeners = this.getChangeListeners(); + if (changeListeners != null) { + for (ChangeListener changeListener : changeListeners) { + if (this.hasChangeListener(changeListener)) { // verify listener is still listening + if (event == null) { + event = new TreeChangeEvent(this.source, treeName, nodes); + } + changeListener.treeChanged(event); + } + } + } + } + + + // ********** misc ********** + + /** + * Convenience method for checking whether an attribute value has changed. + * @see Tools#valuesAreEqual(Object, Object) + */ + public boolean valuesAreEqual(Object value1, Object value2) { + return Tools.valuesAreEqual(value1, value2); + } + + /** + * Convenience method for checking whether an attribute value has changed. + * @see Tools#valuesAreDifferent(Object, Object) + */ + public boolean valuesAreDifferent(Object value1, Object value2) { + return Tools.valuesAreDifferent(value1, value2); + } + + /** + * @see CollectionTools#elementsAreEqual(Iterable, Iterable) + */ + public boolean elementsAreEqual(Iterable iterable1, Iterable iterable2) { + return CollectionTools.elementsAreEqual(iterable1, iterable2); + } + + /** + * @see CollectionTools#elementsAreDifferent(Iterable, Iterable) + */ + public boolean elementsAreDifferent(Iterable iterable1, Iterable iterable2) { + return CollectionTools.elementsAreDifferent(iterable1, iterable2); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.source); + } + + + // ********** member classes ********** + + /** + * Pair a possibly null aspect name with its associated + * listeners. + */ + static abstract class AspectListenerListPair + implements Serializable + { + final ListenerList listenerList; + + private static final long serialVersionUID = 1L; + + AspectListenerListPair(Class listenerClass, L listener) { + super(); + this.listenerList = new ListenerList(listenerClass, listener); + } + + boolean matches(Class listenerClass, @SuppressWarnings("unused") String aspectName) { + return this.listenerList.getListenerType() == listenerClass; + } + + boolean matches(Class listenerClass) { + return this.matches(listenerClass, null); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.getAspectName()); + } + + abstract String getAspectName(); + } + + /** + * Pair a non-null aspect name with its associated listeners. + */ + static class SimpleAspectListenerListPair + extends AspectListenerListPair + { + final String aspectName; + + private static final long serialVersionUID = 1L; + + SimpleAspectListenerListPair(Class listenerClass, String aspectName, L listener) { + super(listenerClass, listener); + if (aspectName == null) { + throw new NullPointerException(); + } + this.aspectName = aspectName; + } + + @Override + boolean matches(Class listenerClass, @SuppressWarnings("hiding") String aspectName) { + return this.aspectName.equals(aspectName) + && super.matches(listenerClass, aspectName); + } + + @Override + String getAspectName() { + return this.aspectName; + } + } + + /** + * Pair a null aspect name with its associated listeners. + */ + static class NullAspectListenerListPair + extends AspectListenerListPair + { + private static final long serialVersionUID = 1L; + + NullAspectListenerListPair(Class listenerClass, L listener) { + super(listenerClass, listener); + } + + @Override + boolean matches(Class listenerClass, String aspectName) { + return (aspectName == null) + && super.matches(listenerClass, null); + } + + @Override + String getAspectName() { + return null; + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/SingleAspectChangeSupport.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/SingleAspectChangeSupport.java new file mode 100644 index 0000000000..ef13a67927 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/SingleAspectChangeSupport.java @@ -0,0 +1,380 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model; + +import java.util.Collection; +import java.util.EventListener; +import java.util.List; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * This change support class changes the behavior of the standard + * change support in several ways:
    + *
  • All events fired by the source must specify the single aspect. + *
  • Listeners are required to be either "general purpose" listeners or + * listeners of the single aspect. + *
+ */ +public class SingleAspectChangeSupport + extends ChangeSupport +{ + protected final Class validListenerClass; + protected final String validAspectName; + + private static final long serialVersionUID = 1L; + + + // ********** constructor ********** + + public SingleAspectChangeSupport(Model source, Class validListenerClass, String validAspectName) { + super(source); + if ( ! validListenerClass.isAssignableFrom(this.getChangeListenerClass())) { + throw new IllegalArgumentException("The change support's change listener class (" + this.getChangeListenerClass().getName() + //$NON-NLS-1$ + ") does not extend the valid listener class: " + validListenerClass.getName()); //$NON-NLS-1$ + } + this.validListenerClass = validListenerClass; + this.validAspectName = validAspectName; + } + + + // ********** internal implementation ********** + + private UnsupportedOperationException buildUnsupportedOperationException() { + return new UnsupportedOperationException( + "This Model supports only changes for the listener type \"" + this.validListenerClass.getName() //$NON-NLS-1$ + + "\" and the aspect \"" + this.validAspectName + '"' //$NON-NLS-1$ + ); + } + + /** + * The listener can be either an instance of the valid listener class or + * the "general-purpose" change listener class (which should extend the + * the valid listener class). + */ + private void check(Class listenerClass) { + if ((listenerClass != this.getChangeListenerClass()) && (listenerClass != this.validListenerClass)) { + throw new IllegalArgumentException( + "This Model supports only changes for the listener type \"" + this.validListenerClass.getName() //$NON-NLS-1$ + + "\" : \"" + listenerClass.getName() + '"' //$NON-NLS-1$ + ); + } + } + + private void check(Class listenerClass, String aspectName) { + this.check(listenerClass); + if ( ! aspectName.equals(this.validAspectName)) { + throw new IllegalArgumentException( + "This Model supports only changes for the aspect \"" + this.validAspectName //$NON-NLS-1$ + + "\" : \"" + aspectName + '"' //$NON-NLS-1$ + ); + } + } + + @Override + protected synchronized void addListener(Class listenerClass, String aspectName, L listener) { + this.check(listenerClass, aspectName); + super.addListener(listenerClass, aspectName, listener); + } + + @Override + protected synchronized void addListener(Class listenerClass, L listener) { + this.check(listenerClass); + super.addListener(listenerClass, listener); + } + + @Override + protected synchronized void removeListener(Class listenerClass, String aspectName, L listener) { + this.check(listenerClass, aspectName); + super.removeListener(listenerClass, aspectName, listener); + } + + @Override + protected synchronized void removeListener(Class listenerClass, L listener) { + this.check(listenerClass); + super.removeListener(listenerClass, listener); + } + + @Override + protected boolean hasAnyListeners(Class listenerClass, String aspectName) { + this.check(listenerClass, aspectName); + return super.hasAnyListeners(listenerClass, aspectName); + } + + @Override + protected boolean hasAnyListeners(Class listenerClass) { + this.check(listenerClass); + return super.hasAnyListeners(listenerClass); + } + + + // ********** state change support ********** + + @Override + public void fireStateChanged(StateChangeEvent event) { + throw this.buildUnsupportedOperationException(); + } + + @Override + public void fireStateChanged() { + throw this.buildUnsupportedOperationException(); + } + + + // ********** property change support ********** + + @Override + public boolean firePropertyChanged(PropertyChangeEvent event) { + this.check(PROPERTY_CHANGE_LISTENER_CLASS, event.getPropertyName()); + return super.firePropertyChanged(event); + } + + @Override + public boolean firePropertyChanged(String propertyName, Object oldValue, Object newValue) { + this.check(PROPERTY_CHANGE_LISTENER_CLASS, propertyName); + return super.firePropertyChanged(propertyName, oldValue, newValue); + } + + @Override + public boolean firePropertyChanged(String propertyName, int oldValue, int newValue) { + this.check(PROPERTY_CHANGE_LISTENER_CLASS, propertyName); + return super.firePropertyChanged(propertyName, oldValue, newValue); + } + + @Override + public boolean firePropertyChanged(String propertyName, boolean oldValue, boolean newValue) { + this.check(PROPERTY_CHANGE_LISTENER_CLASS, propertyName); + return super.firePropertyChanged(propertyName, oldValue, newValue); + } + + + // ********** collection change support ********** + + @Override + public boolean fireItemsAdded(CollectionAddEvent event) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, event.getCollectionName()); + return super.fireItemsAdded(event); + } + + @Override + public boolean fireItemsAdded(String collectionName, Collection addedItems) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + return super.fireItemsAdded(collectionName, addedItems); + } + + @Override + public void fireItemAdded(String collectionName, Object addedItem) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + super.fireItemAdded(collectionName, addedItem); + } + + @Override + public boolean fireItemsRemoved(CollectionRemoveEvent event) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, event.getCollectionName()); + return super.fireItemsRemoved(event); + } + + @Override + public boolean fireItemsRemoved(String collectionName, Collection removedItems) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + return super.fireItemsRemoved(collectionName, removedItems); + } + + @Override + public void fireItemRemoved(String collectionName, Object removedItem) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + super.fireItemRemoved(collectionName, removedItem); + } + + @Override + public void fireCollectionCleared(CollectionClearEvent event) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, event.getCollectionName()); + super.fireCollectionCleared(event); + } + + @Override + public void fireCollectionCleared(String collectionName) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + super.fireCollectionCleared(collectionName); + } + + @Override + public void fireCollectionChanged(CollectionChangeEvent event) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, event.getCollectionName()); + super.fireCollectionChanged(event); + } + + @Override + public void fireCollectionChanged(String collectionName, Collection collection) { + this.check(COLLECTION_CHANGE_LISTENER_CLASS, collectionName); + super.fireCollectionChanged(collectionName, collection); + } + + + // ********** list change support ********** + + @Override + public boolean fireItemsAdded(ListAddEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + return super.fireItemsAdded(event); + } + + @Override + public boolean fireItemsAdded(String listName, int index, List addedItems) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + return super.fireItemsAdded(listName, index, addedItems); + } + + @Override + public void fireItemAdded(String listName, int index, Object addedItem) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + super.fireItemAdded(listName, index, addedItem); + } + + @Override + public boolean fireItemsRemoved(ListRemoveEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + return super.fireItemsRemoved(event); + } + + @Override + public boolean fireItemsRemoved(String listName, int index, List removedItems) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + return super.fireItemsRemoved(listName, index, removedItems); + } + + @Override + public void fireItemRemoved(String listName, int index, Object removedItem) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + super.fireItemRemoved(listName, index, removedItem); + } + + @Override + public boolean fireItemsReplaced(ListReplaceEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + return super.fireItemsReplaced(event); + } + + @Override + public boolean fireItemsReplaced(String listName, int index, List newItems, List replacedItems) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + return super.fireItemsReplaced(listName, index, newItems, replacedItems); + } + + @Override + public boolean fireItemReplaced(String listName, int index, Object newItem, Object replacedItem) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + return super.fireItemReplaced(listName, index, newItem, replacedItem); + } + + @Override + public boolean fireItemsMoved(ListMoveEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + return super.fireItemsMoved(event); + } + + @Override + public boolean fireItemsMoved(String listName, int targetIndex, int sourceIndex, int length) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + return super.fireItemsMoved(listName, targetIndex, sourceIndex, length); + } + + @Override + public void fireListCleared(ListClearEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + super.fireListCleared(event); + } + + @Override + public void fireListCleared(String listName) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + super.fireListCleared(listName); + } + + @Override + public void fireListChanged(ListChangeEvent event) { + this.check(LIST_CHANGE_LISTENER_CLASS, event.getListName()); + super.fireListChanged(event); + } + + @Override + public void fireListChanged(String listName, List list) { + this.check(LIST_CHANGE_LISTENER_CLASS, listName); + super.fireListChanged(listName, list); + } + + + // ********** tree change support ********** + + @Override + public void fireNodeAdded(TreeAddEvent event) { + this.check(TREE_CHANGE_LISTENER_CLASS, event.getTreeName()); + super.fireNodeAdded(event); + } + + @Override + public void fireNodeAdded(String treeName, List path) { + this.check(TREE_CHANGE_LISTENER_CLASS, treeName); + super.fireNodeAdded(treeName, path); + } + + @Override + public void fireNodeRemoved(TreeRemoveEvent event) { + this.check(TREE_CHANGE_LISTENER_CLASS, event.getTreeName()); + super.fireNodeRemoved(event); + } + + @Override + public void fireNodeRemoved(String treeName, List path) { + this.check(TREE_CHANGE_LISTENER_CLASS, treeName); + super.fireNodeRemoved(treeName, path); + } + + @Override + public void fireTreeCleared(TreeClearEvent event) { + this.check(TREE_CHANGE_LISTENER_CLASS, event.getTreeName()); + super.fireTreeCleared(event); + } + + @Override + public void fireTreeCleared(String treeName) { + this.check(TREE_CHANGE_LISTENER_CLASS, treeName); + super.fireTreeCleared(treeName); + } + + @Override + public void fireTreeChanged(TreeChangeEvent event) { + this.check(TREE_CHANGE_LISTENER_CLASS, event.getTreeName()); + super.fireTreeChanged(event); + } + + @Override + public void fireTreeChanged(String treeName, Collection nodes) { + this.check(TREE_CHANGE_LISTENER_CLASS, treeName); + super.fireTreeChanged(treeName, nodes); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTChangeListenerWrapper.java new file mode 100644 index 0000000000..5f16ea4650 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTChangeListenerWrapper.java @@ -0,0 +1,454 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; + +/** + * Wrap another change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTChangeListenerWrapper + implements ChangeListener +{ + private final ChangeListener listener; + + + public AWTChangeListenerWrapper(ChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void stateChanged(StateChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.stateChanged_(event); + } else { + this.executeOnEventQueue(this.buildStateChangedRunnable(event)); + } + } + + public void propertyChanged(PropertyChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.propertyChanged_(event); + } else { + this.executeOnEventQueue(this.buildPropertyChangedRunnable(event)); + } + } + + public void itemsAdded(CollectionAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnEventQueue(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(CollectionRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsRemovedRunnable(event)); + } + } + + public void collectionCleared(CollectionClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionCleared_(event); + } else { + this.executeOnEventQueue(this.buildCollectionClearedRunnable(event)); + } + } + + public void collectionChanged(CollectionChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionChanged_(event); + } else { + this.executeOnEventQueue(this.buildCollectionChangedRunnable(event)); + } + } + + public void itemsAdded(ListAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnEventQueue(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(ListRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsRemovedRunnable(event)); + } + } + + public void itemsMoved(ListMoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsMoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsMovedRunnable(event)); + } + } + + public void itemsReplaced(ListReplaceEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsReplaced_(event); + } else { + this.executeOnEventQueue(this.buildItemsReplacedRunnable(event)); + } + } + + public void listCleared(ListClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.listCleared_(event); + } else { + this.executeOnEventQueue(this.buildListClearedRunnable(event)); + } + } + + public void listChanged(ListChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.listChanged_(event); + } else { + this.executeOnEventQueue(this.buildListChangedRunnable(event)); + } + } + + public void nodeAdded(TreeAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeAdded_(event); + } else { + this.executeOnEventQueue(this.buildNodeAddedRunnable(event)); + } + } + + public void nodeRemoved(TreeRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeRemoved_(event); + } else { + this.executeOnEventQueue(this.buildNodeRemovedRunnable(event)); + } + } + + public void treeCleared(TreeClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeCleared_(event); + } else { + this.executeOnEventQueue(this.buildTreeClearedRunnable(event)); + } + } + + public void treeChanged(TreeChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeChanged_(event); + } else { + this.executeOnEventQueue(this.buildTreeChangedRunnable(event)); + } + } + + private Runnable buildStateChangedRunnable(final StateChangeEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.stateChanged_(event); + } + }; + } + + private Runnable buildPropertyChangedRunnable(final PropertyChangeEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.propertyChanged_(event); + } + }; + } + + private Runnable buildItemsAddedRunnable(final CollectionAddEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final CollectionRemoveEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionClearedRunnable(final CollectionClearEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.collectionCleared_(event); + } + @Override + public String toString() { + return "collection cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionChangedRunnable(final CollectionChangeEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.collectionChanged_(event); + } + @Override + public String toString() { + return "collection changed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsAddedRunnable(final ListAddEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final ListRemoveEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsMovedRunnable(final ListMoveEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsMoved_(event); + } + @Override + public String toString() { + return "items moved runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsReplacedRunnable(final ListReplaceEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.itemsReplaced_(event); + } + @Override + public String toString() { + return "items replaced runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListClearedRunnable(final ListClearEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.listCleared_(event); + } + @Override + public String toString() { + return "list cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListChangedRunnable(final ListChangeEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.listChanged_(event); + } + @Override + public String toString() { + return "list changed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildNodeAddedRunnable(final TreeAddEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.nodeAdded_(event); + } + @Override + public String toString() { + return "node added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildNodeRemovedRunnable(final TreeRemoveEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.nodeRemoved_(event); + } + @Override + public String toString() { + return "node removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeClearedRunnable(final TreeClearEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.treeCleared_(event); + } + @Override + public String toString() { + return "tree cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeChangedRunnable(final TreeChangeEvent event) { + return new Runnable() { + public void run() { + AWTChangeListenerWrapper.this.treeChanged_(event); + } + @Override + public String toString() { + return "tree changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void stateChanged_(StateChangeEvent event) { + this.listener.stateChanged(event); + } + + void propertyChanged_(PropertyChangeEvent event) { + this.listener.propertyChanged(event); + } + + void itemsAdded_(CollectionAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(CollectionRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void collectionCleared_(CollectionClearEvent event) { + this.listener.collectionCleared(event); + } + + void collectionChanged_(CollectionChangeEvent event) { + this.listener.collectionChanged(event); + } + + void itemsAdded_(ListAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(ListRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void itemsMoved_(ListMoveEvent event) { + this.listener.itemsMoved(event); + } + + void itemsReplaced_(ListReplaceEvent event) { + this.listener.itemsReplaced(event); + } + + void listCleared_(ListClearEvent event) { + this.listener.listCleared(event); + } + + void listChanged_(ListChangeEvent event) { + this.listener.listChanged(event); + } + + void nodeAdded_(TreeAddEvent event) { + this.listener.nodeAdded(event); + } + + void nodeRemoved_(TreeRemoveEvent event) { + this.listener.nodeRemoved(event); + } + + void treeCleared_(TreeClearEvent event) { + this.listener.treeCleared(event); + } + + void treeChanged_(TreeChangeEvent event) { + this.listener.treeChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTCollectionChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTCollectionChangeListenerWrapper.java new file mode 100644 index 0000000000..b8af52a34e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTCollectionChangeListenerWrapper.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; + +/** + * Wrap another collection change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTCollectionChangeListenerWrapper + implements CollectionChangeListener +{ + private final CollectionChangeListener listener; + + public AWTCollectionChangeListenerWrapper(CollectionChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void itemsAdded(CollectionAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnEventQueue(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(CollectionRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsRemovedRunnable(event)); + } + } + + public void collectionCleared(CollectionClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionCleared_(event); + } else { + this.executeOnEventQueue(this.buildCollectionClearedRunnable(event)); + } + } + + public void collectionChanged(CollectionChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionChanged_(event); + } else { + this.executeOnEventQueue(this.buildCollectionChangedRunnable(event)); + } + } + + private Runnable buildItemsAddedRunnable(final CollectionAddEvent event) { + return new Runnable() { + public void run() { + AWTCollectionChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final CollectionRemoveEvent event) { + return new Runnable() { + public void run() { + AWTCollectionChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionClearedRunnable(final CollectionClearEvent event) { + return new Runnable() { + public void run() { + AWTCollectionChangeListenerWrapper.this.collectionCleared_(event); + } + @Override + public String toString() { + return "collection cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionChangedRunnable(final CollectionChangeEvent event) { + return new Runnable() { + public void run() { + AWTCollectionChangeListenerWrapper.this.collectionChanged_(event); + } + @Override + public String toString() { + return "collection changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void itemsAdded_(CollectionAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(CollectionRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void collectionCleared_(CollectionClearEvent event) { + this.listener.collectionCleared(event); + } + + void collectionChanged_(CollectionChangeEvent event) { + this.listener.collectionChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTListChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTListChangeListenerWrapper.java new file mode 100644 index 0000000000..bc477f4f7a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTListChangeListenerWrapper.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; + +/** + * Wrap another list change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTListChangeListenerWrapper + implements ListChangeListener +{ + private final ListChangeListener listener; + + public AWTListChangeListenerWrapper(ListChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void itemsAdded(ListAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnEventQueue(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(ListRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsRemovedRunnable(event)); + } + } + + public void itemsMoved(ListMoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsMoved_(event); + } else { + this.executeOnEventQueue(this.buildItemsMovedRunnable(event)); + } + } + + public void itemsReplaced(ListReplaceEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsReplaced_(event); + } else { + this.executeOnEventQueue(this.buildItemsReplacedRunnable(event)); + } + } + + public void listCleared(ListClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.listCleared_(event); + } else { + this.executeOnEventQueue(this.buildListClearedRunnable(event)); + } + } + + public void listChanged(ListChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.listChanged_(event); + } else { + this.executeOnEventQueue(this.buildListChangedRunnable(event)); + } + } + + private Runnable buildItemsAddedRunnable(final ListAddEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final ListRemoveEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsMovedRunnable(final ListMoveEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.itemsMoved_(event); + } + @Override + public String toString() { + return "items moved runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsReplacedRunnable(final ListReplaceEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.itemsReplaced_(event); + } + @Override + public String toString() { + return "items replaced runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListClearedRunnable(final ListClearEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.listCleared_(event); + } + @Override + public String toString() { + return "list cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListChangedRunnable(final ListChangeEvent event) { + return new Runnable() { + public void run() { + AWTListChangeListenerWrapper.this.listChanged_(event); + } + @Override + public String toString() { + return "list changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void itemsAdded_(ListAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(ListRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void itemsMoved_(ListMoveEvent event) { + this.listener.itemsMoved(event); + } + + void itemsReplaced_(ListReplaceEvent event) { + this.listener.itemsReplaced(event); + } + + void listCleared_(ListClearEvent event) { + this.listener.listCleared(event); + } + + void listChanged_(ListChangeEvent event) { + this.listener.listChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTPropertyChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTPropertyChangeListenerWrapper.java new file mode 100644 index 0000000000..a46bd8bc64 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTPropertyChangeListenerWrapper.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; + +/** + * Wrap another property change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTPropertyChangeListenerWrapper + implements PropertyChangeListener +{ + private final PropertyChangeListener listener; + + + public AWTPropertyChangeListenerWrapper(PropertyChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void propertyChanged(PropertyChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.propertyChanged_(event); + } else { + this.executeOnEventQueue(this.buildPropertyChangedRunnable(event)); + } + } + + private Runnable buildPropertyChangedRunnable(final PropertyChangeEvent event) { + return new Runnable() { + public void run() { + AWTPropertyChangeListenerWrapper.this.propertyChanged_(event); + } + @Override + public String toString() { + return "property changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void propertyChanged_(PropertyChangeEvent event) { + this.listener.propertyChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTStateChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTStateChangeListenerWrapper.java new file mode 100644 index 0000000000..c1ecf2b2ee --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTStateChangeListenerWrapper.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; + +/** + * Wrap another state change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTStateChangeListenerWrapper + implements StateChangeListener +{ + private final StateChangeListener listener; + + public AWTStateChangeListenerWrapper(StateChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void stateChanged(StateChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.stateChanged_(event); + } else { + this.executeOnEventQueue(this.buildStateChangedRunnable(event)); + } + } + + private Runnable buildStateChangedRunnable(final StateChangeEvent event) { + return new Runnable() { + public void run() { + AWTStateChangeListenerWrapper.this.stateChanged_(event); + } + @Override + public String toString() { + return "state changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void stateChanged_(StateChangeEvent event) { + this.listener.stateChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTTreeChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTTreeChangeListenerWrapper.java new file mode 100644 index 0000000000..ea60469d1a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTTreeChangeListenerWrapper.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.listener.awt; + +import java.awt.EventQueue; + +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; + +/** + * Wrap another tree change listener and forward events to it on the AWT + * event queue, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public final class AWTTreeChangeListenerWrapper + implements TreeChangeListener +{ + private final TreeChangeListener listener; + + public AWTTreeChangeListenerWrapper(TreeChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void nodeAdded(TreeAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeAdded_(event); + } else { + this.executeOnEventQueue(this.buildNodeAddedRunnable(event)); + } + } + + public void nodeRemoved(TreeRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeRemoved_(event); + } else { + this.executeOnEventQueue(this.buildNodeRemovedRunnable(event)); + } + } + + public void treeCleared(TreeClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeCleared_(event); + } else { + this.executeOnEventQueue(this.buildTreeClearedRunnable(event)); + } + } + + public void treeChanged(TreeChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeChanged_(event); + } else { + this.executeOnEventQueue(this.buildTreeChangedRunnable(event)); + } + } + + private Runnable buildNodeAddedRunnable(final TreeAddEvent event) { + return new Runnable() { + public void run() { + AWTTreeChangeListenerWrapper.this.nodeAdded_(event); + } + @Override + public String toString() { + return "node added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildNodeRemovedRunnable(final TreeRemoveEvent event) { + return new Runnable() { + public void run() { + AWTTreeChangeListenerWrapper.this.nodeRemoved_(event); + } + @Override + public String toString() { + return "node removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeClearedRunnable(final TreeClearEvent event) { + return new Runnable() { + public void run() { + AWTTreeChangeListenerWrapper.this.treeCleared_(event); + } + @Override + public String toString() { + return "tree cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeChangedRunnable(final TreeChangeEvent event) { + return new Runnable() { + public void run() { + AWTTreeChangeListenerWrapper.this.treeChanged_(event); + } + @Override + public String toString() { + return "tree changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return EventQueue.isDispatchThread(); + } + + /** + * {@link EventQueue#invokeLater(Runnable)} seems to work OK; + * but using {@link EventQueue#invokeAndWait(Runnable)} can sometimes make + * things more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnEventQueue(Runnable r) { + EventQueue.invokeLater(r); +// try { +// EventQueue.invokeAndWait(r); +// } catch (InterruptedException ex) { +// throw new RuntimeException(ex); +// } catch (java.lang.reflect.InvocationTargetException ex) { +// throw new RuntimeException(ex); +// } + } + + void nodeAdded_(TreeAddEvent event) { + this.listener.nodeAdded(event); + } + + void nodeRemoved_(TreeRemoveEvent event) { + this.listener.nodeRemoved(event); + } + + void treeCleared_(TreeClearEvent event) { + this.listener.treeCleared(event); + } + + void treeChanged_(TreeChangeEvent event) { + this.listener.treeChanged(event); + } + + @Override + public String toString() { + return "AWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractCollectionValueModel.java new file mode 100644 index 0000000000..31aad85651 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractCollectionValueModel.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * This abstract class provides the infrastructure for "lazily" adding listeners + * to an underlying model as necessary. Subclasses will need to engage and + * disegage the underlying model and fire the appropriate collection change + * events. Subclasses must implement the appropriate {@link CollectionValueModel}. + *

+ * Subclasses must implement the following methods:

    + *
  • {@link #engageModel()}

    + * implement this method to add the appropriate listener to the underlying model + *

  • {@link #disengageModel()}

    + * implement this method to remove the appropriate listener from the underlying model + *

+ */ +public abstract class AbstractCollectionValueModel + extends AbstractModel +{ + + // ********** constructor/initialization ********** + + protected AbstractCollectionValueModel() { + super(); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, CollectionChangeListener.class, CollectionValueModel.VALUES); + } + + + // ********** extend change support ********** + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public synchronized void addChangeListener(ChangeListener listener) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addChangeListener(listener); + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public synchronized void addCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + if (collectionName.equals(CollectionValueModel.VALUES) && this.hasNoListeners()) { + this.engageModel(); + } + super.addCollectionChangeListener(collectionName, listener); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public synchronized void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public synchronized void removeCollectionChangeListener(String collectionName, CollectionChangeListener listener) { + super.removeCollectionChangeListener(collectionName, listener); + if (collectionName.equals(CollectionValueModel.VALUES) && this.hasNoListeners()) { + this.disengageModel(); + } + } + + + // ********** queries ********** + + /** + * Return whether the model has no collection value listeners. + */ + protected boolean hasNoListeners() { + return ! this.hasListeners(); + } + + /** + * Return whether the model has any collection value listeners. + */ + protected boolean hasListeners() { + return this.hasAnyCollectionChangeListeners(CollectionValueModel.VALUES); + } + + + // ********** behavior ********** + + /** + * Engage the underlying model. + */ + protected abstract void engageModel(); + + /** + * Stop listening to the underlying model. + */ + protected abstract void disengageModel(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractListValueModel.java new file mode 100644 index 0000000000..b182a52764 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractListValueModel.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This abstract class provides the infrastructure for "lazily" adding listeners + * to an underlying model as necessary. Subclasses will need to engage and + * disegage the underlying model and fire the appropriate list change + * events. Subclasses must implement the appropriate {@link ListValueModel}. + *

+ * Subclasses must implement the following methods:

    + *
  • {@link #engageModel()}

    + * implement this method to add the appropriate listener to the underlying model + *

  • {@link #disengageModel()}

    + * implement this method to remove the appropriate listener from the underlying model + *

+ */ +public abstract class AbstractListValueModel + extends AbstractModel +{ + + // ********** constructor/initialization ********** + + protected AbstractListValueModel() { + super(); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, ListChangeListener.class, ListValueModel.LIST_VALUES); + } + + + // ********** extend change support ********** + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addChangeListener(ChangeListener listener) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addChangeListener(listener); + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addListChangeListener(String listName, ListChangeListener listener) { + if (listName.equals(ListValueModel.LIST_VALUES) && this.hasNoListeners()) { + this.engageModel(); + } + super.addListChangeListener(listName, listener); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public void removeListChangeListener(String listName, ListChangeListener listener) { + super.removeListChangeListener(listName, listener); + if (listName.equals(ListValueModel.LIST_VALUES) && this.hasNoListeners()) { + this.disengageModel(); + } + } + + + // ********** queries ********** + + /** + * Return whether the model has no collection value listeners. + */ + protected boolean hasNoListeners() { + return ! this.hasListeners(); + } + + /** + * Return whether the model has any collection value listeners. + */ + protected boolean hasListeners() { + return this.hasAnyListChangeListeners(ListValueModel.LIST_VALUES); + } + + + // ********** behavior ********** + + /** + * Engage the underlying model. + */ + protected abstract void engageModel(); + + /** + * Stop listening to the underlying model. + */ + protected abstract void disengageModel(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModel.java new file mode 100644 index 0000000000..af624292d6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModel.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This abstract class provides the infrastructure for "lazily" adding listeners + * to an underlying model as necessary. Subclasses will need to engage and + * disegage the underlying model and fire the appropriate property change + * events. Subclasses must implement the appropriate {@link PropertyValueModel}. + *

+ * Subclasses must implement the following methods:

    + *
  • {@link #engageModel()}

    + * implement this method to add the appropriate listener to the underlying model + *

  • {@link #disengageModel()}

    + * implement this method to remove the appropriate listener from the underlying model + *

+ */ +public abstract class AbstractPropertyValueModel + extends AbstractModel +{ + + // ********** constructor/initialization ********** + + protected AbstractPropertyValueModel() { + super(); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, PropertyChangeListener.class, PropertyValueModel.VALUE); + } + + + // ********** extend change support ********** + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public synchronized void addChangeListener(ChangeListener listener) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public synchronized void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + if (propertyName.equals(PropertyValueModel.VALUE) && this.hasNoListeners()) { + this.engageModel(); + } + super.addPropertyChangeListener(propertyName, listener); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + super.removePropertyChangeListener(propertyName, listener); + if (propertyName.equals(PropertyValueModel.VALUE) && this.hasNoListeners()) { + this.disengageModel(); + } + } + + + // ********** queries ********** + + /** + * Return whether the model has no property value listeners. + */ + protected boolean hasNoListeners() { + return ! this.hasListeners(); + } + + /** + * Return whether the model has any property value listeners. + */ + protected boolean hasListeners() { + return this.hasAnyPropertyChangeListeners(PropertyValueModel.VALUE); + } + + + // ********** behavior ********** + + /** + * Engage the underlying model. + */ + protected abstract void engageModel(); + + /** + * Stop listening to the underlying model. + */ + protected abstract void disengageModel(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModelAdapter.java new file mode 100644 index 0000000000..b2dde293a2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModelAdapter.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * a model, "lazily" listen to it, and convert + * its change notifications into property value model change + * notifications. + *

+ * Subclasses must implement:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current model + *

  • {@link #engageModel_()}

    + * to start listening to the adapted model + *

  • {@link #disengageModel_()}

    + * to stop listening to the adapted model + *

+ * Subclasses can call {@link #propertyChanged()} whenever the calculated + * value of the property changes (as determined by the subclass). + */ +public abstract class AbstractPropertyValueModelAdapter + extends AbstractPropertyValueModel + implements PropertyValueModel +{ + /** + * Cache the current value so we can pass an "old value" when + * we fire a property change event. + * We need this because the value may be calculated and we may + * not able to derive the "old value" from any fired events. + */ + protected V value; + + + // ********** constructor/initialization ********** + + protected AbstractPropertyValueModelAdapter() { + super(); + // our value is null when we are not listening to the model + this.value = null; + } + + + // ********** PropertyValueModel implementation ********** + + /** + * Return the cached value. + */ + public V getValue() { + return this.value; + } + + + // ********** behavior ********** + + /** + * Start listening to the model and build the value. + */ + @Override + protected void engageModel() { + this.engageModel_(); + // synch our value *after* we start listening to the model, + // since the model's value might change when a listener is added + this.value = this.buildValue(); + } + + /** + * Start listening to the model. + */ + protected abstract void engageModel_(); + + /** + * Build and return the current value, as derived from the + * current state of the underlying model. + */ + protected abstract V buildValue(); + + /** + * Stop listening to the model and clear the value. + */ + @Override + protected void disengageModel() { + this.disengageModel_(); + // clear out our value when we are not listening to the model + this.value = null; + } + + /** + * Stop listening to the model. + */ + protected abstract void disengageModel_(); + + /** + * The underlying model changed in some fashion. + * Recalculate the value and notify any listeners. + */ + protected void propertyChanged() { + Object old = this.value; + this.firePropertyChanged(VALUE, old, this.value = this.buildValue()); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.value); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractTreeNodeValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractTreeNodeValueModel.java new file mode 100644 index 0000000000..657676e34d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractTreeNodeValueModel.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; +import java.util.List; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterators.ChainIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.TreeNodeValueModel; + +/** + * Subclasses need only implement the following methods: + * + * #value() + * return the user-determined "value" of the node, + * i.e. the object "wrapped" by the node + * + * #setValue(Object) + * set the user-determined "value" of the node, + * i.e. the object "wrapped" by the node; + * typically only overridden for nodes with "primitive" values + * + * #parent() + * return the parent of the node, which should be another + * TreeNodeValueModel + * + * #childrenModel() + * return a ListValueModel for the node's children + * + * #engageValue() and #disengageValue() + * override these methods to listen to the node's value if + * it can change in a way that should be reflected in the tree + */ +public abstract class AbstractTreeNodeValueModel + extends AbstractModel + implements TreeNodeValueModel +{ + + + // ********** constructors ********** + + /** + * Default constructor. + */ + protected AbstractTreeNodeValueModel() { + super(); + } + + @Override + protected ChangeSupport buildChangeSupport() { + // this model fires *both* "value property change" and "state change" events... +// return new SingleAspectChangeSupport(this, PropertyChangeListener.class, PropertyValueModel.VALUE); + return super.buildChangeSupport(); + } + + + // ********** extend AbstractModel implementation ********** + + /** + * Clients should be adding both "state change" and "value property change" + * listeners. + */ + @Override + public void addStateChangeListener(StateChangeListener listener) { + if (this.hasNoStateChangeListeners()) { + this.engageValue(); + } + super.addStateChangeListener(listener); + } + + /** + * Begin listening to the node's value's state. If the state of the node changes + * in a way that should be reflected in the tree, fire a "state change" event. + */ + protected abstract void engageValue(); + + /** + * @see #addStateChangeListener(StateChangeListener) + */ + @Override + public void removeStateChangeListener(StateChangeListener listener) { + super.removeStateChangeListener(listener); + if (this.hasNoStateChangeListeners()) { + this.disengageValue(); + } + } + + /** + * Stop listening to the node's value. + * @see #engageValue() + */ + protected abstract void disengageValue(); + + + // ********** WritablePropertyValueModel implementation ********** + + public void setValue(T value) { + throw new UnsupportedOperationException(); + } + + + // ********** TreeNodeValueModel implementation ********** + + @SuppressWarnings("unchecked") + public TreeNodeValueModel[] path() { + List> path = CollectionTools.reverseList(this.backPath()); + return path.toArray(new TreeNodeValueModel[path.size()]); + } + + /** + * Return an iterator that climbs up the node's path, + * starting with, and including, the node + * and up to, and including, the root node. + */ + protected Iterator> backPath() { + return new ChainIterator>(this) { + @Override + protected TreeNodeValueModel nextLink(TreeNodeValueModel currentLink) { + return currentLink.parent(); + } + }; + } + + public TreeNodeValueModel child(int index) { + return this.childrenModel().get(index); + } + + public int childrenSize() { + return this.childrenModel().size(); + } + + public int indexOfChild(TreeNodeValueModel child) { + ListValueModel> children = this.childrenModel(); + int size = children.size(); + for (int i = 0; i < size; i++) { + if (children.get(i) == child) { + return i; + } + } + return -1; + } + + public boolean isLeaf() { + return this.childrenModel().size() == 0; + } + + + // ********** standard methods ********** + + /** + * We implement #equals(Object) so that TreePaths containing these nodes + * will resolve properly when the nodes contain the same values. This is + * necessary because nodes are dropped and rebuilt willy-nilly when dealing + * with a sorted list of children; and this allows us to save and restore + * a tree's expanded paths. The nodes in the expanded paths that are + * saved before any modification (e.g. renaming a node) will be different + * from the nodes in the tree's paths after the modification, if the modification + * results in a possible change in the node sort order. ~bjv + */ + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o.getClass() != this.getClass()) { + return false; + } + @SuppressWarnings("unchecked") + AbstractTreeNodeValueModel other = (AbstractTreeNodeValueModel) o; + return this.getValue().equals(other.getValue()); + } + + @Override + public int hashCode() { + return this.getValue().hashCode(); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectAdapter.java new file mode 100644 index 0000000000..99f17918c3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectAdapter.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This abstract extension of {@link AbstractModel} provides a base for adding + * change listeners (property change, collection change, list change, tree change) + * to a subject and converting the subject's change notifications into a single + * set of change notifications for a common aspect (e.g. VALUE). + *

+ * The adapter will only listen to the subject (and subject holder) when the + * adapter itself actually has listeners. This will allow the adapter to be + * garbage collected when appropriate + */ +public abstract class AspectAdapter + extends AbstractModel +{ + /** + * The subject that holds the aspect and fires + * change notification when the aspect changes. + * We need to hold on to this directly so we can + * disengage it when it changes. + */ + protected S subject; + + /** + * A value model that holds the subject + * that holds the aspect and provides change notification. + * This is useful when there are a number of aspect adapters + * that have the same subject and that subject can change. + * All the aspect adapters should share the same subject holder. + * For now, this is can only be set upon construction and is + * immutable. + */ + protected final PropertyValueModel subjectHolder; + + /** A listener that keeps us in synch with the subject holder. */ + protected final PropertyChangeListener subjectChangeListener; + + + // ********** constructors ********** + + /** + * Construct an aspect adapter for the specified subject. + */ + protected AspectAdapter(S subject) { + this(new StaticPropertyValueModel(subject)); + } + + /** + * Construct an aspect adapter for the specified subject holder. + * The subject holder cannot be null. + */ + protected AspectAdapter(PropertyValueModel subjectHolder) { + super(); + if (subjectHolder == null) { + throw new NullPointerException(); + } + this.subjectHolder = subjectHolder; + this.subjectChangeListener = this.buildSubjectChangeListener(); + // the subject is null when we are not listening to it + // this will typically result in our value being null + this.subject = null; + } + + + // ********** initialization ********** + + @Override + protected ChangeSupport buildChangeSupport() { + return new LocalChangeSupport(this, this.getListenerClass(), this.getListenerAspectName()); + } + + /** + * The subject holder's value has changed, keep our subject in synch. + */ + protected PropertyChangeListener buildSubjectChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + AspectAdapter.this.subjectChanged(); + } + @Override + public String toString() { + return "subject change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * The subject has changed. Notify listeners that the value has changed. + */ + protected synchronized void subjectChanged() { + Object oldValue = this.getValue(); + boolean hasListeners = this.hasListeners(); + if (hasListeners) { + this.disengageSubject(); + } + this.subject = this.subjectHolder.getValue(); + if (hasListeners) { + this.engageSubject(); + this.fireAspectChanged(oldValue, this.getValue()); + } + } + + /** + * Return the aspect's current value. + */ + protected abstract Object getValue(); + + /** + * Return the class of listener that is interested in the aspect adapter's + * changes. + */ + protected abstract Class getListenerClass(); + + /** + * Return the name of the aspect adapter's aspect (e.g. VALUE). + * This is the name of the aspect adapter's single aspect, not the + * name of the subject's aspect the aspect adapter is adapting. + */ + protected abstract String getListenerAspectName(); + + /** + * Return whether there are any listeners for the aspect. + */ + protected abstract boolean hasListeners(); + + /** + * Return whether there are no listeners for the aspect. + */ + protected boolean hasNoListeners() { + return ! this.hasListeners(); + } + + /** + * The aspect has changed, notify listeners appropriately. + */ + protected abstract void fireAspectChanged(Object oldValue, Object newValue); + + protected void engageSubject() { + // check for nothing to listen to + if (this.subject != null) { + this.engageSubject_(); + } + } + + /** + * The subject is not null - add our listener. + */ + protected abstract void engageSubject_(); + + protected void disengageSubject() { + // check for nothing to listen to + if (this.subject != null) { + this.disengageSubject_(); + } + } + + /** + * The subject is not null - remove our listener. + */ + protected abstract void disengageSubject_(); + + protected void engageSubjectHolder() { + this.subjectHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + // synch our subject *after* we start listening to the subject holder, + // since its value might change when a listener is added + this.subject = this.subjectHolder.getValue(); + } + + protected void disengageSubjectHolder() { + this.subjectHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + // clear out the subject when we are not listening to its holder + this.subject = null; + } + + protected void engageModels() { + this.engageSubjectHolder(); + this.engageSubject(); + } + + protected void disengageModels() { + this.disengageSubject(); + this.disengageSubjectHolder(); + } + + + // ********** local change support ********** + + /** + * Extend change support to start listening to the aspect adapter's + * models (the subject holder and the subject itself) when the first + * relevant listener is added. + * Conversely, stop listening to the aspect adapter's models when the + * last relevant listener is removed. + * A relevant listener is a listener of the relevant type and aspect or a + * general-purpose listener. + */ + protected class LocalChangeSupport extends SingleAspectChangeSupport { + private static final long serialVersionUID = 1L; + + public LocalChangeSupport(AspectAdapter source, Class validListenerClass, String validAspectName) { + super(source, validListenerClass, validAspectName); + } + + protected boolean hasNoListeners() { + return this.hasNoListeners(this.validListenerClass, this.validAspectName); + } + + + // ********** overrides ********** + + @Override + protected synchronized void addListener(Class listenerClass, T listener) { + if (this.hasNoListeners()) { + AspectAdapter.this.engageModels(); + } + super.addListener(listenerClass, listener); + } + + @Override + protected synchronized void addListener(Class listenerClass, String aspectName, T listener) { + if (this.hasNoListeners()) { + AspectAdapter.this.engageModels(); + } + super.addListener(listenerClass, aspectName, listener); + } + + @Override + protected synchronized void removeListener(Class listenerClass, T listener) { + super.removeListener(listenerClass, listener); + if (this.hasNoListeners()) { + AspectAdapter.this.disengageModels(); + } + } + + @Override + protected synchronized void removeListener(Class listenerClass, String aspectName, T listener) { + super.removeListener(listenerClass, aspectName, listener); + if (this.hasNoListeners()) { + AspectAdapter.this.disengageModels(); + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectCollectionValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectCollectionValueModelAdapter.java new file mode 100644 index 0000000000..9c7f316629 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectCollectionValueModelAdapter.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Collection; +import java.util.EventListener; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This {@link AspectAdapter} provides basic collection change support. + * This converts an "aspect" (as defined by subclasses) into + * a single {@link #VALUES} collection. + *

+ * The typical subclass will override the following methods:

    + *
  • {@link #engageSubject_()}

    + * implement this method to add the appropriate listener to the subject + *

  • {@link #disengageSubject_()}

    + * implement this method to remove the appropriate listener from the subject + *

  • {@link #getIterable()}

    + * at the very minimum, override this method to return an iterable containing the + * subject's collection aspect; it does not need to be overridden if either + * {@link #iterator_()} or {@link #iterator()} is overridden and its behavior changed + *

  • {@link #size_()}

    + * override this method to improve performance; it does not need to be overridden if + * {@link #size()} is overridden and its behavior changed + *

  • {@link #iterator_()}

    + * override this method to return an iterator on the + * subject's collection aspect if it is not possible to implement {@link #getIterable()}; + * it does not need to be overridden if + * {@link #iterator()} is overridden and its behavior changed + *

  • {@link #iterator()}

    + * override this method only if returning an empty iterator when the + * subject is null is unacceptable + *

  • {@link #size()}

    + * override this method only if returning a zero when the + * subject is null is unacceptable + *

+ * To notify listeners, subclasses can call {@link #collectionChanged()} + * whenever the aspect has changed. + */ +public abstract class AspectCollectionValueModelAdapter + extends AspectAdapter + implements CollectionValueModel +{ + + // ********** constructors ********** + + /** + * Construct a collection value model adapter for an aspect of the + * specified subject. + */ + protected AspectCollectionValueModelAdapter(PropertyValueModel subjectHolder) { + super(subjectHolder); + } + + + // ********** CollectionValueModel implementation ********** + + /** + * Return the elements of the subject's collection aspect. + */ + public Iterator iterator() { + return (this.subject == null) ? EmptyIterator.instance() : this.iterator_(); + } + + /** + * Return the elements of the subject's collection aspect. + * At this point we can be sure the subject is not null. + * @see #iterator() + */ + protected Iterator iterator_() { + return this.getIterable().iterator(); + } + + /** + * Return the elements of the subject's collection aspect. + * At this point we can be sure the subject is not null. + * @see #iterator_() + */ + protected Iterable getIterable() { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * Return the size of the subject's collection aspect. + */ + public int size() { + return (this.subject == null) ? 0 : this.size_(); + } + + /** + * Return the size of the subject's collection aspect. + * At this point we can be sure the subject is not null. + * @see #size() + */ + protected int size_() { + return CollectionTools.size(this.iterator()); + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected Object getValue() { + return this.buildValueCollection(); + } + + @Override + protected Class getListenerClass() { + return CollectionChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return VALUES; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyCollectionChangeListeners(VALUES); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + @SuppressWarnings("unchecked") Collection newCollection = (Collection) newValue; + this.fireCollectionChanged(VALUES, newCollection); + } + + protected void collectionChanged() { + this.fireCollectionChanged(VALUES, this.buildValueCollection()); + } + + protected Collection buildValueCollection() { + return CollectionTools.collection(this.iterator()); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.buildValueCollection()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectListValueModelAdapter.java new file mode 100644 index 0000000000..41d36a066c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectListValueModelAdapter.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.EventListener; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyListIterator; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This {@link AspectAdapter} provides basic list change support. + * This converts an "aspect" (as defined by subclasses) into + * a single {@link #LIST_VALUES} list. + *

+ * The typical subclass will override the following methods:

    + *
  • {@link #engageSubject_()}

    + * implement this method to add the appropriate listener to the subject + *

  • {@link #disengageSubject_()}

    + * implement this method to remove the appropriate listener from the subject + *

  • {@link #getListIterable()}

    + * at the very minimum, override this method to return a list iterable containing the + * subject's list aspect; it does not need to be overridden if either + * {@link #listIterator_()} or {@link #listIterator()} is overridden and its behavior changed + *

  • {@link #get(int)}

    + * override this method to improve performance + *

  • {@link #size_()}

    + * override this method to improve performance; it does not need to be overridden if + * {@link #size()} is overridden and its behavior changed + *

  • {@link #toArray_()}

    + * override this method to improve performance; it does not need to be overridden if + * {@link #toArray()} is overridden and its behavior changed + *

  • {@link #listIterator_()}

    + * override this method to return a list iterator on the subject's list + * aspect if it is not possible to implement {@link #getListIterable()}; + * it does not need to be overridden if + * {@link #listIterator()} is overridden and its behavior changed + *

  • {@link #listIterator()}

    + * override this method only if returning an empty list iterator when the + * subject is null is unacceptable + *

  • {@link #size()}

    + * override this method only if returning a zero when the + * subject is null is unacceptable + *

  • {@link #toArray()}

    + * override this method only if returning an empty array when the + * subject is null is unacceptable + *

+ * To notify listeners, subclasses can call {@link #listChanged()} + * whenever the aspect has changed. + */ +public abstract class AspectListValueModelAdapter + extends AspectAdapter + implements ListValueModel +{ + private static final Object[] EMPTY_ARRAY = new Object[0]; + + + // ********** constructors ********** + + /** + * Construct a list value model adapter for an aspect of the + * specified subject. + */ + protected AspectListValueModelAdapter(PropertyValueModel subjectHolder) { + super(subjectHolder); + } + + + // ********** ListValueModel implementation ********** + + /** + * Return the elements of the subject's list aspect. + */ + public ListIterator iterator() { + return this.listIterator(); + } + + /** + * Return the elements of the subject's list aspect. + */ + public ListIterator listIterator() { + return (this.subject == null) ? EmptyListIterator.instance() : this.listIterator_(); + } + + /** + * Return the elements of the subject's list aspect. + * At this point we can be sure the subject is not null. + * @see #listIterator() + */ + protected ListIterator listIterator_() { + return this.getListIterable().iterator(); + } + + /** + * Return the elements of the subject's list aspect. + * At this point we can be sure the subject is not null. + * @see #listIterator_() + */ + protected ListIterable getListIterable() { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * Return the element at the specified index of the subject's list aspect. + */ + public E get(int index) { + return CollectionTools.get(this.listIterator(), index); + } + + /** + * Return the size of the subject's list aspect. + */ + public int size() { + return this.subject == null ? 0 : this.size_(); + } + + /** + * Return the size of the subject's list aspect. + * At this point we can be sure the subject is not null. + * @see #size() + */ + protected int size_() { + return CollectionTools.size(this.listIterator()); + } + + /** + * Return an array manifestation of the subject's list aspect. + */ + public Object[] toArray() { + return this.subject == null ? EMPTY_ARRAY : this.toArray_(); + } + + /** + * Return an array manifestation of the subject's list aspect. + * At this point we can be sure the subject is not null. + * @see #toArray() + */ + protected Object[] toArray_() { + return ArrayTools.array(this.listIterator(), this.size()); + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected List getValue() { + return this.buildValueList(); + } + + @Override + protected Class getListenerClass() { + return ListChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return LIST_VALUES; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyListChangeListeners(LIST_VALUES); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + @SuppressWarnings("unchecked") List newList = (List) newValue; + this.fireListChanged(LIST_VALUES, newList); + } + + protected void listChanged() { + this.fireListChanged(LIST_VALUES, this.buildValueList()); + } + + protected List buildValueList() { + return CollectionTools.list(this.iterator()); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.buildValueList()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectPropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectPropertyValueModelAdapter.java new file mode 100644 index 0000000000..180153a586 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectPropertyValueModelAdapter.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This {@link AspectAdapter} provides basic property change support. + * This converts an "aspect" (as defined by subclasses) into + * a single {@link #VALUE} property. + *

+ * The typical subclass will override the following methods:

    + *
  • {@link #engageSubject_()}

    + * implement this method to add the appropriate listener to the subject + *

  • {@link #disengageSubject_()}

    + * implement this method to remove the appropriate listener from the subject + *

  • {@link #buildValue_()}

    + * at the very minimum, override this method to return the value of the + * subject's aspect (or "virtual" aspect); it does not need to be + * overridden if {@link #buildValue()} is overridden and its behavior changed + *

  • {@link #setValue_(Object)}

    + * override this method if the client code needs to set the value of + * the subject's aspect; oftentimes, though, the client code (e.g. UI) + * will need only to get the value; it does not need to be + * overridden if {@link #setValue(Object)} is overridden and its behavior changed + *

  • {@link #buildValue()}

    + * override this method only if returning a null value when + * the subject is null is unacceptable + *

  • {@link #setValue(Object)}

    + * override this method only if something must be done when the subject + * is null (e.g. throw an exception) + *

+ * To notify listeners, subclasses can call {@link #propertyChanged()} + * whenever the aspect has changed. + */ +public abstract class AspectPropertyValueModelAdapter + extends AspectAdapter + implements WritablePropertyValueModel +{ + /** + * Cache the current value of the aspect so we + * can pass an "old value" when we fire a property change event. + * We need this because the value may be calculated and may + * not be in the property change event fired by the subject, + * especially when dealing with multiple aspects. + */ + protected V value; + + + // ********** constructors ********** + + /** + * Construct a property value model adapter for an aspect of the + * specified subject. + */ + protected AspectPropertyValueModelAdapter(PropertyValueModel subjectHolder) { + super(subjectHolder); + // our value is null when we are not listening to the subject + this.value = null; + } + + + // ********** PropertyValueModel implementation ********** + + /** + * Return the value of the subject's aspect. + */ + @Override + public final V getValue() { + return this.value; + } + + + // ********** WritablePropertyValueModel implementation ********** + + /** + * Set the value of the subject's aspect. + */ + public void setValue(V value) { + if (this.subject != null) { + this.setValue_(value); + } + } + + /** + * Set the value of the subject's aspect. + * At this point we can be sure the subject is not null. + * @see #setValue(Object) + */ + protected void setValue_(@SuppressWarnings("unused") V value) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected Class getListenerClass() { + return PropertyChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return VALUE; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyPropertyChangeListeners(VALUE); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + this.firePropertyChanged(VALUE, oldValue, newValue); + } + + @Override + protected void engageSubject() { + super.engageSubject(); + // synch our value *after* we start listening to the subject, + // since its value might change when a listener is added + this.value = this.buildValue(); + } + + @Override + protected void disengageSubject() { + super.disengageSubject(); + // clear out our value when we are not listening to the subject + this.value = null; + } + + + // ********** behavior ********** + + /** + * Return the aspect's value. + * At this point the subject may be null. + */ + protected V buildValue() { + return (this.subject == null) ? null : this.buildValue_(); + } + + /** + * Return the value of the subject's aspect. + * At this point we can be sure the subject is not null. + * @see #buildValue() + */ + protected V buildValue_() { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * This method can be called by subclasses whenever the subject's aspect + * has changed; listeners will be notified appropriately. + */ + protected void propertyChanged() { + V old = this.value; + this.value = this.buildValue(); + this.fireAspectChanged(old, this.value); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.value); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectTreeValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectTreeValueModelAdapter.java new file mode 100644 index 0000000000..6989991744 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectTreeValueModelAdapter.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Collection; +import java.util.EventListener; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.TreeValueModel; + +/** + * This {@link AspectAdapter} provides basic tree change support. + * This converts an "aspect" (as defined by subclasses) into + * a single {@link #NODES} tree. + *

+ * The typical subclass will override the following methods:

    + *
  • {@link #engageSubject_()}

    + * implement this method to add the appropriate listener to the subject + *

  • {@link #disengageSubject_()}

    + * implement this method to remove the appropriate listener from the subject + *

  • {@link #nodes_()}

    + * at the very minimum, override this method to return an iterator + * on the subject's tree aspect; it does not need to be overridden if + * {@link #nodes()} is overridden and its behavior changed + *

  • {@link #nodes()}

    + * override this method only if returning an empty iterator when the + * subject is null is unacceptable + *

+ * To notify listeners, subclasses can call {@link #treeChanged()} + * whenever the aspect has changed. + */ +public abstract class AspectTreeValueModelAdapter + extends AspectAdapter + implements TreeValueModel +{ + + // ********** constructors ********** + + /** + * Construct a tree value model adapter for an aspect of the + * specified subject. + */ + protected AspectTreeValueModelAdapter(PropertyValueModel subjectHolder) { + super(subjectHolder); + } + + + // ********** TreeValueModel implementation ********** + + /** + * Return the nodes of the subject's tree aspect. + */ + public Iterator nodes() { + return (this.subject == null) ? EmptyIterator.instance() : this.nodes_(); + } + + /** + * Return the nodes of the subject's tree aspect. + * At this point we can be sure the subject is not null. + * @see #nodes() + */ + protected Iterator nodes_() { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected Object getValue() { + return this.buildValueCollection(); + } + + @Override + protected Class getListenerClass() { + return TreeChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return NODES; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyTreeChangeListeners(NODES); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + @SuppressWarnings("unchecked") Collection newNodes = (Collection) newValue; + this.fireTreeChanged(NODES, newNodes); + } + + protected void treeChanged() { + this.fireTreeChanged(NODES, this.buildValueCollection()); + } + + protected Collection buildValueCollection() { + return CollectionTools.collection(this.nodes()); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.buildValueCollection()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/BufferedWritablePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/BufferedWritablePropertyValueModel.java new file mode 100644 index 0000000000..87d191ca6a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/BufferedWritablePropertyValueModel.java @@ -0,0 +1,392 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * A BufferedWritablePropertyValueModel is used to hold a temporary copy of the value + * in another property value model (the "wrapped" value holder). The application + * can modify this temporary copy, ad nauseam; but the temporary copy is only + * passed through to the "wrapped" value holder when the trigger "accepts" the + * buffered value. Alternatively, the application can "reset" the buffered value + * to the original, "wrapped" value. + *

+ * The trigger is another {@link PropertyValueModel} that holds a {@link Boolean} + * and the application changes the trigger's value to true on "accept", false on "reset". + * Typically, in a dialog:

    + *
  • pressing the "OK" button will trigger an "accept" and close the dialog + *
  • pressing the "Cancel" button will simply close the dialog, + * dropping the "buffered" values into the bit bucket + *
  • pressing the "Apply" button will trigger an "accept" and leave the dialog open + *
  • pressing the "Restore" button will trigger a "reset" and leave the dialog open + *
+ *

+ * A number of buffered property value models can wrap another set of + * property aspect adapters that adapt the various aspects of a single + * domain model. All the bufferd property value models can be hooked to the + * same trigger, and that trigger is controlled by the application, typically + * via the OK button in a dialog. + * + * @see PropertyAspectAdapter + */ +public class BufferedWritablePropertyValueModel + extends PropertyValueModelWrapper + implements WritablePropertyValueModel +{ + + /** + * We cache the value here until it is accepted and passed + * through to the wrapped value holder. + */ + protected T bufferedValue; + + /** + * This is set to true when we are "accepting" the buffered value + * and passing it through to the wrapped value holder. This allows + * us to ignore the property change event fired by the wrapped + * value holder. + * (We can't stop listening to the wrapped value holder, because + * if we are the only listener that could "deactivate" the wrapped + * value holder.) + */ + protected boolean accepting; + + /** + * This is the trigger that indicates whether the buffered value + * should be accepted or reset. + */ + protected final PropertyValueModel triggerHolder; + + /** This listens to the trigger holder. */ + protected final PropertyChangeListener triggerChangeListener; + + /** + * This flag indicates whether our buffered value has been assigned + * a value and is possibly out of synch with the wrapped value. + */ + protected boolean buffering; + + + // ********** constructors ********** + + /** + * Construct a buffered property value model with the specified wrapped + * property value model and trigger holder. + */ + public BufferedWritablePropertyValueModel(WritablePropertyValueModel valueHolder, PropertyValueModel triggerHolder) { + super(valueHolder); + if (triggerHolder == null) { + throw new NullPointerException(); + } + this.triggerHolder = triggerHolder; + this.bufferedValue = null; + this.buffering = false; + this.accepting = false; + this.triggerChangeListener = this.buildTriggerChangeListener(); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildTriggerChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + BufferedWritablePropertyValueModel.this.triggerChanged(event); + } + @Override + public String toString() { + return "trigger change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ValueModel implementation ********** + + /** + * If we are currently "buffering" a value, return that; + * otherwise, return the wrapped value. + */ + public T getValue() { + return this.buffering ? this.bufferedValue : this.valueHolder.getValue(); + } + + /** + * Assign the new value to our "buffered" value. + * It will be pushed to the wrapped value holder + * when the trigger is "accepted". + */ + public void setValue(T value) { + if (this.buffering) { + if (this.valuesAreEqual(value, this.valueHolder.getValue())) { + // the buffered value is being set back to the original value + this.reset(); + } else { + // the buffered value is being changed + Object old = this.bufferedValue; + this.bufferedValue = value; + this.firePropertyChanged(VALUE, old, value); + } + } else { + if (this.valuesAreEqual(value, this.valueHolder.getValue())) { + // the buffered value is being set to the same value as the original value - ignore + } else { + // the buffered value is being set for the first time + Object old = this.valueHolder.getValue(); + this.bufferedValue = value; + this.buffering = true; + this.firePropertyChanged(VALUE, old, value); + } + } + } + + + // ********** PropertyValueModelWrapper extensions ********** + + /** + * extend to engage the trigger holder also + */ + @Override + protected void engageModel() { + super.engageModel(); + this.triggerHolder.addPropertyChangeListener(VALUE, this.triggerChangeListener); + } + + /** + * extend to disengage the trigger holder also + */ + @Override + protected void disengageModel() { + this.triggerHolder.removePropertyChangeListener(VALUE, this.triggerChangeListener); + super.disengageModel(); + } + + + // ********** behavior ********** + + /** + * If we are currently "accepting" the value (i.e passing it on to the + * "wrapped" model), ignore change notifications, since we caused + * them and our own listeners are already aware of the change. + */ + @Override + protected void valueChanged(PropertyChangeEvent event) { + if ( ! this.accepting) { + this.valueChanged_(event); + } + } + + /** + * If we have a "buffered" value, check whether the "wrapped" value has + * changed to be the same as the "buffered" value. If it has, stop "buffering"; + * if not, do nothing. + * If we do not yet have a "buffered" value, simply propagate the + * change notification with the buffered model as the source. + */ + protected void valueChanged_(PropertyChangeEvent event) { + if (this.buffering) { + if (this.valuesAreEqual(event.getNewValue(), this.bufferedValue)) { + // the buffered value is being set back to the original value + this.reset(); + } else { + this.handleChangeConflict(event); + } + } else { + this.firePropertyChanged(event.clone(this)); + } + } + + /** + * By default, if we have a "buffered" value and the "wrapped" value changes, + * we simply ignore the new "wrapped" value and simply overlay it with the + * "buffered" value if it is "accepted". ("Last One In Wins" concurrency model) + * Subclasses can override this method to change that behavior with a + * different concurrency model. For example, you could drop the "buffered" value + * and replace it with the new "wrapped" value, or you could throw an + * exception. + */ + protected void handleChangeConflict(@SuppressWarnings("unused") PropertyChangeEvent event) { + // the default is to do nothing + } + + /** + * The trigger changed:

    + *
  • If it is now true, "accept" the buffered value and push + * it to the wrapped value holder. + *
  • If it is now false, "reset" the buffered value to its original value. + *
+ */ + protected void triggerChanged(PropertyChangeEvent event) { + // if nothing has been "buffered", we don't need to do anything: + // nothing needs to be passed through; nothing needs to be reset; + if (this.buffering) { + if (((Boolean) event.getNewValue()).booleanValue()) { + this.accept(); + } else { + this.reset(); + } + } + } + + protected void accept() { + // set the accepting flag so we ignore any events + // fired by the wrapped value holder + this.accepting = true; + try { + this.getValueHolder().setValue(this.bufferedValue); + } finally { + this.bufferedValue = null; + this.buffering = false; + // clear the flag once the "accept" is complete + this.accepting = false; + } + } + + protected void reset() { + // notify our listeners that our value has been reset + Object old = this.bufferedValue; + this.bufferedValue = null; + this.buffering = false; + this.firePropertyChanged(VALUE, old, this.valueHolder.getValue()); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + + + // ********** convenience methods ********** + + /** + * Return whether the buffered model is currently "buffering" + * a value. + */ + public boolean isBuffering() { + return this.buffering; + } + + /** + * Our constructor accepts only a {@link WritablePropertyValueModel}{@code}. + */ + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + + + // ********** inner class ********** + + /** + * Trigger is a special property value model that only maintains its + * value (of true or false) during the change notification caused by + * {@link #setValue(T)}. In other words, a Trigger + * only has a valid value when it is being set. + */ + public static class Trigger extends SimplePropertyValueModel { + + + // ********** constructor ********** + + /** + * Construct a trigger with a null value. + */ + public Trigger() { + super(); + } + + + // ********** ValueModel implementation ********** + + /** + * Extend so that this method can only be invoked during + * change notification triggered by {@link #setValue(Object)}. + */ + @Override + public Boolean getValue() { + if (this.value == null) { + throw new IllegalStateException("The method Trigger.getValue() may only be called during change notification."); //$NON-NLS-1$ + } + return this.value; + } + + /** + * Extend to reset the value to null once all the + * listeners have been notified. + */ + @Override + public void setValue(Boolean value) { + super.setValue(value); + this.value = null; + } + + + // ********** convenience methods ********** + + /** + * Set the trigger's value:
    + *
  • true indicates "accept" + *
  • false indicates "reset" + *
+ */ + public void setValue(boolean value) { + this.setValue(Boolean.valueOf(value)); + } + + /** + * Return the trigger's value:
    + *
  • true indicates "accept" + *
  • false indicates "reset" + *
+ * This method can only be invoked during change notification. + */ + public boolean booleanValue() { + return this.getValue().booleanValue(); + } + + /** + * Accept the trigger (i.e. set its value to true). + */ + public void accept() { + this.setValue(true); + } + + /** + * Return whether the trigger has been accepted + * (i.e. its value was changed to true). + * This method can only be invoked during change notification. + */ + public boolean isAccepted() { + return this.booleanValue(); + } + + /** + * Reset the trigger (i.e. set its value to false). + */ + public void reset() { + this.setValue(false); + } + + /** + * Return whether the trigger has been reset + * (i.e. its value was changed to false). + * This method can only be invoked during change notification. + */ + public boolean isReset() { + return ! this.booleanValue(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationPropertyValueModel.java new file mode 100644 index 0000000000..c62e6853bf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationPropertyValueModel.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A CachingTransformationPropertyValueModel wraps another + * {@link PropertyValueModel} and uses a {@link Transformer} + * to transform the wrapped value before it is returned by {@link #getValue()}. + * The transformed value is calculated and cached during initialization and every + * time the wrapped value changes. This can be useful when the old value + * passed in to {@link #valueChanged(org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent)} + * can no longer be "transformed" because its state is no longer valid. + * This caching can also improve time performance in some situations. + *

+ * As an alternative to building a {@link Transformer}, + * a subclass of CachingTransformationPropertyValueModel can + * either override {@link #transform_(Object)} or, + * if something other than null should be returned when the wrapped value + * is null, override {@link #transform(Object)}. + */ +public class CachingTransformationPropertyValueModel + extends TransformationPropertyValueModel +{ + /** + * Cache the transformed value so that during property change event notification + * we do not have to transform the old value. The old value could no longer be valid in + * the model; as a result, transforming it would not be valid. + */ + protected T2 cachedValue; + + + // ********** constructors/initialization ********** + + /** + * Construct a property value model with the specified nested + * property value model and the default transformer. + * Use this constructor if you want to override + * {@link #transform_(Object)} or {@link #transform(Object)} + * instead of building a {@link Transformer}. + */ + public CachingTransformationPropertyValueModel(PropertyValueModel valueHolder) { + super(valueHolder); + } + + /** + * Construct an property value model with the specified nested + * property value model and transformer. + */ + public CachingTransformationPropertyValueModel(PropertyValueModel valueHolder, Transformer transformer) { + super(valueHolder, transformer); + } + + + // ********** behavior ********** + + /** + * We have listeners, transform the nested value and cache the result. + */ + @Override + protected void engageModel() { + super.engageModel(); + this.cachedValue = this.transform(this.valueHolder.getValue()); + } + + /** + * We have no more listeners, clear the cached value. + */ + @Override + protected void disengageModel() { + this.cachedValue = null; + super.disengageModel(); + } + + /** + * No need to transform the nested value, simply return the cached value, + * which is already transformed. + */ + @Override + public T2 getValue() { + return this.cachedValue; + } + + /** + * Transform the specified new value, caching it before returning it. + * A bit of a side-effect, but it seems reasonable. + */ + @Override + protected T2 transformNew(T1 value) { + this.cachedValue = super.transformNew(value); + return this.cachedValue; + } + + /** + * No need to transform the old value, simply return the cached value, + * which is already transformed. + */ + @Override + protected T2 transformOld(T1 value) { + return this.cachedValue; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationWritablePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationWritablePropertyValueModel.java new file mode 100644 index 0000000000..1560367c3e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationWritablePropertyValueModel.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.BidiTransformer; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * A CachingTransformationWritablePropertyValueModel augments the + * behavior of a {@link TransformationWritablePropertyValueModel} by caching + * the transformed value. + * The transformed value is calculated and cached during initialization and every + * time the wrapped value changes. This can be useful when the old value + * passed in to {@link #valueChanged(org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent)} + * can no longer be "transformed" because its state is no longer valid. + * This caching can also improve time performance in some situations. + */ +public class CachingTransformationWritablePropertyValueModel + extends TransformationWritablePropertyValueModel +{ + /** + * Cache the transformed value so that during property change event notification + * we do not have to transform the old value. The old value could no longer be valid in + * the model; as a result, transforming it would not be valid. + */ + protected T2 cachedValue; + + + // ********** constructors/initialization ********** + + /** + * Construct a writable property value model with the specified nested + * writable property value model and the default bidi transformer. + * Use this constructor if you want to override + * {@link #transform_(Object)} and {@link reverseTransform_(Object)} + * (or {@link #transform(Object)} and {@link #reverseTransform(Object)}) + * methods instead of building a {@link BidiTransformer}. + */ + public CachingTransformationWritablePropertyValueModel(WritablePropertyValueModel valueHolder) { + super(valueHolder); + } + + /** + * Construct a writable property value model with the specified nested + * writable property value model and bidi transformer. + */ + public CachingTransformationWritablePropertyValueModel(WritablePropertyValueModel valueHolder, BidiTransformer transformer) { + super(valueHolder, transformer); + } + + + // ********** behavior ********** + + /** + * We have listeners, transform the nested value and cache the result. + */ + @Override + protected void engageModel() { + super.engageModel(); + this.cachedValue = this.transform(this.valueHolder.getValue()); + } + + /** + * We have no more listeners, clear the cached value. + */ + @Override + protected void disengageModel() { + this.cachedValue = null; + super.disengageModel(); + } + + /** + * No need to transform the nested value, simply return the cached value, + * which is already transformed. + */ + @Override + public T2 getValue() { + return this.cachedValue; + } + + /** + * Transform the specified new value, caching it before returning it. + * A bit of a side-effect, but it seems reasonable. + */ + @Override + protected T2 transformNew(T1 value) { + this.cachedValue = super.transformNew(value); + return this.cachedValue; + } + + /** + * No need to transform the old value, simply return the cached value, + * which is already transformed. + */ + @Override + protected T2 transformOld(T1 value) { + return this.cachedValue; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ChangePropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ChangePropertyValueModelAdapter.java new file mode 100644 index 0000000000..56d8ddd85e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ChangePropertyValueModelAdapter.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.SimpleChangeListener; + +/** + * This abstract class provides the infrastructure needed to wrap + * a model, "lazily" listen to it, and convert + * its change notifications into property value model change + * notifications. + *

+ * Subclasses must override:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current model + *

+ * Subclasses might want to override the following methods + * to improve performance (by not recalculating the value, if possible):
    + *
  • {@link #modelChanged(ChangeEvent event)} + *
  • {@link #buildChangeListener()} + *
+ */ +public abstract class ChangePropertyValueModelAdapter + extends AbstractPropertyValueModelAdapter +{ + /** The wrapped model. */ + protected final Model model; + + /** A listener that allows us to synch with changes to the wrapped collection holder. */ + protected final ChangeListener changeListener; + + + // ********** constructor/initialization ********** + + /** + * Construct a property value model with the specified wrapped model. + */ + protected ChangePropertyValueModelAdapter(Model model) { + super(); + this.model = model; + this.changeListener = this.buildChangeListener(); + } + + protected ChangeListener buildChangeListener() { + return new SimpleChangeListener() { + @Override + protected void modelChanged(ChangeEvent event) { + ChangePropertyValueModelAdapter.this.modelChanged(event); + } + @Override + public String toString() { + return "change listener command"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the model. + */ + @Override + protected void engageModel_() { + this.model.addChangeListener(this.changeListener); + } + + /** + * Stop listening to the model. + */ + @Override + protected void disengageModel_() { + this.model.removeChangeListener(this.changeListener); + } + + + // ********** change support ********** + + /** + * The wrapped model has changed; + * propagate the change notification appropriately. + */ + protected void modelChanged(@SuppressWarnings("unused") ChangeEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionAspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionAspectAdapter.java new file mode 100644 index 0000000000..b9a0cf9362 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionAspectAdapter.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This extension of {@link AspectCollectionValueModelAdapter} provides + * basic collection change support. + * This converts a set of one or more collections into + * a single {@link #VALUES} collection. + *

+ * The typical subclass will override the following methods (see the descriptions + * in {@link AspectCollectionValueModelAdapter}):

    + *
  • {@link #getIterable()} + *
  • {@link #size_()} + *
  • {@link #iterator_()} + *
  • {@link #iterator()} + *
  • {@link #size()} + *
+ */ +public abstract class CollectionAspectAdapter + extends AspectCollectionValueModelAdapter +{ + /** + * The name of the subject's collections that we use for the value. + */ + protected final String[] collectionNames; + protected static final String[] EMPTY_COLLECTION_NAMES = new String[0]; + + /** A listener that listens to the subject's collection aspects. */ + protected final CollectionChangeListener collectionChangeListener; + + + // ********** constructors ********** + + /** + * Construct a collection aspect adapter for the specified subject + * and collection. + */ + protected CollectionAspectAdapter(String collectionName, S subject) { + this(new String[] {collectionName}, subject); + } + + /** + * Construct a collection aspect adapter for the specified subject + * and collections. + */ + protected CollectionAspectAdapter(String[] collectionNames, S subject) { + this(new StaticPropertyValueModel(subject), collectionNames); + } + + /** + * Construct a collection aspect adapter for the specified subject holder + * and collections. + */ + protected CollectionAspectAdapter(PropertyValueModel subjectHolder, String... collectionNames) { + super(subjectHolder); + this.collectionNames = collectionNames; + this.collectionChangeListener = this.buildCollectionChangeListener(); + } + + /** + * Construct a collection aspect adapter for the specified subject holder + * and collections. + */ + protected CollectionAspectAdapter(PropertyValueModel subjectHolder, Collection collectionNames) { + this(subjectHolder, collectionNames.toArray(new String[collectionNames.size()])); + } + + /** + * Construct a collection aspect adapter for an "unchanging" collection in + * the specified subject. This is useful for a collection aspect that does not + * change for a particular subject; but the subject will change, resulting in + * a new collection. + */ + protected CollectionAspectAdapter(PropertyValueModel subjectHolder) { + this(subjectHolder, EMPTY_COLLECTION_NAMES); + } + + + // ********** initialization ********** + + protected CollectionChangeListener buildCollectionChangeListener() { + // transform the subject's collection change events into VALUES collection change events + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + CollectionAspectAdapter.this.itemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + CollectionAspectAdapter.this.itemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + CollectionAspectAdapter.this.collectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + CollectionAspectAdapter.this.collectionChanged(event); + } + @Override + public String toString() { + return "collection change listener: " + Arrays.asList(CollectionAspectAdapter.this.collectionNames); //$NON-NLS-1$ + } + }; + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected void engageSubject_() { + for (String collectionName : this.collectionNames) { + ((Model) this.subject).addCollectionChangeListener(collectionName, this.collectionChangeListener); + } + } + + @Override + protected void disengageSubject_() { + for (String collectionName : this.collectionNames) { + ((Model) this.subject).removeCollectionChangeListener(collectionName, this.collectionChangeListener); + } + } + + + // ********** behavior ********** + + protected void itemsAdded(CollectionAddEvent event) { + this.fireItemsAdded(event.clone(this, VALUES)); + } + + protected void itemsRemoved(CollectionRemoveEvent event) { + this.fireItemsRemoved(event.clone(this, VALUES)); + } + + protected void collectionCleared(CollectionClearEvent event) { + this.fireCollectionCleared(event.clone(this, VALUES)); + } + + protected void collectionChanged(CollectionChangeEvent event) { + this.fireCollectionChanged(event.clone(this, VALUES)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionListValueModelAdapter.java new file mode 100644 index 0000000000..4c3d8b4cca --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionListValueModelAdapter.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * An adapter that allows us to make a {@link CollectionValueModel} behave like + * a read-only {@link ListValueModel}, sorta. + *

+ * To maintain a reasonably consistent appearance to client code, we + * keep an internal list somewhat in synch with the wrapped collection. + *

+ * NB: Since we only listen to the wrapped collection when we have + * listeners ourselves and we can only stay in synch with the wrapped + * collection while we are listening to it, results to various methods + * (e.g. {@link #size()}, {@link #get(int)}) will be unpredictable whenever + * we do not have any listeners. This should not be too painful since, + * most likely, clients will also be listeners. + */ +public class CollectionListValueModelAdapter + extends AbstractListValueModel + implements ListValueModel +{ + /** The wrapped collection value model. */ + protected final CollectionValueModel collectionHolder; + + /** A listener that forwards any events fired by the collection holder. */ + protected final CollectionChangeListener collectionChangeListener; + + /** + * Our internal list, which holds the same elements as + * the wrapped collection, but keeps them in order. + */ + // we declare this an ArrayList so we can use #clone() and #ensureCapacity(int) + protected final ArrayList list; + + + // ********** constructors ********** + + /** + * Wrap the specified collection value model. + */ + public CollectionListValueModelAdapter(CollectionValueModel collectionHolder) { + super(); + if (collectionHolder == null) { + throw new NullPointerException(); + } + this.collectionHolder = collectionHolder; + this.collectionChangeListener = this.buildCollectionChangeListener(); + this.list = new ArrayList(collectionHolder.size()); + // postpone building the list and listening to the underlying collection + // until we have listeners ourselves... + } + + + // ********** initialization ********** + + /** + * The wrapped collection has changed, forward an equivalent + * list change event to our listeners. + */ + protected CollectionChangeListener buildCollectionChangeListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + CollectionListValueModelAdapter.this.itemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + CollectionListValueModelAdapter.this.itemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + CollectionListValueModelAdapter.this.collectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + CollectionListValueModelAdapter.this.collectionChanged(event); + } + @Override + public String toString() { + return "collection change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.list); + } + + public E get(int index) { + return this.list.get(index); + } + + public int size() { + return this.list.size(); + } + + public Object[] toArray() { + return this.list.toArray(); + } + + + // ********** behavior ********** + + @Override + protected void engageModel() { + this.collectionHolder.addCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + // synch our list *after* we start listening to the collection holder, + // since its value might change when a listener is added + this.buildList(); + } + + @Override + protected void disengageModel() { + this.collectionHolder.removeCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + // clear out the list when we are not listening to the collection holder + this.list.clear(); + } + + protected void buildList() { + // if the new collection is empty, do nothing + int size = this.collectionHolder.size(); + if (size != 0) { + this.buildList(size); + } + } + + protected void buildList(int size) { + this.list.ensureCapacity(size); + for (E each : this.collectionHolder) { + this.list.add(each); + } + } + + protected void itemsAdded(CollectionAddEvent event) { + this.addItemsToList(this.indexToAddItems(), this.getItems(event), this.list, LIST_VALUES); + } + + protected int indexToAddItems() { + return this.list.size(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(CollectionAddEvent event) { + return (Iterable) event.getItems(); + } + + protected void itemsRemoved(CollectionRemoveEvent event) { + this.removeItemsFromList(this.getItems(event), this.list, LIST_VALUES); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(CollectionRemoveEvent event) { + return (Iterable) event.getItems(); + } + + protected void collectionCleared(@SuppressWarnings("unused") CollectionClearEvent event) { + this.clearList(this.list, LIST_VALUES); + } + + /** + * synchronize our internal list with the wrapped collection + * and fire the appropriate events + */ + protected void collectionChanged(@SuppressWarnings("unused") CollectionChangeEvent event) { + int size = this.collectionHolder.size(); + if (size == 0) { + if (this.list.isEmpty()) { + // no change + } else { + this.clearList(this.list, LIST_VALUES); + } + } else { + if (this.list.isEmpty()) { + this.buildList(size); + this.fireItemsAdded(LIST_VALUES, 0, this.list); + } else { + this.synchronizeList(this.buildSyncList(), this.list, LIST_VALUES); + } + } + } + + protected Iterable buildSyncList() { + return this.collectionHolder; + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.list); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionPropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionPropertyValueModelAdapter.java new file mode 100644 index 0000000000..24d18b27ab --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionPropertyValueModelAdapter.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * a collection value model, "lazily" listen to it, and convert + * its change notifications into property value model change + * notifications. + *

+ * Subclasses must override:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current collection value + *

+ * Subclasses might want to override the following methods + * to improve performance (by not recalculating the value, if possible):
    + *
  • {@link #itemsAdded(CollectionAddEvent event)} + *
  • {@link #itemsRemoved(CollectionRemoveEvent event)} + *
  • {@link #collectionCleared(CollectionClearEvent event)} + *
  • {@link #collectionChanged(CollectionChangeEvent event)} + *
+ */ +public abstract class CollectionPropertyValueModelAdapter + extends AbstractPropertyValueModelAdapter +{ + /** The wrapped collection value model. */ + protected final CollectionValueModel collectionModel; + + /** A listener that allows us to synch with changes to the wrapped collection holder. */ + protected final CollectionChangeListener collectionChangeListener; + + + // ********** constructor/initialization ********** + + /** + * Construct a property value model with the specified wrapped + * collection value model. + */ + protected CollectionPropertyValueModelAdapter(CollectionValueModel collectionModel) { + super(); + this.collectionModel = collectionModel; + this.collectionChangeListener = this.buildCollectionChangeListener(); + } + + protected CollectionChangeListener buildCollectionChangeListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + CollectionPropertyValueModelAdapter.this.itemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + CollectionPropertyValueModelAdapter.this.itemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + CollectionPropertyValueModelAdapter.this.collectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + CollectionPropertyValueModelAdapter.this.collectionChanged(event); + } + @Override + public String toString() { + return "collection change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the collection holder. + */ + @Override + protected void engageModel_() { + this.collectionModel.addCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + } + + /** + * Stop listening to the collection holder. + */ + @Override + protected void disengageModel_() { + this.collectionModel.removeCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + } + + + // ********** collection change support ********** + + /** + * Items were added to the wrapped collection holder; + * propagate the change notification appropriately. + */ + protected void itemsAdded(@SuppressWarnings("unused") CollectionAddEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * Items were removed from the wrapped collection holder; + * propagate the change notification appropriately. + */ + protected void itemsRemoved(@SuppressWarnings("unused") CollectionRemoveEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The wrapped collection holder was cleared; + * propagate the change notification appropriately. + */ + protected void collectionCleared(@SuppressWarnings("unused") CollectionClearEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The value of the wrapped collection holder has changed; + * propagate the change notification appropriately. + */ + protected void collectionChanged(@SuppressWarnings("unused") CollectionChangeEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionValueModelWrapper.java new file mode 100644 index 0000000000..a791dcb3d3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionValueModelWrapper.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * another collection value model, "lazily" listen to it, and propagate + * its change notifications. Subclasses must implement the appropriate + * {@link CollectionValueModel}. + */ +public abstract class CollectionValueModelWrapper + extends AbstractCollectionValueModel +{ + /** The wrapped collection value model. */ + protected final CollectionValueModel collectionHolder; + + /** A listener that allows us to synch with changes to the wrapped collection holder. */ + protected final CollectionChangeListener collectionChangeListener; + + + // ********** constructors ********** + + /** + * Construct a collection value model with the specified wrapped + * collection value model. + */ + protected CollectionValueModelWrapper(CollectionValueModel collectionHolder) { + super(); + this.collectionHolder = collectionHolder; + this.collectionChangeListener = this.buildCollectionChangeListener(); + } + + + // ********** initialization ********** + + protected CollectionChangeListener buildCollectionChangeListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + CollectionValueModelWrapper.this.itemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + CollectionValueModelWrapper.this.itemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + CollectionValueModelWrapper.this.collectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + CollectionValueModelWrapper.this.collectionChanged(event); + } + @Override + public String toString() { + return "collection change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** AbstractCollectionValueModel implementation ********** + + /** + * Start listening to the collection holder. + */ + @Override + protected void engageModel() { + this.collectionHolder.addCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + } + + /** + * Stop listening to the collection holder. + */ + @Override + protected void disengageModel() { + this.collectionHolder.removeCollectionChangeListener(CollectionValueModel.VALUES, this.collectionChangeListener); + } + + + // ********** helper methods ********** + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(CollectionAddEvent event) { + return (Iterable) event.getItems(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(CollectionRemoveEvent event) { + return (Iterable) event.getItems(); + } + + + // ********** collection change support ********** + + /** + * Items were added to the wrapped collection holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsAdded(CollectionAddEvent event); + + /** + * Items were removed from the wrapped collection holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsRemoved(CollectionRemoveEvent event); + + /** + * The wrapped collection holder was cleared; + * propagate the change notification appropriately. + */ + protected abstract void collectionCleared(CollectionClearEvent event); + + /** + * The value of the wrapped collection holder has changed; + * propagate the change notification appropriately. + */ + protected abstract void collectionChanged(CollectionChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeBooleanPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeBooleanPropertyValueModel.java new file mode 100644 index 0000000000..acedc5ebb4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeBooleanPropertyValueModel.java @@ -0,0 +1,338 @@ +/******************************************************************************* + * Copyright (c) 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A CompositeBooleanPropertyValueModel adapts a + * {@link CollectionValueModel} holding boolean {@link PropertyValueModel}s + * to a single boolean {@link PropertyValueModel}. The composite value is + * determined by the {@link CompositeBooleanPropertyValueModel.Adapter} provided + * at construction. Typically the composite will be either an AND or an OR of + * all the boolean values. + *

+ * If there are no boolean {@link PropertyValueModel}s, the composite + * will return its default value; which, by default, is null. + *

+ * NB: None of the wrapped boolean models can return a + * null value or this class will throw an exception. The assumption + * is that all the wrapped boolean models are configured with "default" values + * that determine the model's value (TRUE or FALSE) when null. + */ +public class CompositeBooleanPropertyValueModel + extends CompositePropertyValueModel +{ + /** + * Calculation of the model's value is delegated to this adapter. + */ + protected final Adapter adapter; + + /** + * The default setting for the composite boolean property value model; + * for when the underlying collection model is empty. + * The default [default value] is null. + */ + private final Boolean defaultValue; + + + // ********** AND factory methods ********** + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * The model's default value, when it contains no nested models, is + * null. + */ + public static CompositeBooleanPropertyValueModel and(PropertyValueModel... array) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, array); + } + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static CompositeBooleanPropertyValueModel and(Boolean defaultValue, PropertyValueModel... array) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, defaultValue, array); + } + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * The model's default value, when it contains no nested models, is + * null. + */ + public static > CompositeBooleanPropertyValueModel and(Collection collection) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, collection); + } + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static > CompositeBooleanPropertyValueModel and(Boolean defaultValue, Collection collection) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, defaultValue, collection); + } + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * The model's default value, when it contains no nested models, is + * null. + */ + public static CompositeBooleanPropertyValueModel and(CollectionValueModel> collectionModel) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, collectionModel); + } + + /** + * Construct a boolean property value model that is a composite AND of the + * specified boolean property value models; i.e. the model's value is true + * if all the contained models are true, otherwise its value is false. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static CompositeBooleanPropertyValueModel and(Boolean defaultValue, CollectionValueModel> collectionModel) { + return new CompositeBooleanPropertyValueModel(AND_ADAPTER, defaultValue, collectionModel); + } + + + // ********** OR factory methods ********** + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * The model's default value, when it contains no nested models, is + * null. + */ + public static CompositeBooleanPropertyValueModel or(PropertyValueModel... array) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, array); + } + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static CompositeBooleanPropertyValueModel or(Boolean defaultValue, PropertyValueModel... array) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, defaultValue, array); + } + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * The model's default value, when it contains no nested models, is + * null. + */ + public static > CompositeBooleanPropertyValueModel or(Collection collection) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, collection); + } + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static > CompositeBooleanPropertyValueModel or(Boolean defaultValue, Collection collection) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, defaultValue, collection); + } + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * The model's default value, when it contains no nested models, is + * null. + */ + public static CompositeBooleanPropertyValueModel or(CollectionValueModel> collectionModel) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, collectionModel); + } + + /** + * Construct a boolean property value model that is a composite OR of the + * specified boolean property value models; i.e. the model's value is false + * if all the contained models are false, otherwise its value is true. + * When the model contains no nested models, its value will be the + * specified default. + */ + public static CompositeBooleanPropertyValueModel or(Boolean defaultValue, CollectionValueModel> collectionModel) { + return new CompositeBooleanPropertyValueModel(OR_ADAPTER, defaultValue, collectionModel); + } + + + // ********** constructors ********** + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * The model's default value, when it contains no nested models, is + * null. + */ + public CompositeBooleanPropertyValueModel(Adapter adapter, PropertyValueModel... array) { + this(adapter, null, array); + } + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * When the model contains no nested models, its value will be the + * specified default. + */ + public CompositeBooleanPropertyValueModel(Adapter adapter, Boolean defaultValue, PropertyValueModel... array) { + super(array); + this.adapter = adapter; + this.defaultValue = defaultValue; + } + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * The model's default value, when it contains no nested models, is + * null. + */ + public > CompositeBooleanPropertyValueModel(Adapter adapter, Collection collection) { + this(adapter, null, collection); + } + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * When the model contains no nested models, its value will be the + * specified default. + */ + public > CompositeBooleanPropertyValueModel(Adapter adapter, Boolean defaultValue, Collection collection) { + super(collection); + this.adapter = adapter; + this.defaultValue = defaultValue; + } + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * The model's default value, when it contains no nested models, is + * null. + */ + public CompositeBooleanPropertyValueModel(Adapter adapter, CollectionValueModel> collectionModel) { + this(adapter, null, collectionModel); + } + + /** + * Construct a boolean property value model that is a composite of the specified + * boolean property value models, as defined by the specified adapter. + * When the model contains no nested models, its value will be the + * specified default. + */ + public CompositeBooleanPropertyValueModel(Adapter adapter, Boolean defaultValue, CollectionValueModel> collectionModel) { + super(collectionModel); + this.adapter = adapter; + this.defaultValue = defaultValue; + } + + + // ********** implementation ********** + + /** + * Return true if all the contained booleans are true; otherwise return + * false. + */ + @Override + protected Boolean buildValue() { + return (this.collectionModel.size() == 0) ? this.defaultValue : this.buildValue_(); + } + + protected Boolean buildValue_() { + return this.adapter.buildValue(this.getBooleans()); + } + + protected Iterable getBooleans() { + return new TransformationIterable, Boolean>(this.getCollectionModel()) { + @Override + protected Boolean transform(PropertyValueModel booleanModel) { + return booleanModel.getValue(); + } + }; + } + + @Override + @SuppressWarnings("unchecked") + protected CollectionValueModel> getCollectionModel() { + return (CollectionValueModel>) super.getCollectionModel(); + } + + + // ********** adapter ********** + + /** + * This adapter allows the {@link CompositeBooleanPropertyValueModel} to be + * pluggable. + */ + public interface Adapter { + /** + * Return the composite boolean value of the specified collection + * of boolean models. + */ + Boolean buildValue(Iterable booleans); + } + + /** + * Return true if all the booleans are true; otherwise return false. + */ + public static final Adapter AND_ADAPTER = new Adapter() { + public Boolean buildValue(Iterable booleans) { + for (Boolean b : booleans) { + if ( ! b.booleanValue()) { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + } + @Override + public String toString() { + return "AND_ADAPTER"; //$NON-NLS-1$ + } + }; + + /** + * Return false if all the booleans are false; otherwise return true. + */ + public static final Adapter OR_ADAPTER = new Adapter() { + public Boolean buildValue(Iterable booleans) { + for (Boolean b : booleans) { + if (b.booleanValue()) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + @Override + public String toString() { + return "OR_ADAPTER"; //$NON-NLS-1$ + } + }; + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeCollectionValueModel.java new file mode 100644 index 0000000000..72f16a3344 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeCollectionValueModel.java @@ -0,0 +1,448 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.NullList; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterators.CompositeIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * A CompositeCollectionValueModel wraps another + * {@link CollectionValueModel} and uses a {@link Transformer} + * to convert each item in the wrapped collection to yet another + * {@link CollectionValueModel}. This composite collection contains + * the combined items from all these component collections. + *

+ * NB: The wrapped collection must be an "identity set" that does not + * contain the same item twice or this class will throw an exception. + *

+ * Terminology:

    + *
  • sources - the items in the wrapped collection value model; these + * are converted into component CVMs by the transformer + *
  • component CVMs - the component collection value models that are combined + * by this composite collection value model + *
  • items - the items held by the component CVMs + *
+ */ +public class CompositeCollectionValueModel + extends CollectionValueModelWrapper + implements CollectionValueModel +{ + /** + * This is the (optional) user-supplied object that transforms + * the items in the wrapped collection to collection value models. + */ + private final Transformer> transformer; + + /** + * Cache of the component collection value models that + * were generated by the transformer; keyed by the item + * in the wrapped collection that was passed to the transformer. + */ + private final IdentityHashMap> componentCVMs = + new IdentityHashMap>(); + + /** + * Cache of the collections corresponding to the component + * collection value models above; keyed by the component + * collection value models. + * Use {@link ArrayList}s so we can use {@link ArrayList}-specific methods + * (e.g. {@link ArrayList#clone()} and {@link ArrayList#ensureCapacity(int)}). + */ + private final IdentityHashMap, ArrayList> collections = + new IdentityHashMap, ArrayList>(); + + /** Listener that listens to all the component collection value models. */ + private final CollectionChangeListener componentCVMListener; + + /** Cache the size of the composite collection. */ + private int size; + + + // ********** constructors ********** + + /** + * Construct a collection value model with the specified wrapped + * collection value model. Use this constructor if
    + *
  • the wrapped collection value model already contains other + * collection value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeCollectionValueModel(CollectionValueModel collectionHolder) { + this(collectionHolder, Transformer.Null.>instance()); + } + + /** + * Construct a collection value model with the specified wrapped + * collection value model and transformer. + */ + public CompositeCollectionValueModel(CollectionValueModel collectionHolder, Transformer> transformer) { + super(collectionHolder); + this.transformer = transformer; + this.componentCVMListener = this.buildComponentListener(); + this.size = 0; + } + + /** + * Construct a collection value model with the specified wrapped + * list value model. Use this constructor if
    + *
  • the wrapped collection value model already contains other + * collection value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeCollectionValueModel(ListValueModel listHolder) { + this(new ListCollectionValueModelAdapter(listHolder)); + } + + /** + * Construct a collection value model with the specified wrapped + * list value model and transformer. + */ + public CompositeCollectionValueModel(ListValueModel listHolder, Transformer> transformer) { + this(new ListCollectionValueModelAdapter(listHolder), transformer); + } + + /** + * Construct a collection value model with the specified, unchanging, wrapped + * collection. Use this constructor if
    + *
  • the wrapped collection value model already contains other + * collection value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeCollectionValueModel(Collection collection) { + this(new StaticCollectionValueModel(collection)); + } + + /** + * Construct a collection value model with the specified, unchanging, wrapped + * collection and transformer. + */ + public CompositeCollectionValueModel(Collection collection, Transformer> transformer) { + this(new StaticCollectionValueModel(collection), transformer); + } + + /** + * Construct a collection value model with the specified, unchanging, wrapped + * collection. Use this constructor if
    + *
  • the wrapped collection value model already contains other + * collection value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeCollectionValueModel(E1... collection) { + this(new StaticCollectionValueModel(collection)); + } + + /** + * Construct a collection value model with the specified, unchanging, wrapped + * collection and transformer. + */ + public CompositeCollectionValueModel(E1[] collection, Transformer> transformer) { + this(new StaticCollectionValueModel(collection), transformer); + } + + + // ********** initialization ********** + + protected CollectionChangeListener buildComponentListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + CompositeCollectionValueModel.this.componentItemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + CompositeCollectionValueModel.this.componentItemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + CompositeCollectionValueModel.this.componentCollectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + CompositeCollectionValueModel.this.componentCollectionChanged(event); + } + @Override + public String toString() { + return "component listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + return new CompositeIterator(this.buildCollectionsIterators()); + } + + protected Iterator> buildCollectionsIterators() { + return new TransformationIterator, Iterator>(this.collections.values().iterator()) { + @Override + protected Iterator transform(ArrayList next) { + return next.iterator(); + } + }; + } + + public int size() { + return this.size; + } + + + // ********** CollectionValueModelWrapper overrides/implementation ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch our cache *after* we start listening to the wrapped collection, + // since its value might change when a listener is added + this.addAllComponentSources(); + } + + /** + * Transform all the sources to collection value models + * and add their items to our cache, with no event notification. + */ + protected void addAllComponentSources() { + for (E1 source : this.collectionHolder) { + this.addComponentSource(source, NullList.instance()); + } + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // stop listening to the components... + for (CollectionValueModel componentCVM : this.componentCVMs.values()) { + componentCVM.removeCollectionChangeListener(VALUES, this.componentCVMListener); + } + // ...and clear the cache + this.componentCVMs.clear(); + this.collections.clear(); + this.size = 0; + } + + /** + * Some component sources were added; + * add their corresponding items to our cache. + */ + @Override + protected void itemsAdded(CollectionAddEvent event) { + ArrayList addedItems = new ArrayList(); + for (E1 item : this.getItems(event)) { + this.addComponentSource(item, addedItems); + } + this.fireItemsAdded(VALUES, addedItems); + } + + /** + * Transform the specified source to a collection value model + * and add its items to our cache and the "collecting parameter". + */ + protected void addComponentSource(E1 source, List addedItems) { + CollectionValueModel componentCVM = this.transform(source); + if (this.componentCVMs.put(source, componentCVM) != null) { + throw new IllegalStateException("duplicate component: " + source); //$NON-NLS-1$ + } + componentCVM.addCollectionChangeListener(VALUES, this.componentCVMListener); + ArrayList componentCollection = new ArrayList(componentCVM.size()); + if (this.collections.put(componentCVM, componentCollection) != null) { + throw new IllegalStateException("duplicate collection: " + source); //$NON-NLS-1$ + } + this.addComponentItems(componentCVM, componentCollection); + addedItems.addAll(componentCollection); + } + + /** + * Add the items in the specified component CVM to the specified component + * collection. + */ + protected void addComponentItems(CollectionValueModel componentCVM, ArrayList componentCollection) { + int itemsSize = componentCVM.size(); + this.size += itemsSize; + componentCollection.ensureCapacity(componentCollection.size() + itemsSize); + CollectionTools.addAll(componentCollection, componentCVM); + } + + /** + * Some component sources were removed; + * remove their corresponding items from our cache. + */ + @Override + protected void itemsRemoved(CollectionRemoveEvent event) { + ArrayList removedItems = new ArrayList(); + for (E1 item : this.getItems(event)) { + this.removeComponentSource(item, removedItems); + } + this.fireItemsRemoved(VALUES, removedItems); + } + + /** + * Remove the items corresponding to the specified source + * from our cache. + */ + protected void removeComponentSource(E1 source, List removedItems) { + CollectionValueModel componentCVM = this.componentCVMs.remove(source); + if (componentCVM == null) { + throw new IllegalStateException("missing component: " + source); //$NON-NLS-1$ + } + componentCVM.removeCollectionChangeListener(VALUES, this.componentCVMListener); + ArrayList componentCollection = this.collections.remove(componentCVM); + if (componentCollection == null) { + throw new IllegalStateException("missing collection: " + source); //$NON-NLS-1$ + } + removedItems.addAll(componentCollection); + this.removeComponentItems(componentCollection); + } + + /** + * Update our size and collection cache. + */ + protected void removeComponentItems(ArrayList componentCollection) { + this.size -= componentCollection.size(); + componentCollection.clear(); + } + + /** + * The component sources cleared; + * clear our cache. + */ + @Override + protected void collectionCleared(CollectionClearEvent event) { + this.removeAllComponentSources(); + this.fireCollectionCleared(VALUES); + } + + protected void removeAllComponentSources() { + // copy the keys so we don't eat our own tail + ArrayList copy = new ArrayList(this.componentCVMs.keySet()); + for (E1 source : copy) { + this.removeComponentSource(source, NullList.instance()); + } + } + + /** + * The component sources changed; + * rebuild our cache. + */ + @Override + protected void collectionChanged(CollectionChangeEvent event) { + this.removeAllComponentSources(); + this.addAllComponentSources(); + this.fireCollectionChanged(VALUES, CollectionTools.collection(this.iterator())); + } + + + // ********** internal methods ********** + + /** + * Transform the specified object into a collection value model. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Transformer}. + */ + protected CollectionValueModel transform(E1 value) { + return this.transformer.transform(value); + } + + /** + * One of the component collections had items added; + * synchronize our caches. + */ + protected void componentItemsAdded(CollectionAddEvent event) { + int itemsSize = event.getItemsSize(); + this.size += itemsSize; + + ArrayList componentCollection = this.collections.get(this.componentCVM(event)); + componentCollection.ensureCapacity(componentCollection.size() + itemsSize); + + this.addItemsToCollection(this.getComponentItems(event), componentCollection, VALUES); + } + + /** + * One of the component collections had items removed; + * synchronize our caches. + */ + protected void componentItemsRemoved(CollectionRemoveEvent event) { + this.size -= event.getItemsSize(); + ArrayList componentCollection = this.collections.get(this.componentCVM(event)); + this.removeItemsFromCollection(this.getComponentItems(event), componentCollection, VALUES); + } + + /** + * One of the component collections was cleared; + * synchronize our caches by clearing out the appropriate + * collection. + */ + protected void componentCollectionCleared(CollectionClearEvent event) { + ArrayList componentCollection = this.collections.get(this.componentCVM(event)); + ArrayList removedItems = new ArrayList(componentCollection); + this.removeComponentItems(componentCollection); + this.fireItemsRemoved(VALUES, removedItems); + } + + /** + * One of the component collections changed; + * synchronize our caches by clearing out the appropriate + * collection and then rebuilding it. + */ + protected void componentCollectionChanged(CollectionChangeEvent event) { + CollectionValueModel componentCVM = this.componentCVM(event); + ArrayList componentCollection = this.collections.get(componentCVM); + this.removeComponentItems(componentCollection); + this.addComponentItems(componentCVM, componentCollection); + this.fireCollectionChanged(VALUES, CollectionTools.collection(this.iterator())); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getComponentItems(CollectionAddEvent event) { + return (Iterable) event.getItems(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getComponentItems(CollectionRemoveEvent event) { + return (Iterable) event.getItems(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected CollectionValueModel componentCVM(CollectionEvent event) { + return (CollectionValueModel) event.getSource(); + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeListValueModel.java new file mode 100644 index 0000000000..3b017b08c9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeListValueModel.java @@ -0,0 +1,683 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterables.SingleElementIterable; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyCompositeListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationListIterator; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * A CompositeListValueModel wraps another + * {@link ListValueModel} and uses a {@link Transformer} + * to convert each item in the wrapped list to yet another + * {@link ListValueModel}. This composite list contains + * the combined items from all these component lists. + *

+ * Terminology:

    + *
  • sources - the items in the wrapped list value model; these + * are converted into component LVMs by the transformer + *
  • component LVMs - the component list value models that are combined + * by this composite list value model + *
  • items - the items held by the component LVMs + *
+ */ +public class CompositeListValueModel + extends ListValueModelWrapper + implements ListValueModel +{ + /** + * This is the (optional) user-supplied object that transforms + * the items in the wrapped list to list value models. + */ + private final Transformer> transformer; + + /** + * Cache of the sources, component LVMs, lists. + */ + private final ArrayList infoList = new ArrayList(); + protected class Info { + // the object passed to the transformer + final E1 source; + // the list value model generated by the transformer + final ListValueModel componentLVM; + // cache of the items held by the component LVM + final ArrayList items; + // the component LVM's beginning index within the composite LVM + int begin; + protected Info(E1 source, ListValueModel componentLVM, ArrayList items, int begin) { + super(); + this.source = source; + this.componentLVM = componentLVM; + this.items = items; + this.begin = begin; + } + } + + /** Listener that listens to all the component list value models. */ + private final ListChangeListener componentLVMListener; + + /** Cache the size of the composite list. */ + private int size; + + + // ********** constructors ********** + + /** + * Construct a list value model with the specified wrapped + * list value model. Use this constructor if
    + *
  • the wrapped list value model already contains other + * list value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeListValueModel(ListValueModel listHolder) { + this(listHolder, Transformer.Null.>instance()); + } + + /** + * Construct a list value model with the specified wrapped + * list value model and transformer. + */ + public CompositeListValueModel(ListValueModel listHolder, Transformer> transformer) { + super(listHolder); + this.transformer = transformer; + this.componentLVMListener = this.buildComponentLVMListener(); + this.size = 0; + } + + /** + * Construct a list value model with the specified, unchanging, wrapped + * list. Use this constructor if
    + *
  • the wrapped list value model already contains other + * list value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeListValueModel(List list) { + this(new StaticListValueModel(list)); + } + + /** + * Construct a list value model with the specified, unchanging, wrapped + * list and transformer. + */ + public CompositeListValueModel(List list, Transformer> transformer) { + this(new StaticListValueModel(list), transformer); + } + + /** + * Construct a list value model with the specified, unchanging, wrapped + * list. Use this constructor if
    + *
  • the wrapped list value model already contains other + * list value models, or + *
  • you want to override {@link #transform(E1)} + * instead of building a {@link Transformer} + *
+ */ + public CompositeListValueModel(E1... list) { + this(new StaticListValueModel(list)); + } + + /** + * Construct a list value model with the specified, unchanging, wrapped + * list and transformer. + */ + public CompositeListValueModel(E1[] list, Transformer> transformer) { + this(new StaticListValueModel(list), transformer); + } + + + // ********** initialization ********** + + protected ListChangeListener buildComponentLVMListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + CompositeListValueModel.this.componentItemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + CompositeListValueModel.this.componentItemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + CompositeListValueModel.this.componentItemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + CompositeListValueModel.this.componentItemsMoved(event); + } + public void listCleared(ListClearEvent event) { + CompositeListValueModel.this.componentListCleared(event); + } + public void listChanged(ListChangeEvent event) { + CompositeListValueModel.this.componentListChanged(event); + } + @Override + public String toString() { + return "component LVM listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ListValueModel implementation ********** + + public E2 get(int index) { + if ((index < 0) || (index >= this.size)) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size); //$NON-NLS-1$ //$NON-NLS-2$ + } + // move backwards through the info list + for (int i = this.infoList.size(); i-- > 0; ) { + Info info = this.infoList.get(i); + if (index >= info.begin) { + return info.items.get(index - info.begin); + } + } + throw new IllegalStateException(); // something is wack + } + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyCompositeListIterator(this.buildListsIterators()); + } + + protected ListIterator> buildListsIterators() { + return new TransformationListIterator>(this.infoList.listIterator()) { + @Override + protected ListIterator transform(Info info) { + return info.items.listIterator(); + } + }; + } + + public int size() { + return this.size; + } + + public Object[] toArray() { + return ArrayTools.array(this.listIterator(), this.size); + } + + + // ********** ListValueModelWrapper overrides/implementation ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch our cache *after* we start listening to the wrapped list, + // since its value might change when a listener is added + this.addComponentSources(0, this.listHolder, this.listHolder.size(), false); // false = do not fire event + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // stop listening to the component LVMs... + for (Info info : this.infoList) { + info.componentLVM.removeListChangeListener(LIST_VALUES, this.componentLVMListener); + } + // ...and clear the cache + this.infoList.clear(); + this.size = 0; + } + + /** + * Some component sources were added; update our cache. + */ + @Override + protected void itemsAdded(ListAddEvent event) { + this.addComponentSources(event.getIndex(), this.getItems(event), event.getItemsSize(), true); // true = fire event + } + + /** + * Add infos corresponding to the specified sources to our cache. + * Fire the appropriate event if requested. + */ + protected void addComponentSources(int addedSourcesIndex, Iterable addedSources, int addedSourcesSize, boolean fireEvent) { + ArrayList newInfoList = new ArrayList(addedSourcesSize); + // the 'items' are either tacked on to the end or + // at the 'begin' index of the first 'info' that is being pushed back + int newItemsIndex = (addedSourcesIndex == this.infoList.size()) ? this.size : this.infoList.get(addedSourcesIndex).begin; + + int begin = newItemsIndex; + for (E1 source : addedSources) { + ListValueModel componentLVM = this.transform(source); + componentLVM.addListChangeListener(LIST_VALUES, this.componentLVMListener); + ArrayList items = new ArrayList(componentLVM.size()); + CollectionTools.addAll(items, componentLVM.listIterator()); + newInfoList.add(new Info(source, componentLVM, items, begin)); + begin += items.size(); + } + this.infoList.addAll(addedSourcesIndex, newInfoList); + int newItemsSize = begin - newItemsIndex; + this.size += newItemsSize; + + // bump the 'begin' index for all the infos that were pushed back by the insert + int movedInfosIndex = addedSourcesIndex + addedSourcesSize; + for (int i = movedInfosIndex; i < this.infoList.size(); i++) { + this.infoList.get(i).begin += newItemsSize; + } + + if (fireEvent) { + ArrayList newItems = new ArrayList(newItemsSize); + for (int i = addedSourcesIndex; i < movedInfosIndex; i++) { + newItems.addAll(this.infoList.get(i).items); + } + this.fireItemsAdded(LIST_VALUES, newItemsIndex, newItems); + } + } + + /** + * Some component sources were removed; update our cache. + */ + @Override + protected void itemsRemoved(ListRemoveEvent event) { + this.removeComponentSources(event.getIndex(), event.getItemsSize(), true); // true = fire event + } + + /** + * Remove the infos corresponding to the specified sources from our cache. + */ + protected void removeComponentSources(int removedSourcesIndex, int removedSourcesSize, boolean fireEvent) { + int removedItemsIndex = this.infoList.get(removedSourcesIndex).begin; + int movedSourcesIndex = removedSourcesIndex + removedSourcesSize; + int movedItemsIndex = (movedSourcesIndex == this.infoList.size()) ? this.size : this.infoList.get(movedSourcesIndex).begin; + int removedItemsSize = movedItemsIndex - removedItemsIndex; + this.size -= removedItemsSize; + + List subList = this.infoList.subList(removedSourcesIndex, removedSourcesIndex + removedSourcesSize); + ArrayList removedInfoList = new ArrayList(subList); // make a copy + subList.clear(); + + // decrement the 'begin' index for all the infos that were moved forward by the deletes + for (int i = removedSourcesIndex; i < this.infoList.size(); i++) { + this.infoList.get(i).begin -= removedItemsSize; + } + + for (Info removedInfo : removedInfoList) { + removedInfo.componentLVM.removeListChangeListener(LIST_VALUES, this.componentLVMListener); + } + + if (fireEvent) { + ArrayList removedItems = new ArrayList(removedItemsSize); + for (Info removedInfo : removedInfoList) { + removedItems.addAll(removedInfo.items); + } + this.fireItemsRemoved(LIST_VALUES, removedItemsIndex, removedItems); + } + } + + /** + * Some component sources were replaced; update our cache. + */ + @Override + protected void itemsReplaced(ListReplaceEvent event) { + this.replaceComponentSources(event.getIndex(), this.getNewItems(event), event.getItemsSize(), true); // true = fire event + } + + /** + * Replaced component sources will not (typically) map to a set of replaced + * items, so we remove and add the corresponding lists of items, resulting in + * two events. + */ + protected void replaceComponentSources(int replacedSourcesIndex, Iterable newSources, int replacedSourcesSize, boolean fireEvent) { + this.removeComponentSources(replacedSourcesIndex, replacedSourcesSize, fireEvent); + this.addComponentSources(replacedSourcesIndex, newSources, replacedSourcesSize, fireEvent); + } + + /** + * Some component sources were moved; update our cache. + */ + @Override + protected void itemsMoved(ListMoveEvent event) { + this.moveComponentSources(event.getTargetIndex(), event.getSourceIndex(), event.getLength(), true); // true = fire event + } + + protected void moveComponentSources(int targetSourcesIndex, int sourceSourcesIndex, int movedSourcesLength, boolean fireEvent) { + int sourceItemsIndex = this.infoList.get(sourceSourcesIndex).begin; + + int nextSourceSourceIndex = sourceSourcesIndex + movedSourcesLength; + int nextSourceItemIndex = (nextSourceSourceIndex == this.infoList.size()) ? this.size : this.infoList.get(nextSourceSourceIndex).begin; + int moveItemsLength = nextSourceItemIndex - sourceItemsIndex; + + int targetItemsIndex = -1; + if (sourceSourcesIndex > targetSourcesIndex) { + // move from high to low index + targetItemsIndex = this.infoList.get(targetSourcesIndex).begin; + } else { + // move from low to high index (higher items move down during move) + int nextTargetSourceIndex = targetSourcesIndex + movedSourcesLength; + targetItemsIndex = (nextTargetSourceIndex == this.infoList.size()) ? this.size : this.infoList.get(nextTargetSourceIndex).begin; + targetItemsIndex = targetItemsIndex - moveItemsLength; + } + + CollectionTools.move(this.infoList, targetSourcesIndex, sourceSourcesIndex, movedSourcesLength); + + // update the 'begin' indexes of all the affected 'infos' + int min = Math.min(targetSourcesIndex, sourceSourcesIndex); + int max = Math.max(targetSourcesIndex, sourceSourcesIndex) + movedSourcesLength; + int begin = Math.min(targetItemsIndex, sourceItemsIndex); + for (int i = min; i < max; i++) { + Info info = this.infoList.get(i); + info.begin = begin; + begin += info.componentLVM.size(); + } + + if (fireEvent) { + this.fireItemsMoved(LIST_VALUES, targetItemsIndex, sourceItemsIndex, moveItemsLength); + } + } + + /** + * The component sources were cleared; clear our cache. + */ + @Override + protected void listCleared(ListClearEvent event) { + this.clearComponentSources(); + } + + protected void clearComponentSources() { + this.removeComponentSources(0, this.infoList.size(), false); // false = do not fire event + this.fireListCleared(LIST_VALUES); + } + + /** + * The component sources changed; rebuild our cache. + */ + @Override + protected void listChanged(ListChangeEvent event) { + int newSize = this.listHolder.size(); + if (newSize == 0) { + this.clearComponentSources(); + return; + } + + int oldSize = this.infoList.size(); + if (oldSize == 0) { + this.addComponentSources(0, this.listHolder, newSize, true); // true = fire event + return; + } + + int min = Math.min(newSize, oldSize); + // handle replaced sources individually so we don't fire events for unchanged sources + for (int i = 0; i < min; i++) { + E1 newSource = this.listHolder.get(i); + E1 oldSource = this.infoList.get(i).source; + if (this.valuesAreDifferent(newSource, oldSource)) { + this.replaceComponentSources(i, new SingleElementIterable(newSource), 1, true); // true = fire event + } + } + + if (newSize == oldSize) { + return; + } + + if (newSize < oldSize) { + this.removeComponentSources(min, oldSize - newSize, true); // true = fire event + return; + } + + // newSize > oldSize + this.addComponentSources(min, this.buildSubListHolder(min), newSize - oldSize, true); // true = fire event + } + + protected Iterable buildSubListHolder(int fromIndex) { + int listHolderSize = this.listHolder.size(); + return CollectionTools.list(this.listHolder, listHolderSize).subList(fromIndex, listHolderSize); + } + + protected Iterable buildSubListHolder(int fromIndex, int toIndex) { + int listHolderSize = this.listHolder.size(); + return ((fromIndex == 0) && (toIndex == listHolderSize)) ? + this.listHolder : + CollectionTools.list(this.listHolder, listHolderSize).subList(fromIndex, toIndex); + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + + + // ********** internal methods ********** + + /** + * Transform the specified object into a list value model. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Transformer}. + */ + protected ListValueModel transform(E1 value) { + return this.transformer.transform(value); + } + + /** + * Return the index of the specified component LVM. + */ + protected int indexOf(ListValueModel componentLVM) { + for (int i = 0; i < this.infoList.size(); i++) { + if (this.infoList.get(i).componentLVM == componentLVM) { + return i; + } + } + throw new IllegalArgumentException("invalid component LVM: " + componentLVM); //$NON-NLS-1$ + } + + /** + * Return the index of the specified event's component LVM. + */ + protected int indexFor(ListEvent event) { + return this.indexOf(this.getComponentLVM(event)); + } + + /** + * Items were added to one of the component lists; + * synchronize our cache. + */ + protected void componentItemsAdded(ListAddEvent event) { + int componentLVMIndex = this.indexFor(event); + this.addComponentItems(componentLVMIndex, this.infoList.get(componentLVMIndex), event.getIndex(), this.getComponentItems(event), event.getItemsSize()); + } + + protected void addComponentItems(int componentLVMIndex, Info info, int addedItemsIndex, Iterable addedItems, int addedItemsSize) { + // update the affected 'begin' indices + for (int i = componentLVMIndex + 1; i < this.infoList.size(); i++) { + this.infoList.get(i).begin += addedItemsSize; + } + this.size += addedItemsSize; + + // synchronize the cached list + CollectionTools.addAll(info.items, addedItemsIndex, addedItems, addedItemsSize); + + // translate the event + this.fireItemsAdded(LIST_VALUES, info.begin + addedItemsIndex, info.items.subList(addedItemsIndex, addedItemsIndex + addedItemsSize)); + } + + /** + * Items were removed from one of the component lists; + * synchronize our cache. + */ + protected void componentItemsRemoved(ListRemoveEvent event) { + // update the affected 'begin' indices + int componentLVMIndex = this.indexFor(event); + int removedItemsSize = event.getItemsSize(); + for (int i = componentLVMIndex + 1; i < this.infoList.size(); i++) { + this.infoList.get(i).begin -= removedItemsSize; + } + this.size -= removedItemsSize; + + // synchronize the cached list + Info info = this.infoList.get(componentLVMIndex); + int itemIndex = event.getIndex(); + info.items.subList(itemIndex, itemIndex + event.getItemsSize()).clear(); + + // translate the event + this.fireItemsRemoved(event.clone(this, LIST_VALUES, info.begin)); + } + + /** + * Items were replaced in one of the component lists; + * synchronize our cache. + */ + protected void componentItemsReplaced(ListReplaceEvent event) { + // no changes to the 'begin' indices or size + + // synchronize the cached list + int componentLVMIndex = this.indexFor(event); + Info info = this.infoList.get(componentLVMIndex); + int i = event.getIndex(); + for (E2 item : this.getComponentItems(event)) { + info.items.set(i++, item); + } + + // translate the event + this.fireItemsReplaced(event.clone(this, LIST_VALUES, info.begin)); + } + + /** + * Items were moved in one of the component lists; + * synchronize our cache. + */ + protected void componentItemsMoved(ListMoveEvent event) { + // no changes to the 'begin' indices or size + + // synchronize the cached list + int componentLVMIndex = this.indexFor(event); + Info info = this.infoList.get(componentLVMIndex); + CollectionTools.move(info.items, event.getTargetIndex(), event.getSourceIndex(), event.getLength()); + + // translate the event + this.fireItemsMoved(event.clone(this, LIST_VALUES, info.begin)); + } + + /** + * One of the component lists was cleared; + * synchronize our cache. + */ + protected void componentListCleared(ListClearEvent event) { + int componentLVMIndex = this.indexFor(event); + this.clearComponentList(componentLVMIndex, this.infoList.get(componentLVMIndex)); + } + + protected void clearComponentList(int componentLVMIndex, Info info) { + // update the affected 'begin' indices + int removedItemsSize = info.items.size(); + if (removedItemsSize == 0) { + return; + } + + for (int i = componentLVMIndex + 1; i < this.infoList.size(); i++) { + this.infoList.get(i).begin -= removedItemsSize; + } + this.size -= removedItemsSize; + + // synchronize the cached list + ArrayList items = new ArrayList(info.items); // make a copy + info.items.clear(); + + // translate the event + this.fireItemsRemoved(LIST_VALUES, info.begin, items); + } + + /** + * One of the component lists changed; + * synchronize our cache by synchronizing the appropriate + * list and firing the appropriate events. + */ + protected void componentListChanged(ListChangeEvent event) { + int componentLVMIndex = this.indexFor(event); + Info info = this.infoList.get(componentLVMIndex); + + int newItemsSize = info.componentLVM.size(); + if (newItemsSize == 0) { + this.clearComponentList(componentLVMIndex, info); + return; + } + + int oldItemsSize = info.items.size(); + if (oldItemsSize == 0) { + this.addComponentItems(componentLVMIndex, info, 0, info.componentLVM, newItemsSize); + return; + } + + int min = Math.min(newItemsSize, oldItemsSize); + // handle replaced items individually so we don't fire events for unchanged items + for (int i = 0; i < min; i++) { + E2 newItem = info.componentLVM.get(i); + E2 oldItem = info.items.set(i, newItem); + this.fireItemReplaced(LIST_VALUES, info.begin + i, newItem, oldItem); + } + + int delta = newItemsSize - oldItemsSize; + if (delta == 0) { // newItemsSize == oldItemsSize + return; + } + + for (int i = componentLVMIndex + 1; i < this.infoList.size(); i++) { + this.infoList.get(i).begin += delta; + } + this.size += delta; + + if (delta < 0) { // newItemsSize < oldItemsSize + List subList = info.items.subList(newItemsSize, oldItemsSize); + ArrayList removedItems = new ArrayList(subList); // make a copy + subList.clear(); + this.fireItemsRemoved(LIST_VALUES, info.begin + newItemsSize, removedItems); + return; + } + + // newItemsSize > oldItemsSize + ArrayList addedItems = new ArrayList(delta); + for (int i = oldItemsSize; i < newItemsSize; i++) { + addedItems.add(info.componentLVM.get(i)); + } + info.items.addAll(addedItems); + this.fireItemsAdded(LIST_VALUES, info.begin + oldItemsSize, addedItems); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getComponentItems(ListAddEvent event) { + return (Iterable) event.getItems(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getComponentItems(ListReplaceEvent event) { + return (Iterable) event.getNewItems(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected ListValueModel getComponentLVM(ListEvent event) { + return (ListValueModel) event.getSource(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositePropertyValueModel.java new file mode 100644 index 0000000000..0654c0694c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositePropertyValueModel.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.IdentityHashBag; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A CompositePropertyValueModel adapts a + * {@link CollectionValueModel} holding other {@link PropertyValueModel}s + * to a single {@link PropertyValueModel}. + *

+ * Subclasses must implement:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * component values + *

+ * NB: The wrapped collection must not contain any duplicates + * or this class will throw an exception. + */ +public abstract class CompositePropertyValueModel + extends CollectionPropertyValueModelAdapter +{ + /** + * Cache the component property value models so we can stop listening to + * them when they are removed from the collection value model. + */ + protected final IdentityHashBag> componentPVMs = + new IdentityHashBag>(); + + /** + * Listen to every property value model in the collection value model. + * If one changes, we need to re-calculate our value. + */ + protected final PropertyChangeListener propertyChangeListener; + + + // ********** constructors ********** + + /** + * Construct a property value model that is a composite of the specified + * property value models. + */ + public CompositePropertyValueModel(PropertyValueModel... collection) { + this(Arrays.asList(collection)); + } + + /** + * Construct a property value model that is a composite of the specified + * property value models. + */ + public > CompositePropertyValueModel(Collection collection) { + this(new StaticCollectionValueModel(collection)); + } + + /** + * Construct a property value model that is a composite of the specified + * property value models. + */ + public CompositePropertyValueModel(CollectionValueModel> collectionModel) { + super(collectionModel); + this.propertyChangeListener = this.buildPropertyChangeListener(); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildPropertyChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + CompositePropertyValueModel.this.propertyChanged(event); + } + @Override + public String toString() { + return "property change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Subclasses can override this method if the event can be used to improve + * the performance of building a new value (e.g. some property changes may + * not necessitate the re-calculation of the value). + */ + protected void propertyChanged(@SuppressWarnings("unused") PropertyChangeEvent event) { + this.propertyChanged(); + } + + + // ********** CollectionPropertyValueModelAdapter overrides ********** + + @Override + protected void engageModel_() { + super.engageModel_(); + this.addComponentPVMs(this.getCollectionModel()); + } + + protected > void addComponentPVMs(Iterable pvms) { + for (PropertyValueModel each : pvms) { + this.componentPVMs.add(each); + each.addPropertyChangeListener(VALUE, this.propertyChangeListener); + } + } + + @Override + protected void disengageModel_() { + this.removeComponentPVMs(this.getCollectionModel()); + super.disengageModel_(); + } + + protected > void removeComponentPVMs(Iterable pvms) { + for (PropertyValueModel each : pvms) { + each.removePropertyChangeListener(VALUE, this.propertyChangeListener); + this.componentPVMs.remove(each); + } + } + + @Override + protected void itemsAdded(CollectionAddEvent event) { + this.addComponentPVMs(this.getItems(event)); + super.itemsAdded(event); + } + + @Override + protected void itemsRemoved(CollectionRemoveEvent event) { + this.removeComponentPVMs(this.getItems(event)); + super.itemsRemoved(event); + } + + @Override + protected void collectionCleared(CollectionClearEvent event) { + this.removeAllComponentPVMs(); + super.collectionCleared(event); + } + + protected void removeAllComponentPVMs() { + // copy the list so we don't eat our own tail + ArrayList> copy = new ArrayList>(this.componentPVMs); + this.removeComponentPVMs(copy); + } + + @Override + protected void collectionChanged(CollectionChangeEvent event) { + this.removeAllComponentPVMs(); + this.addComponentPVMs(this.getCollectionModel()); + super.collectionChanged(event); + } + + + // ********** convenience methods ********** + + /** + * Our constructor accepts only a {@link CollectionValueModel}{@code>}. + */ + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected CollectionValueModel> getCollectionModel() { + return (CollectionValueModel>) this.collectionModel; + } + + /** + * Our constructor accepts only a {@link CollectionValueModel}{@code>}. + */ + @SuppressWarnings("unchecked") + protected Iterable> getItems(CollectionAddEvent event) { + return (Iterable>) event.getItems(); + } + + /** + * Our constructor accepts only a {@link CollectionValueModel}{@code>}. + */ + @SuppressWarnings("unchecked") + protected Iterable> getItems(CollectionRemoveEvent event) { + return (Iterable>) event.getItems(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ExtendedListValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ExtendedListValueModelWrapper.java new file mode 100644 index 0000000000..6601615cc4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ExtendedListValueModelWrapper.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyCompositeListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This wrapper extends a {@link ListValueModel} (or {@link CollectionValueModel}) + * with fixed collections of items on either end. + *

+ * NB: Be careful using or wrapping this list value model, since the + * "extended" items may be unexpected by the client code or wrapper. + */ +public class ExtendedListValueModelWrapper + extends ListValueModelWrapper + implements ListValueModel +{ + /** the items "prepended" to the wrapped list */ + protected List prefix; + + /** the items "appended" to the wrapped list */ + protected List suffix; + + + // ********** lots o' constructors ********** + + /** + * Extend the specified list with a prefix and suffix. + */ + public ExtendedListValueModelWrapper(List prefix, ListValueModel listHolder, List suffix) { + super(listHolder); + this.prefix = new ArrayList(prefix); + this.suffix = new ArrayList(suffix); + } + + /** + * Extend the specified list with a prefix and suffix. + */ + public ExtendedListValueModelWrapper(E prefix, ListValueModel listHolder, E suffix) { + super(listHolder); + this.prefix = Collections.singletonList(prefix); + this.suffix = Collections.singletonList(suffix); + } + + /** + * Extend the specified list with a prefix. + */ + public ExtendedListValueModelWrapper(List prefix, ListValueModel listHolder) { + super(listHolder); + this.prefix = new ArrayList(prefix); + this.suffix = Collections.emptyList(); + } + + /** + * Extend the specified list with a prefix. + */ + public ExtendedListValueModelWrapper(E prefix, ListValueModel listHolder) { + super(listHolder); + this.prefix = Collections.singletonList(prefix); + this.suffix = Collections.emptyList(); + } + + /** + * Extend the specified list with a suffix. + */ + public ExtendedListValueModelWrapper(ListValueModel listHolder, List suffix) { + super(listHolder); + this.prefix = Collections.emptyList(); + this.suffix = new ArrayList(suffix); + } + + /** + * Extend the specified list with a suffix. + */ + public ExtendedListValueModelWrapper(ListValueModel listHolder, E suffix) { + super(listHolder); + this.prefix = Collections.emptyList(); + this.suffix = Collections.singletonList(suffix); + } + + /** + * Extend the specified list with a prefix containing a single null item. + */ + public ExtendedListValueModelWrapper(ListValueModel listHolder) { + super(listHolder); + this.prefix = Collections.singletonList(null); + this.suffix = Collections.emptyList(); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.listIterator_()); + } + + @SuppressWarnings("unchecked") + protected ListIterator listIterator_() { + return new ReadOnlyCompositeListIterator( + this.prefix.listIterator(), + this.listHolder.listIterator(), + this.suffix.listIterator() + ); + } + + public E get(int index) { + int prefixSize = this.prefix.size(); + if (index < prefixSize) { + return this.prefix.get(index); + } else if (index >= prefixSize + this.listHolder.size()) { + return this.suffix.get(index - (prefixSize + this.listHolder.size())); + } else { + return this.listHolder.get(index - prefixSize); + } + } + + public int size() { + return this.prefix.size() + this.listHolder.size() + this.suffix.size(); + } + + public Object[] toArray() { + ArrayList list = new ArrayList(this.size()); + list.addAll(this.prefix); + CollectionTools.addAll(list, this.listHolder.iterator()); + list.addAll(this.suffix); + return list.toArray(); + } + + + // ********** ListValueModelWrapper implementation/overrides ********** + + @Override + protected void itemsAdded(ListAddEvent event) { + this.fireItemsAdded(event.clone(this, LIST_VALUES, this.prefix.size())); + } + + @Override + protected void itemsRemoved(ListRemoveEvent event) { + this.fireItemsRemoved(event.clone(this, LIST_VALUES, this.prefix.size())); + } + + @Override + protected void itemsReplaced(ListReplaceEvent event) { + this.fireItemsReplaced(event.clone(this, LIST_VALUES, this.prefix.size())); + } + + @Override + protected void itemsMoved(ListMoveEvent event) { + this.fireItemsMoved(event.clone(this, LIST_VALUES, this.prefix.size())); + } + + @Override + protected void listCleared(ListClearEvent event) { + this.fireListChanged(LIST_VALUES, this.buildList()); // not "cleared" + } + + @Override + protected void listChanged(ListChangeEvent event) { + this.fireListChanged(LIST_VALUES, this.buildList()); + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + + + // ********** miscellaneous ********** + + public void setPrefix(List prefix) { + this.prefix = prefix; + this.fireListChanged(LIST_VALUES, this.buildList()); + } + + public void setSuffix(List suffix) { + this.suffix = suffix; + this.fireListChanged(LIST_VALUES, this.buildList()); + } + + private List buildList() { + return CollectionTools.list(this.listIterator_()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringCollectionValueModel.java new file mode 100644 index 0000000000..3defd4f00d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringCollectionValueModel.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.Filter; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterables.FilteringIterable; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyIterator; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * A FilteringCollectionValueModel wraps another + * {@link CollectionValueModel} and uses a {@link Filter} + * to determine which items in the collection are returned by calls + * to {@link #iterator()}. + *

+ * The filter can be changed at any time; allowing the same + * adapter to be used with different filter criteria (e.g. when the user + * wants to view a list of .java files). + *

+ * NB: If the objects in the "filtered" collection can change in such a way + * that they should be removed from the "filtered" collection, you will + * need to wrap the original collection in an {@link ItemAspectListValueModelAdapter}. + * For example, if the filter only "accepts" items whose names begin + * with "X" and the names of the items can change, you will need to + * wrap the original list of unfiltered items with an + * {@link ItemPropertyListValueModelAdapter} that listens for changes to each + * item's name and fires the appropriate event whenever an item's name + * changes. The event will cause this wrapper to re-filter the changed + * item and add or remove it from the "filtered" collection as appropriate. + */ +public class FilteringCollectionValueModel + extends CollectionValueModelWrapper + implements CollectionValueModel +{ + /** This filters the items in the nested collection. */ + private Filter filter; + + /** Cache the items that were accepted by the filter */ + private final Collection filteredItems = new ArrayList(); + + + // ********** constructors ********** + + /** + * Construct a collection value model with the specified wrapped + * collection value model and a filter that simply accepts every object. + */ + public FilteringCollectionValueModel(CollectionValueModel collectionHolder) { + this(collectionHolder, Filter.Null.instance()); + } + + /** + * Construct a collection value model with the specified wrapped + * collection value model and filter. + */ + public FilteringCollectionValueModel(CollectionValueModel collectionHolder, Filter filter) { + super(collectionHolder); + this.filter = filter; + } + + /** + * Construct a collection value model with the specified wrapped + * list value model and a filter that simply accepts every object. + */ + public FilteringCollectionValueModel(ListValueModel listHolder) { + this(new ListCollectionValueModelAdapter(listHolder)); + } + + /** + * Construct a collection value model with the specified wrapped + * list value model and filter. + */ + public FilteringCollectionValueModel(ListValueModel listHolder, Filter filter) { + this(new ListCollectionValueModelAdapter(listHolder), filter); + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + return new ReadOnlyIterator(this.filteredItems); + } + + public int size() { + return this.filteredItems.size(); + } + + + // ********** CollectionValueModelWrapper overrides/implementation ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch our cache *after* we start listening to the nested collection, + // since its value might change when a listener is added + CollectionTools.addAll(this.filteredItems, this.filter(this.collectionHolder)); + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // clear out the cache when we are not listening to the nested collection + this.filteredItems.clear(); + } + + @Override + protected void itemsAdded(CollectionAddEvent event) { + // filter the values before propagating the change event + this.addItemsToCollection(this.filter(this.getItems(event)), this.filteredItems, VALUES); + } + + @Override + protected void itemsRemoved(CollectionRemoveEvent event) { + // do *not* filter the values, because they may no longer be + // "accepted" and that might be why they were removed in the first place; + // anyway, any extraneous items are harmless + this.removeItemsFromCollection(event.getItems(), this.filteredItems, VALUES); + } + + @Override + protected void collectionCleared(CollectionClearEvent event) { + this.clearCollection(this.filteredItems, VALUES); + } + + @Override + protected void collectionChanged(CollectionChangeEvent event) { + this.rebuildFilteredItems(); + } + + + // ********** miscellaneous ********** + + /** + * Change the filter and rebuild the collection. + */ + public void setFilter(Filter filter) { + this.filter = filter; + this.rebuildFilteredItems(); + } + + /** + * Return an iterable that filters the specified iterable. + */ + protected Iterable filter(Iterable items) { + return new FilteringIterable(items, this.filter); + } + + /** + * Synchronize our cache with the wrapped collection. + */ + protected void rebuildFilteredItems() { + this.filteredItems.clear(); + CollectionTools.addAll(this.filteredItems, this.filter(this.collectionHolder)); + this.fireCollectionChanged(VALUES, this.filteredItems); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.filteredItems); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringPropertyValueModel.java new file mode 100644 index 0000000000..56935822d8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringPropertyValueModel.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.Filter; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A FilteringPropertyValueModel wraps another + * {@link PropertyValueModel} and uses a {@link Filter} + * to determine when the wrapped value is to be returned by calls + * to {@link #getValue()}. + *

+ * As an alternative to building a {@link Filter}, a subclass + * can override {@link #accept(T)}. + *

+ * One, possibly undesirable, side-effect of using this value model is that + * it must return *something* as the value. The default behavior is + * to return null whenever the wrapped value is not "accepted", + * which can be configured and/or overridden ({@link #getDefaultValue()}). + */ +public class FilteringPropertyValueModel + extends PropertyValueModelWrapper + implements PropertyValueModel +{ + protected final Filter filter; + protected final T defaultValue; + + + // ********** constructors ********** + + /** + * Construct a filtering property value model with the specified nested + * property value model and a disabled filter. + * Use this constructor if you want to override {@link #accept(T)} + * instead of building a {@link Filter}. + * The default value will be null. + */ + public FilteringPropertyValueModel(PropertyValueModel valueHolder) { + this(valueHolder, Filter.Disabled.instance(), null); + } + + /** + * Construct a filtering property value model with the specified nested + * property value model, specified default value, and a disabled filter. + * Use this constructor if you want to override {@link #accept(T)} + * instead of building a {@link Filter} + * and you need to specify + * a default value other than null. + */ + public FilteringPropertyValueModel(PropertyValueModel valueHolder, T defaultValue) { + this(valueHolder, Filter.Disabled.instance(), defaultValue); + } + + /** + * Construct a filtering property value model with the specified nested + * property value model and filter. + * The default value will be null. + */ + public FilteringPropertyValueModel(PropertyValueModel valueHolder, Filter filter) { + this(valueHolder, filter, null); + } + + /** + * Construct a filtering property value model with the specified nested + * property value model, filter, and default value. + */ + public FilteringPropertyValueModel(PropertyValueModel valueHolder, Filter filter, T defaultValue) { + super(valueHolder); + this.filter = filter; + this.defaultValue = defaultValue; + } + + + // ********** PropertyValueModel implementation ********** + + public T getValue() { + return this.filterValue(this.valueHolder.getValue()); + } + + + // ********** PropertyValueModelWrapper implementation ********** + + @Override + protected void valueChanged(PropertyChangeEvent event) { + // filter the values before propagating the change event + @SuppressWarnings("unchecked") + T eventOldValue = (T) event.getOldValue(); + Object oldValue = this.filterValue(eventOldValue); + @SuppressWarnings("unchecked") + T eventNewValue = (T) event.getNewValue(); + Object newValue = this.filterValue(eventNewValue); + this.firePropertyChanged(VALUE, oldValue, newValue); + } + + + // ********** queries ********** + + /** + * If the specified value is "accepted" simply return it, + * otherwise return the default value. + */ + protected T filterValue(T value) { + return this.accept(value) ? value : this.getDefaultValue(); + } + + /** + * Return whether the filtering property value model should + * return the specified value from a call to + * {@link #getValue()}; the value came + * from the nested property value model + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link Filter}. + */ + protected boolean accept(T value) { + return this.filter.accept(value); + } + + /** + * Return the object that should be returned if + * the nested value was rejected by the filter. + * The default is null. + */ + protected T getDefaultValue() { + return this.defaultValue; + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringWritablePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringWritablePropertyValueModel.java new file mode 100644 index 0000000000..61eac0eec9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringWritablePropertyValueModel.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.BidiFilter; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * A FilteringWritablePropertyValueModel wraps another + * {@link WritablePropertyValueModel} and uses a {@link BidiFilter} + * to determine when the wrapped value is to be returned by calls + * to {@link FilteringPropertyValueModel#getValue() getValue()} and modified by calls to + * {@link #setValue(T)}. + *

+ * As an alternative to building a {@link BidiFilter}, a subclass + * can override {@link FilteringPropertyValueModel#accept(T) accept(T)} and {@link #reverseAccept(T)}. + *

+ * One, possibly undesirable, side-effect of using this value model is that + * it must return *something* as the value. The default behavior is + * to return null whenever the wrapped value is not "accepted", + * which can be configured and/or overridden ({@link FilteringPropertyValueModel#getDefaultValue() getDefaultValue()}). + *

+ * Similarly, if an incoming value is not "reverse accepted", nothing + * will passed through to the wrapped value holder, not even null. + */ +public class FilteringWritablePropertyValueModel + extends FilteringPropertyValueModel + implements WritablePropertyValueModel +{ + + + // ********** constructors ********** + + /** + * Construct a filtering property value model with the specified nested + * property value model and a disabled filter. + * Use this constructor if you want to override + * {@link #accept(T)} and {@link #reverseAccept(T)} + * instead of building a {@link BidiFilter}. + * The default value will be null. + */ + public FilteringWritablePropertyValueModel(WritablePropertyValueModel valueHolder) { + this(valueHolder, BidiFilter.Disabled.instance(), null); + } + + /** + * Construct a filtering property value model with the specified nested + * property value model, specified default value, and a disabled filter. + * Use this constructor if you want to override + * {@link #accept(T)} and {@link #reverseAccept(T)} + * instead of building a {@link BidiFilter}. + * and you need to specify + * a default value other than null. + */ + public FilteringWritablePropertyValueModel(WritablePropertyValueModel valueHolder, T defaultValue) { + this(valueHolder, BidiFilter.Disabled.instance(), defaultValue); + } + + /** + * Construct an property value model with the specified nested + * property value model and filter. + * The default value will be null. + */ + public FilteringWritablePropertyValueModel(WritablePropertyValueModel valueHolder, BidiFilter filter) { + this(valueHolder, filter, null); + } + + /** + * Construct an property value model with the specified nested + * property value model, filter, and default value. + */ + public FilteringWritablePropertyValueModel(WritablePropertyValueModel valueHolder, BidiFilter filter, T defaultValue) { + super(valueHolder, filter, defaultValue); + } + + + // ********** WritablePropertyValueModel implementation ********** + + public void setValue(T value) { + if (this.reverseAccept(value)) { + this.getValueHolder().setValue(value); + } + } + + + // ********** queries ********** + + /** + * Return whether the filtering writable property value model + * should pass through the specified value to the nested + * writable property value model in a call to + * {@link #setValue(T)}. + *

+ * This method can be overridden by a subclass as an + * alternative to building a {@link BidiFilter}. + */ + protected boolean reverseAccept(T value) { + return this.getFilter().reverseAccept(value); + } + + /** + * Our constructor accepts only a {@link WritablePropertyValueModel}{@code}. + */ + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + + /** + * Our constructors accept only a bidirectional filter. + */ + protected BidiFilter getFilter() { + return (BidiFilter) this.filter; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemAspectListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemAspectListValueModelAdapter.java new file mode 100644 index 0000000000..eff8183982 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemAspectListValueModelAdapter.java @@ -0,0 +1,274 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EventObject; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.SimpleIntReference; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Abstract list value model that provides behavior for wrapping a {@link ListValueModel} + * (or {@link CollectionValueModel}) and listening for changes to aspects of the + * items held by the list (or collection). Changes to the actual list + * (or collection) are also monitored. + * + * This is useful if you have a collection of items that can be modified by adding + * or removing items or the items themselves might change in a fashion that + * might change the list or collection's external appearance. + * + * Subclasses need to override two methods:

    + *
  • {@link #engageItem_(Model)}

    + * begin listening to the appropriate aspect of the specified item and call + * {@link #itemAspectChanged(EventObject)} whenever the aspect changes + *

  • {@link #disengageItem_(Model)}

    + * stop listening to the appropriate aspect of the specified item + *

+ */ +public abstract class ItemAspectListValueModelAdapter + extends ListValueModelWrapper + implements ListValueModel +{ + + /** + * Maintain a counter for each of the items in the + * wrapped list holder we are listening to. + */ + protected final IdentityHashMap counters; + + + // ********** constructors ********** + + /** + * Constructor - the list holder is required. + */ + protected ItemAspectListValueModelAdapter(ListValueModel listHolder) { + super(listHolder); + this.counters = new IdentityHashMap(); + } + + /** + * Constructor - the collection holder is required. + */ + protected ItemAspectListValueModelAdapter(CollectionValueModel collectionHolder) { + this(new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.listHolder.listIterator()); + } + + public E get(int index) { + return this.listHolder.get(index); + } + + public int size() { + return this.listHolder.size(); + } + + public Object[] toArray() { + return this.listHolder.toArray(); + } + + + // ********** behavior ********** + + /** + * Start listening to the list holder and the items in the list. + */ + @Override + protected void engageModel() { + super.engageModel(); + this.engageAllItems(); + } + + protected void engageAllItems() { + this.engageItems(this.listHolder); + } + + protected void engageItems(Iterable items) { + for (E item : items) { + this.engageItem(item); + } + } + + protected void engageItem(E item) { + // listen to each item only once + SimpleIntReference counter = this.counters.get(item); + if (counter == null) { + counter = new SimpleIntReference(); + this.counters.put(item, counter); + this.engageItem_((Model) item); + } + counter.increment(); + } + + /** + * Start listening to the specified item. + */ + protected abstract void engageItem_(Model item); + + /** + * Stop listening to the list holder and the items in the list. + */ + @Override + protected void disengageModel() { + this.disengageAllItems(); + super.disengageModel(); + } + + protected void disengageAllItems() { + this.disengageItems(this.listHolder); + } + + protected void disengageItems(Iterable items) { + for (E item : items) { + this.disengageItem(item); + } + } + + protected void disengageItem(E item) { + // stop listening to each item only once + SimpleIntReference counter = this.counters.get(item); + if (counter == null) { + // something is wrong if this happens... ~bjv + throw new IllegalStateException("missing counter: " + item); //$NON-NLS-1$ + } + if (counter.decrement() == 0) { + this.counters.remove(item); + this.disengageItem_((Model) item); + } + } + + /** + * Stop listening to the specified item. + */ + protected abstract void disengageItem_(Model item); + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + + + // ********** list change support ********** + + /** + * Items were added to the wrapped list holder. + * Forward the event and begin listening to the added items. + */ + @Override + protected void itemsAdded(ListAddEvent event) { + // re-fire event with the wrapper as the source + this.fireItemsAdded(event.clone(this, LIST_VALUES)); + this.engageItems(this.getItems(event)); + } + + /** + * Items were removed from the wrapped list holder. + * Stop listening to the removed items and forward the event. + */ + @Override + protected void itemsRemoved(ListRemoveEvent event) { + this.disengageItems(this.getItems(event)); + // re-fire event with the wrapper as the source + this.fireItemsRemoved(event.clone(this, LIST_VALUES)); + } + + /** + * Items were replaced in the wrapped list holder. + * Stop listening to the removed items, forward the event, + * and begin listening to the added items. + */ + @Override + protected void itemsReplaced(ListReplaceEvent event) { + this.disengageItems(this.getOldItems(event)); + // re-fire event with the wrapper as the source + this.fireItemsReplaced(event.clone(this, LIST_VALUES)); + this.engageItems(this.getNewItems(event)); + } + + /** + * Items were moved in the wrapped list holder. + * No need to change any listeners; just forward the event. + */ + @Override + protected void itemsMoved(ListMoveEvent event) { + // re-fire event with the wrapper as the source + this.fireItemsMoved(event.clone(this, LIST_VALUES)); + } + + /** + * The wrapped list holder was cleared. + * Stop listening to the removed items and forward the event. + */ + @Override + protected void listCleared(ListClearEvent event) { + // we should only need to disengage each item once... + // make a copy to prevent a ConcurrentModificationException + Collection keys = new ArrayList(this.counters.keySet()); + this.disengageItems(keys); + this.counters.clear(); + // re-fire event with the wrapper as the source + this.fireListCleared(LIST_VALUES); + } + + /** + * The wrapped list holder has changed in some dramatic fashion. + * Reconfigure our listeners and forward the event. + */ + @Override + protected void listChanged(ListChangeEvent event) { + // we should only need to disengage each item once... + // make a copy to prevent a ConcurrentModificationException + Collection keys = new ArrayList(this.counters.keySet()); + this.disengageItems(keys); + this.counters.clear(); + // re-fire event with the wrapper as the source + this.fireListChanged(event.clone(this)); + this.engageAllItems(); + } + + + // ********** item change support ********** + + /** + * The specified item has a bound property that has changed. + * Notify listeners of the change. The listeners will have to determine + * whether the item aspect change is significant. + */ + protected void itemAspectChanged(@SuppressWarnings("unused") EventObject event) { + this.fireListChanged(LIST_VALUES, CollectionTools.list(this.listHolder, this.listHolder.size())); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemChangeListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemChangeListValueModelAdapter.java new file mode 100644 index 0000000000..b6f817b79c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemChangeListValueModelAdapter.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.SimpleChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to all of the changes + * of each item in the wrapped list model. + */ +public class ItemChangeListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + /** Listener that listens to all the items in the list. */ + protected final ChangeListener itemChangeListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified items. + */ + public ItemChangeListValueModelAdapter(ListValueModel listHolder) { + super(listHolder); + this.itemChangeListener = this.buildItemChangeListener(); + } + + + // ********** initialization ********** + + protected ChangeListener buildItemChangeListener() { + return new SimpleChangeListener() { + @Override + protected void modelChanged(ChangeEvent event) { + ItemChangeListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + item.addChangeListener(this.itemChangeListener); + } + + @Override + protected void disengageItem_(Model item) { + item.removeChangeListener(this.itemChangeListener); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemCollectionListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemCollectionListValueModelAdapter.java new file mode 100644 index 0000000000..8079739d76 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemCollectionListValueModelAdapter.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to one or more collection + * aspects of each item in the wrapped list model. + */ +public class ItemCollectionListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + + /** The names of the items' collections that we listen to. */ + protected final String[] collectionNames; + + /** Listener that listens to all the items in the list. */ + protected final CollectionChangeListener itemCollectionListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified item Collections. + */ + public ItemCollectionListValueModelAdapter(ListValueModel listHolder, String... collectionNames) { + super(listHolder); + this.collectionNames = collectionNames; + this.itemCollectionListener = this.buildItemCollectionListener(); + } + + /** + * Construct an adapter for the specified item Collections. + */ + public ItemCollectionListValueModelAdapter(CollectionValueModel collectionHolder, String... collectionNames) { + this(new CollectionListValueModelAdapter(collectionHolder), collectionNames); + } + + + // ********** initialization ********** + + /** + * All we really care about is the fact that a Collection aspect has + * changed. Do the same thing no matter which event occurs. + */ + protected CollectionChangeListener buildItemCollectionListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + ItemCollectionListValueModelAdapter.this.itemAspectChanged(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + ItemCollectionListValueModelAdapter.this.itemAspectChanged(event); + } + public void collectionCleared(CollectionClearEvent event) { + ItemCollectionListValueModelAdapter.this.itemAspectChanged(event); + } + public void collectionChanged(CollectionChangeEvent event) { + ItemCollectionListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item collection listener: " + Arrays.asList(ItemCollectionListValueModelAdapter.this.collectionNames); //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + for (String collectionName : this.collectionNames) { + item.addCollectionChangeListener(collectionName, this.itemCollectionListener); + } + } + + @Override + protected void disengageItem_(Model item) { + for (String collectionName : this.collectionNames) { + item.removeCollectionChangeListener(collectionName, this.itemCollectionListener); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemListListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemListListValueModelAdapter.java new file mode 100644 index 0000000000..79cac2cdcc --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemListListValueModelAdapter.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to one or more list + * aspects of each item in the wrapped list model. + */ +public class ItemListListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + + /** The names of the subject's lists that we listen to. */ + protected final String[] listNames; + + /** Listener that listens to all the items in the list. */ + protected final ListChangeListener itemListListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified item List aspects. + */ + public ItemListListValueModelAdapter(ListValueModel listHolder, String... listNames) { + super(listHolder); + this.listNames = listNames; + this.itemListListener = this.buildItemListListener(); + } + + /** + * Construct an adapter for the specified item List aspects. + */ + public ItemListListValueModelAdapter(CollectionValueModel collectionHolder, String... listNames) { + this(new CollectionListValueModelAdapter(collectionHolder), listNames); + } + + + // ********** initialization ********** + + /** + * All we really care about is the fact that the List aspect has + * changed. Do the same thing no matter which event occurs. + */ + protected ListChangeListener buildItemListListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + public void itemsMoved(ListMoveEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + public void listCleared(ListClearEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + public void listChanged(ListChangeEvent event) { + ItemListListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item list listener: " + Arrays.asList(ItemListListValueModelAdapter.this.listNames); //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + for (String listName : this.listNames) { + item.addListChangeListener(listName, this.itemListListener); + } + } + + @Override + protected void disengageItem_(Model item) { + for (String listName : this.listNames) { + item.removeListChangeListener(listName, this.itemListListener); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemPropertyListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemPropertyListValueModelAdapter.java new file mode 100644 index 0000000000..870a36590f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemPropertyListValueModelAdapter.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to one or more + * properties of each item in the wrapped list model. + */ +public class ItemPropertyListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + + /** The names of the items' properties that we listen to. */ + protected final String[] propertyNames; + + /** Listener that listens to all the items in the list. */ + protected final PropertyChangeListener itemPropertyListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified item properties. + */ + public ItemPropertyListValueModelAdapter(ListValueModel listHolder, String... propertyNames) { + super(listHolder); + this.propertyNames = propertyNames; + this.itemPropertyListener = this.buildItemPropertyListener(); + } + + /** + * Construct an adapter for the specified item properties. + */ + public ItemPropertyListValueModelAdapter(CollectionValueModel collectionHolder, String... propertyNames) { + this(new CollectionListValueModelAdapter(collectionHolder), propertyNames); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildItemPropertyListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + ItemPropertyListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item property listener: " + Arrays.asList(ItemPropertyListValueModelAdapter.this.propertyNames); //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + for (String propertyName : this.propertyNames) { + item.addPropertyChangeListener(propertyName, this.itemPropertyListener); + } + } + + @Override + protected void disengageItem_(Model item) { + for (String propertyName : this.propertyNames) { + item.removePropertyChangeListener(propertyName, this.itemPropertyListener); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemStateListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemStateListValueModelAdapter.java new file mode 100644 index 0000000000..b4dc5ee61d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemStateListValueModelAdapter.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to the + * "state" of each item in the wrapped list model. + */ +public class ItemStateListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + /** Listener that listens to all the items in the list. */ + protected final StateChangeListener itemStateListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the item state. + */ + public ItemStateListValueModelAdapter(ListValueModel listHolder) { + super(listHolder); + this.itemStateListener = this.buildItemStateListener(); + } + + /** + * Construct an adapter for the item state. + */ + public ItemStateListValueModelAdapter(CollectionValueModel collectionHolder) { + this(new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** initialization ********** + + protected StateChangeListener buildItemStateListener() { + return new StateChangeListener() { + public void stateChanged(StateChangeEvent event) { + ItemStateListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item state listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + item.addStateChangeListener(this.itemStateListener); + } + + @Override + protected void disengageItem_(Model item) { + item.removeStateChangeListener(this.itemStateListener); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemTreeListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemTreeListValueModelAdapter.java new file mode 100644 index 0000000000..5ae5a968bd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemTreeListValueModelAdapter.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Extend {@link ItemAspectListValueModelAdapter} to listen to one or more tree + * aspects of each item in the wrapped list model. + */ +public class ItemTreeListValueModelAdapter + extends ItemAspectListValueModelAdapter +{ + + /** The names of the items' tree that we listen to. */ + protected final String[] treeNames; + + /** Listener that listens to all the items in the list. */ + protected final TreeChangeListener itemTreeListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified item trees. + */ + public ItemTreeListValueModelAdapter(ListValueModel listHolder, String... treeNames) { + super(listHolder); + this.treeNames = treeNames; + this.itemTreeListener = this.buildItemTreeListener(); + } + + /** + * Construct an adapter for the specified item trees. + */ + public ItemTreeListValueModelAdapter(CollectionValueModel collectionHolder, String... treeNames) { + this(new CollectionListValueModelAdapter(collectionHolder), treeNames); + } + + + // ********** initialization ********** + + /** + * All we really care about is the fact that a tree aspect has + * changed. Do the same thing no matter which event occurs. + */ + protected TreeChangeListener buildItemTreeListener() { + return new TreeChangeListener() { + public void nodeAdded(TreeAddEvent event) { + ItemTreeListValueModelAdapter.this.itemAspectChanged(event); + } + public void nodeRemoved(TreeRemoveEvent event) { + ItemTreeListValueModelAdapter.this.itemAspectChanged(event); + } + public void treeCleared(TreeClearEvent event) { + ItemTreeListValueModelAdapter.this.itemAspectChanged(event); + } + public void treeChanged(TreeChangeEvent event) { + ItemTreeListValueModelAdapter.this.itemAspectChanged(event); + } + @Override + public String toString() { + return "item tree listener: " + Arrays.asList(ItemTreeListValueModelAdapter.this.treeNames); //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + @Override + protected void engageItem_(Model item) { + for (String treeName : this.treeNames) { + item.addTreeChangeListener(treeName, this.itemTreeListener); + } + } + + @Override + protected void disengageItem_(Model item) { + for (String treeName : this.treeNames) { + item.removeTreeChangeListener(treeName, this.itemTreeListener); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListAspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListAspectAdapter.java new file mode 100644 index 0000000000..82b5eb4e9b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListAspectAdapter.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This extension of {@link AspectListValueModelAdapter} provides + * basic list change support. + * This converts a set of one or more lists into + * a single {@link #LIST_VALUES} list. + *

+ * The typical subclass will override the following methods (see the descriptions + * in {@link AspectListValueModelAdapter}):

    + *
  • {@link #listIterator_()} + *
  • {@link #get(int)} + *
  • {@link #size_()} + *
  • {@link #toArray_()} + *
  • {@link #listIterator()} + *
  • {@link #size()} + *
  • {@link #toArray()} + *
+ */ +public abstract class ListAspectAdapter + extends AspectListValueModelAdapter +{ + /** + * The name of the subject's lists that we use for the value. + */ + protected final String[] listNames; + protected static final String[] EMPTY_LIST_NAMES = new String[0]; + + /** A listener that listens to the subject's list aspects. */ + protected final ListChangeListener listChangeListener; + + + // ********** constructors ********** + + /** + * Construct a list aspect adapter for the specified subject + * and list. + */ + protected ListAspectAdapter(String listName, S subject) { + this(new String[] {listName}, subject); + } + + /** + * Construct a list aspect adapter for the specified subject + * and lists. + */ + protected ListAspectAdapter(String[] listNames, S subject) { + this(new StaticPropertyValueModel(subject), listNames); + } + + /** + * Construct a list aspect adapter for the specified subject holder + * and lists. + */ + protected ListAspectAdapter(PropertyValueModel subjectHolder, String... listNames) { + super(subjectHolder); + this.listNames = listNames; + this.listChangeListener = this.buildListChangeListener(); + } + + /** + * Construct a list aspect adapter for the specified subject holder + * and lists. + */ + protected ListAspectAdapter(PropertyValueModel subjectHolder, Collection listNames) { + this(subjectHolder, listNames.toArray(new String[listNames.size()])); + } + + /** + * Construct a list aspect adapter for an "unchanging" list in + * the specified subject. This is useful for a list aspect that does not + * change for a particular subject; but the subject will change, resulting in + * a new list. + */ + protected ListAspectAdapter(PropertyValueModel subjectHolder) { + this(subjectHolder, EMPTY_LIST_NAMES); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + // transform the subject's list change events into VALUE list change events + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListAspectAdapter.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListAspectAdapter.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListAspectAdapter.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ListAspectAdapter.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ListAspectAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ListAspectAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "list change listener: " + Arrays.asList(ListAspectAdapter.this.listNames); //$NON-NLS-1$ + } + }; + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected void engageSubject_() { + for (String listName : this.listNames) { + ((Model) this.subject).addListChangeListener(listName, this.listChangeListener); + } + } + + @Override + protected void disengageSubject_() { + for (String listName : this.listNames) { + ((Model) this.subject).removeListChangeListener(listName, this.listChangeListener); + } + } + + + // ********** behavior ********** + + protected void itemsAdded(ListAddEvent event) { + this.fireItemsAdded(event.clone(this, LIST_VALUES)); + } + + protected void itemsRemoved(ListRemoveEvent event) { + this.fireItemsRemoved(event.clone(this, LIST_VALUES)); + } + + protected void itemsReplaced(ListReplaceEvent event) { + this.fireItemsReplaced(event.clone(this, LIST_VALUES)); + } + + protected void itemsMoved(ListMoveEvent event) { + this.fireItemsMoved(event.clone(this, LIST_VALUES)); + } + + protected void listCleared(ListClearEvent event) { + this.fireListCleared(event.clone(this, LIST_VALUES)); + } + + protected void listChanged(ListChangeEvent event) { + this.fireListChanged(event.clone(this, LIST_VALUES)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCollectionValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCollectionValueModelAdapter.java new file mode 100644 index 0000000000..e37f223a6f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCollectionValueModelAdapter.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyIterator; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * An adapter that allows us to make a {@link ListValueModel} behave like + * a read-only {@link CollectionValueModel}, sorta. + *

+ * We keep an internal collection somewhat in synch with the wrapped list. + *

+ * NB: Since we only listen to the wrapped list when we have + * listeners ourselves and we can only stay in synch with the wrapped + * list while we are listening to it, results to various methods + * (e.g. {@link #size()}, {@link iterator()}) will be unpredictable whenever + * we do not have any listeners. This should not be too painful since, + * most likely, client objects will also be listeners. + */ +public class ListCollectionValueModelAdapter + extends AbstractCollectionValueModel + implements CollectionValueModel +{ + /** The wrapped list value model. */ + protected final ListValueModel listHolder; + + /** A listener that forwards any events fired by the list holder. */ + protected final ListChangeListener listChangeListener; + + /** + * Our internal collection, which holds the same elements as + * the wrapped list. + */ + // we declare this an ArrayList so we can use #clone() and #ensureCapacity(int) + protected final ArrayList collection; + + + // ********** constructors ********** + + /** + * Wrap the specified list value model. + */ + public ListCollectionValueModelAdapter(ListValueModel listHolder) { + super(); + if (listHolder == null) { + throw new NullPointerException(); + } + this.listHolder = listHolder; + this.listChangeListener = this.buildListChangeListener(); + this.collection = new ArrayList(); + // postpone building the collection and listening to the underlying list + // until we have listeners ourselves... + } + + + // ********** initialization ********** + + /** + * The wrapped list has changed, forward an equivalent + * collection change event to our listeners. + */ + protected ListChangeListener buildListChangeListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListCollectionValueModelAdapter.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListCollectionValueModelAdapter.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListCollectionValueModelAdapter.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ListCollectionValueModelAdapter.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ListCollectionValueModelAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ListCollectionValueModelAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "list change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + // try to prevent backdoor modification of the list + return new ReadOnlyIterator(this.collection); + } + + public int size() { + return this.collection.size(); + } + + + // ********** AbstractCollectionValueModel implementation ********** + + @Override + protected void engageModel() { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + // synch our collection *after* we start listening to the list holder, + // since its value might change when a listener is added + this.buildCollection(); + } + + @Override + protected void disengageModel() { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + // clear out the collection when we are not listening to the list holder + this.collection.clear(); + } + + + // ********** behavior ********** + + protected void itemsAdded(ListAddEvent event) { + this.addItemsToCollection(this.getItems(event), this.collection, VALUES); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(ListAddEvent event) { + return (Iterable) event.getItems(); + } + + protected void itemsRemoved(ListRemoveEvent event) { + this.removeItemsFromCollection(this.getItems(event), this.collection, VALUES); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(ListRemoveEvent event) { + return (Iterable) event.getItems(); + } + + protected void itemsReplaced(ListReplaceEvent event) { + this.removeItemsFromCollection(this.getOldItems(event), this.collection, VALUES); + this.addItemsToCollection(this.getNewItems(event), this.collection, VALUES); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getOldItems(ListReplaceEvent event) { + return (Iterable) event.getOldItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getNewItems(ListReplaceEvent event) { + return (Iterable) event.getNewItems(); + } + + protected void itemsMoved(@SuppressWarnings("unused") ListMoveEvent event) { + // do nothing? moving items in a list has no net effect on a collection... + } + + protected void listCleared(@SuppressWarnings("unused") ListClearEvent event) { + // put in empty check so we don't fire events unnecessarily + if ( ! this.collection.isEmpty()) { + this.collection.clear(); + this.fireCollectionCleared(VALUES); + } + } + + /** + * synchronize our internal collection with the wrapped list + * and fire the appropriate events + */ + protected void listChanged(@SuppressWarnings("unused") ListChangeEvent event) { + if (this.listHolder.size() == 0) { + if (this.collection.isEmpty()) { + // no change + } else { + this.clearCollection(this.collection, VALUES); + } + } else { + if (this.collection.isEmpty()) { + this.buildCollection(); + this.fireItemsAdded(VALUES, this.collection); + } else { + this.collection.clear(); + this.buildCollection(); + this.fireCollectionChanged(VALUES, this.collection); + } + } + } + + protected void buildCollection() { + // if the new list is empty, do nothing + int size = this.listHolder.size(); + if (size != 0) { + this.buildCollection(size); + } + } + + protected void buildCollection(int size) { + this.collection.ensureCapacity(size); + for (E each : this.listHolder) { + this.collection.add(each); + } + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.collection); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCurator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCurator.java new file mode 100644 index 0000000000..753638b098 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCurator.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.EventListener; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This extension of {@link AspectAdapter} provides list change support + * by adapting a subject's state change events to a minimum set + * of list change events. + */ +public abstract class ListCurator + extends AspectAdapter + implements ListValueModel +{ + /** How the list looked before the last state change */ + private final ArrayList record; + + /** A listener that listens for the subject's state to change */ + private final StateChangeListener stateChangeListener; + + + // ********** constructors ********** + + /** + * Construct a curator for the specified subject. + */ + protected ListCurator(S subject) { + this(new StaticPropertyValueModel(subject)); + } + + /** + * Construct a curator for the specified subject holder. + * The subject holder cannot be null. + */ + protected ListCurator(PropertyValueModel subjectHolder) { + super(subjectHolder); + this.record = new ArrayList(); + this.stateChangeListener = this.buildStateChangeListener(); + } + + + // ********** initialization ********** + + /** + * The subject's state has changed, do inventory and report to listeners. + */ + protected StateChangeListener buildStateChangeListener() { + return new StateChangeListener() { + public void stateChanged(StateChangeEvent event) { + ListCurator.this.submitInventoryReport(); + } + @Override + public String toString() { + return "state change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ListValueModel implementation ********** + + public ListIterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.record); + } + + /** + * Return the item at the specified index of the subject's list aspect. + */ + public E get(int index) { + return this.record.get(index); + } + + /** + * Return the size of the subject's list aspect. + */ + public int size() { + return this.record.size(); + } + + /** + * Return an array manifestation of the subject's list aspect. + */ + public Object[] toArray() { + return this.record.toArray(); + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected ListIterator getValue() { + return this.iterator(); + } + + @Override + protected Class getListenerClass() { + return ListChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return LIST_VALUES; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyListChangeListeners(LIST_VALUES); + } + + /** + * The aspect has changed, notify listeners appropriately. + */ + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + this.fireListChanged(LIST_VALUES, this.record); + } + + /** + * The subject is not null - add our listener. + */ + @Override + protected void engageSubject_() { + ((Model) this.subject).addStateChangeListener(this.stateChangeListener); + // synch our list *after* we start listening to the subject, + // since its value might change when a listener is added + CollectionTools.addAll(this.record, this.iteratorForRecord()); + } + + /** + * The subject is not null - remove our listener. + */ + @Override + protected void disengageSubject_() { + ((Model) this.subject).removeStateChangeListener(this.stateChangeListener); + // clear out the list when we are not listening to the subject + this.record.clear(); + } + + + // ********** ListCurator protocol ********** + + /** + * This is intended to be different from {@link ListValueModel#iterator()}. + * It is intended to be used only when the subject changes or the + * subject's "state" changes (as signified by a state change event). + */ + protected abstract Iterator iteratorForRecord(); + + + // ********** behavior ********** + + void submitInventoryReport() { + List newRecord = CollectionTools.list(this.iteratorForRecord()); + int recordIndex = 0; + + // add items from the new record + for (E newItem : newRecord) { + this.inventoryNewItem(recordIndex, newItem); + recordIndex++; + } + + // clean out items that are no longer in the new record + for (recordIndex = 0; recordIndex < this.record.size(); ) { + E item = this.record.get(recordIndex); + + if (newRecord.contains(item)) { + recordIndex++; + } else { + this.removeItemFromInventory(recordIndex, item); + } + } + } + + private void inventoryNewItem(int recordIndex, E newItem) { + List rec = new ArrayList(this.record); + + if ((recordIndex < rec.size()) && rec.get(recordIndex).equals(newItem)) { + return; + } + if (rec.contains(newItem)) { + this.removeItemFromInventory(recordIndex, rec.get(recordIndex)); + this.inventoryNewItem(recordIndex, newItem); + } else { + this.addItemToInventory(recordIndex, newItem); + } + } + + private void addItemToInventory(int index, E item) { + this.addItemToList(index, item, this.record, LIST_VALUES); + } + + private void removeItemFromInventory(int index, @SuppressWarnings("unused") E item) { + this.removeItemFromList(index, this.record, LIST_VALUES); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.record); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListPropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListPropertyValueModelAdapter.java new file mode 100644 index 0000000000..e3f7105771 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListPropertyValueModelAdapter.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * a list value model, "lazily" listen to it, and convert + * its change notifications into property value model change + * notifications. + *

+ * Subclasses must override:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current list value + *

+ * Subclasses might want to override the following methods + * to improve performance (by not recalculating the value, if possible):
    + *
  • {@link #itemsAdded(ListAddEvent event)} + *
  • {@link #itemsRemoved(ListRemoveEvent event)} + *
  • {@link #itemsReplaced(ListReplaceEvent event)} + *
  • {@link #itemsMoved(ListMoveEvent event)} + *
  • {@link #listCleared(ListClearEvent event)} + *
  • {@link #listChanged(ListChangeEvent event)} + *
+ */ +public abstract class ListPropertyValueModelAdapter + extends AbstractPropertyValueModelAdapter +{ + /** The wrapped list value model. */ + protected final ListValueModel listHolder; + + /** A listener that allows us to synch with changes to the wrapped list holder. */ + protected final ListChangeListener listChangeListener; + + + // ********** constructor/initialization ********** + + /** + * Construct a property value model with the specified wrapped + * list value model. + */ + protected ListPropertyValueModelAdapter(ListValueModel listHolder) { + super(); + this.listHolder = listHolder; + this.listChangeListener = this.buildListChangeListener(); + } + + protected ListChangeListener buildListChangeListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListPropertyValueModelAdapter.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListPropertyValueModelAdapter.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListPropertyValueModelAdapter.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ListPropertyValueModelAdapter.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ListPropertyValueModelAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ListPropertyValueModelAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "list change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the list holder. + */ + @Override + protected void engageModel_() { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + /** + * Stop listening to the list holder. + */ + @Override + protected void disengageModel_() { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + + // ********** collection change support ********** + + /** + * Items were added to the wrapped list holder; + * propagate the change notification appropriately. + */ + protected void itemsAdded(@SuppressWarnings("unused") ListAddEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * Items were removed from the wrapped list holder; + * propagate the change notification appropriately. + */ + protected void itemsRemoved(@SuppressWarnings("unused") ListRemoveEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * Items were replaced in the wrapped list holder; + * propagate the change notification appropriately. + */ + protected void itemsReplaced(@SuppressWarnings("unused") ListReplaceEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * Items were moved in the wrapped list holder; + * propagate the change notification appropriately. + */ + protected void itemsMoved(@SuppressWarnings("unused") ListMoveEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The wrapped list holder was cleared; + * propagate the change notification appropriately. + */ + protected void listCleared(@SuppressWarnings("unused") ListClearEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The value of the wrapped list holder has changed; + * propagate the change notification appropriately. + */ + protected void listChanged(@SuppressWarnings("unused") ListChangeEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListValueModelWrapper.java new file mode 100644 index 0000000000..7e01a399f3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListValueModelWrapper.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * another list value model, "lazily" listen to it, and propagate + * its change notifications. Subclasses must implement the appropriate + * {@link ListValueModel}. + */ +public abstract class ListValueModelWrapper + extends AbstractListValueModel +{ + /** The wrapped list value model. */ + protected final ListValueModel listHolder; + + /** A listener that allows us to synch with changes to the wrapped list holder. */ + protected final ListChangeListener listChangeListener; + + + // ********** constructors ********** + + /** + * Construct a list value model with the specified wrapped + * list value model. + */ + protected ListValueModelWrapper(ListValueModel listHolder) { + super(); + if (listHolder == null) { + throw new NullPointerException(); + } + this.listHolder = listHolder; + this.listChangeListener = this.buildListChangeListener(); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListValueModelWrapper.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListValueModelWrapper.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListValueModelWrapper.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ListValueModelWrapper.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ListValueModelWrapper.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ListValueModelWrapper.this.listChanged(event); + } + @Override + public String toString() { + return "list change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the list holder. + */ + @Override + protected void engageModel() { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + /** + * Stop listening to the list holder. + */ + @Override + protected void disengageModel() { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(ListAddEvent event) { + return (Iterable) event.getItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(ListRemoveEvent event) { + return (Iterable) event.getItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getNewItems(ListReplaceEvent event) { + return (Iterable) event.getNewItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getOldItems(ListReplaceEvent event) { + return (Iterable) event.getOldItems(); + } + + + // ********** list change support ********** + + /** + * Items were added to the wrapped list holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsAdded(ListAddEvent event); + + /** + * Items were removed from the wrapped list holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsRemoved(ListRemoveEvent event); + + /** + * Items were replaced in the wrapped list holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsReplaced(ListReplaceEvent event); + + /** + * Items were moved in the wrapped list holder; + * propagate the change notification appropriately. + */ + protected abstract void itemsMoved(ListMoveEvent event); + + /** + * The wrapped list holder was cleared; + * propagate the change notification appropriately. + */ + protected abstract void listCleared(ListClearEvent event); + + /** + * The value of the wrapped list holder has changed; + * propagate the change notification appropriately. + */ + protected abstract void listChanged(ListChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullCollectionValueModel.java new file mode 100644 index 0000000000..ba1061b96c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullCollectionValueModel.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * A read-only collection value model for when you + * don't need to support a collection. In particular, this + * is useful for the leaf nodes of a tree that never have + * children. + *

+ * We don't use a singleton because we hold on to listeners. + */ +public final class NullCollectionValueModel + extends AbstractModel + implements CollectionValueModel +{ + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public NullCollectionValueModel() { + super(); + } + + + // ********** CollectionValueModel implementation ********** + + public int size() { + return 0; + } + + public Iterator iterator() { + return EmptyIterator.instance(); + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullListValueModel.java new file mode 100644 index 0000000000..78801cce30 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullListValueModel.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyListIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * An empty list value model for when you don't + * need to support a list. + *

+ * We don't use a singleton because we hold on to listeners. + */ +public final class NullListValueModel + extends AbstractModel + implements ListValueModel +{ + private static final Object[] EMPTY_ARRAY = new Object[0]; + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public NullListValueModel() { + super(); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return EmptyIterator.instance(); + } + + public ListIterator listIterator() { + return EmptyListIterator.instance(); + } + + public int size() { + return 0; + } + + public E get(int index) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public Object[] toArray() { + return EMPTY_ARRAY; + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullPropertyValueModel.java new file mode 100644 index 0000000000..f445d96b36 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullPropertyValueModel.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A property value model for when you + * don't need to support a value. + *

+ * We don't use a singleton because we hold on to listeners. + */ +public final class NullPropertyValueModel + extends AbstractModel + implements PropertyValueModel +{ + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public NullPropertyValueModel() { + super(); + } + + + // ********** PropertyValueModel implementation ********** + + public T getValue() { + return null; + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullTreeValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullTreeValueModel.java new file mode 100644 index 0000000000..ad4b960b6e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullTreeValueModel.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.TreeValueModel; + +/** + * A tree value model for when you + * don't need to support any nodes. + *

+ * We don't use a singleton because we hold on to listeners. + */ +public final class NullTreeValueModel + extends AbstractModel + implements TreeValueModel +{ + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public NullTreeValueModel() { + super(); + } + + + // ********** TreeValueModel implementation ********** + + public Iterator nodes() { + return EmptyIterator.instance(); + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyAspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyAspectAdapter.java new file mode 100644 index 0000000000..327a7342d3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyAspectAdapter.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This {@link AspectPropertyValueModelAdapter} provides basic property change support. + * This converts a set of one or more standard properties into + * a single {@link #VALUE} property. + *

+ * The typical subclass will override the following methods (see the descriptions + * in {@link AspectPropertyValueModelAdapter}):

    + *
  • {@link #buildValue_()} + *
  • {@link #setValue_(Object)} + *
  • {@link #buildValue()} + *
  • {@link #setValue(Object)} + *
+ */ +public abstract class PropertyAspectAdapter + extends AspectPropertyValueModelAdapter +{ + /** The name of the subject's properties that we use for the value. */ + protected final String[] propertyNames; + protected static final String[] EMPTY_PROPERTY_NAMES = new String[0]; + + /** A listener that listens to the appropriate properties of the subject. */ + protected final PropertyChangeListener propertyChangeListener; + + + // ********** constructors ********** + + /** + * Construct a property aspect adapter for the specified subject + * and property. + */ + protected PropertyAspectAdapter(String propertyName, S subject) { + this(new String[] {propertyName}, subject); + } + + /** + * Construct a property aspect adapter for the specified subject + * and properties. + */ + protected PropertyAspectAdapter(String[] propertyNames, S subject) { + this(new StaticPropertyValueModel(subject), propertyNames); + } + + /** + * Construct a property aspect adapter for the specified subject holder + * and properties. + */ + protected PropertyAspectAdapter(PropertyValueModel subjectHolder, String... propertyNames) { + super(subjectHolder); + this.propertyNames = propertyNames; + this.propertyChangeListener = this.buildPropertyChangeListener(); + } + + /** + * Construct a property aspect adapter for the specified subject holder + * and properties. + */ + protected PropertyAspectAdapter(PropertyValueModel subjectHolder, Collection propertyNames) { + this(subjectHolder, propertyNames.toArray(new String[propertyNames.size()])); + } + + /** + * Construct a property aspect adapter for an "unchanging" property in + * the specified subject. This is useful for a property aspect that does not + * change for a particular subject; but the subject will change, resulting in + * a new property. (A {@link TransformationPropertyValueModel} could also be + * used in this situation.) + */ + protected PropertyAspectAdapter(PropertyValueModel subjectHolder) { + this(subjectHolder, EMPTY_PROPERTY_NAMES); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildPropertyChangeListener() { + // transform the subject's property change events into VALUE property change events + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + PropertyAspectAdapter.this.propertyChanged(event); + } + @Override + public String toString() { + return "property change listener: " + Arrays.asList(PropertyAspectAdapter.this.propertyNames); //$NON-NLS-1$ + } + }; + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected void engageSubject_() { + for (String propertyName : this.propertyNames) { + ((Model) this.subject).addPropertyChangeListener(propertyName, this.propertyChangeListener); + } + } + + @Override + protected void disengageSubject_() { + for (String propertyName : this.propertyNames) { + ((Model) this.subject).removePropertyChangeListener(propertyName, this.propertyChangeListener); + } + } + + protected void propertyChanged(@SuppressWarnings("unused") PropertyChangeEvent event) { + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyCollectionValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyCollectionValueModelAdapter.java new file mode 100644 index 0000000000..1d432aeda5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyCollectionValueModelAdapter.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Collections; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyIterator; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementIterator; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * An adapter that allows us to make a {@link PropertyValueModel} behave like + * a read-only, single-element {@link CollectionValueModel}, sorta. + *

+ * If the property's value is null, an empty iterator is returned + * (i.e. you can't have a collection with a null element). + */ +public class PropertyCollectionValueModelAdapter + extends AbstractCollectionValueModel + implements CollectionValueModel +{ + /** The wrapped property value model. */ + protected final PropertyValueModel valueHolder; + + /** A listener that forwards any events fired by the value holder. */ + protected final PropertyChangeListener propertyChangeListener; + + /** Cache the value. */ + protected E value; + + + // ********** constructors/initialization ********** + + /** + * Convert the specified property value model to a collection + * value model. + */ + public PropertyCollectionValueModelAdapter(PropertyValueModel valueHolder) { + super(); + if (valueHolder == null) { + throw new NullPointerException(); + } + this.valueHolder = valueHolder; + this.propertyChangeListener = this.buildPropertyChangeListener(); + this.value = null; + // postpone building the value and listening to the underlying value + // until we have listeners ourselves... + } + + /** + * The wrapped value has changed, forward an equivalent + * collection change event to our listeners. + */ + protected PropertyChangeListener buildPropertyChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + @SuppressWarnings("unchecked") + E eventNewValue = (E) event.getNewValue(); + PropertyCollectionValueModelAdapter.this.valueChanged(eventNewValue); + } + @Override + public String toString() { + return "property change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + return (this.value == null) ? EmptyIterator.instance() : this.iterator_(); + } + + protected Iterator iterator_() { + return new SingleElementIterator(this.value); + } + + public int size() { + return (this.value == null) ? 0 : 1; + } + + + // ********** AbstractCollectionValueModel implementation ********** + + @Override + protected void engageModel() { + this.valueHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + // synch our value *after* we start listening to the value holder, + // since its value might change when a listener is added + this.value = this.valueHolder.getValue(); + } + + @Override + protected void disengageModel() { + this.valueHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + // clear out the value when we are not listening to the value holder + this.value = null; + } + + + // ********** behavior ********** + + /** + * synchronize our internal value with the wrapped value + * and fire the appropriate events + */ + protected void valueChanged(E newValue) { + E oldValue = this.value; + this.value = newValue; + if (oldValue == null) { + // we wouldn't get the event if the new value were null too + this.fireItemAdded(VALUES, newValue); + } else { + if (newValue == null) { + this.fireItemRemoved(VALUES, oldValue); + } else { + // we wouldn't get the event if the new value was the same as the old + this.fireCollectionChanged(VALUES, Collections.singleton(newValue)); + } + } + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyListValueModelAdapter.java new file mode 100644 index 0000000000..81fa3cc8a8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyListValueModelAdapter.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.EmptyListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.SingleElementListIterator; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * An adapter that allows us to make a {@link PropertyValueModel} behave like + * a read-only, single-element {@link ListValueModel}, sorta. + *

+ * If the property's value is null, an empty iterator is returned + * (i.e. you can't have a collection with a null element). + */ +public class PropertyListValueModelAdapter + extends AbstractListValueModel + implements ListValueModel +{ + /** The wrapped property value model. */ + protected final PropertyValueModel valueHolder; + + /** A listener that forwards any events fired by the value holder. */ + protected final PropertyChangeListener propertyChangeListener; + + /** Cache the value. */ + protected E value; + + + // ********** constructors/initialization ********** + + /** + * Convert the specified property value model to a list + * value model. + */ + public PropertyListValueModelAdapter(PropertyValueModel valueHolder) { + super(); + if (valueHolder == null) { + throw new NullPointerException(); + } + this.valueHolder = valueHolder; + this.propertyChangeListener = this.buildPropertyChangeListener(); + // postpone building the value and listening to the underlying value + // until we have listeners ourselves... + } + + /** + * The wrapped value has changed, forward an equivalent + * list change event to our listeners. + */ + protected PropertyChangeListener buildPropertyChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + @SuppressWarnings("unchecked") + E eventNewValue = (E) event.getNewValue(); + PropertyListValueModelAdapter.this.valueChanged(eventNewValue); + } + @Override + public String toString() { + return "property change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return (this.value == null) ? + EmptyListIterator.instance() + : + new SingleElementListIterator(this.value); + } + + public int size() { + return (this.value == null) ? 0 : 1; + } + + public E get(int index) { + if (this.value == null) { + throw this.buildIOOBE(index, 0); + } + if (index > 0) { + throw this.buildIOOBE(index, 1); + } + return this.value; + } + + protected static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public Object[] toArray() { + return (this.value == null) ? EMPTY_OBJECT_ARRAY : new Object[] {this.value}; + } + + + // ********** behavior ********** + + protected IndexOutOfBoundsException buildIOOBE(int index, int size) { + return new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override + protected void engageModel() { + this.valueHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + // synch our value *after* we start listening to the value holder, + // since its value might change when a listener is added + this.value = this.valueHolder.getValue(); + } + + @Override + protected void disengageModel() { + this.valueHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + // clear out the value when we are not listening to the value holder + this.value = null; + } + + /** + * synchronize our internal value with the wrapped value + * and fire the appropriate events + */ + protected void valueChanged(E newValue) { + E oldValue = this.value; + this.value = newValue; + if (oldValue == null) { + this.fireItemAdded(LIST_VALUES, 0, newValue); + } else { + if (newValue == null) { + this.fireItemRemoved(LIST_VALUES, 0, oldValue); + } else { + this.fireItemReplaced(LIST_VALUES, 0, newValue, oldValue); + } + } + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyValueModelWrapper.java new file mode 100644 index 0000000000..241b85d609 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyValueModelWrapper.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * another property value model, "lazily" listen to it, and propagate + * its change notifications. Subclasses must implement the appropriate + * {@link PropertyValueModel}. + *

+ * Subclasses must implement the following methods:

    + *
  • {@link #valueChanged(PropertyChangeEvent)}

    + * implement this method to propagate the appropriate change notification + *

+ */ +public abstract class PropertyValueModelWrapper + extends AbstractPropertyValueModel +{ + /** The wrapped property value model. */ + protected final PropertyValueModel valueHolder; + + /** A listener that allows us to synch with changes to the wrapped value holder. */ + protected final PropertyChangeListener valueChangeListener; + + + // ********** constructors/initialization ********** + + /** + * Construct a property value model with the specified wrapped + * property value model. The value holder is required. + */ + protected PropertyValueModelWrapper(PropertyValueModel valueHolder) { + super(); + if (valueHolder == null) { + throw new NullPointerException(); + } + this.valueHolder = valueHolder; + this.valueChangeListener = this.buildValueChangeListener(); + } + + protected PropertyChangeListener buildValueChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + PropertyValueModelWrapper.this.valueChanged(event); + } + @Override + public String toString() { + return "value change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Begin listening to the value holder. + */ + @Override + protected void engageModel() { + this.valueHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.valueChangeListener); + } + + /** + * Stop listening to the value holder. + */ + @Override + protected void disengageModel() { + this.valueHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.valueChangeListener); + } + + + // ********** property change support ********** + + /** + * The value of the wrapped value holder has changed; + * propagate the change notification appropriately. + */ + protected abstract void valueChanged(PropertyChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ReadOnlyWritablePropertyValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ReadOnlyWritablePropertyValueModelWrapper.java new file mode 100644 index 0000000000..dcf7b97594 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ReadOnlyWritablePropertyValueModelWrapper.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * A simple implementation of {@link WritablePropertyValueModel} that actually + * ... isn't ... writable. It can however be used in places that require a + * {@link WritablePropertyValueModel} and where the developer is sure that no + * attempt will be made to write to it. + */ +public class ReadOnlyWritablePropertyValueModelWrapper + extends PropertyValueModelWrapper + implements WritablePropertyValueModel +{ + public ReadOnlyWritablePropertyValueModelWrapper(PropertyValueModel valueHolder) { + super(valueHolder); + } + + + public T getValue() { + return this.valueHolder.getValue(); + } + + public void setValue(T value) { + throw new UnsupportedOperationException(); + } + + @Override + protected void valueChanged(PropertyChangeEvent event) { + this.firePropertyChanged(event.clone(this)); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SetCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SetCollectionValueModel.java new file mode 100644 index 0000000000..d04722f9ff --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SetCollectionValueModel.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.HashBag; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyIterator; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * A SetCollectionValueModel wraps another + * {@link CollectionValueModel} and returns the items in the collection + * only once. + */ +public class SetCollectionValueModel + extends CollectionValueModelWrapper + implements CollectionValueModel +{ + private final HashBag bag = new HashBag(); + + + // ********** constructors ********** + + /** + * Construct a collection value model with the specified wrapped + * collection value model and a filter that simply accepts every object. + */ + public SetCollectionValueModel(CollectionValueModel collectionHolder) { + super(collectionHolder); + } + + /** + * Construct a collection value model with the specified wrapped + * list value model and a filter that simply accepts every object. + */ + public SetCollectionValueModel(ListValueModel listHolder) { + this(new ListCollectionValueModelAdapter(listHolder)); + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + return new ReadOnlyIterator(this.bag.uniqueIterator()); + } + + public int size() { + return this.bag.uniqueCount(); + } + + + // ********** CollectionValueModelWrapper overrides/implementation ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch our cache *after* we start listening to the nested collection, + // since its value might change when a listener is added + CollectionTools.addAll(this.bag, this.collectionHolder); + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // clear out the cache when we are not listening to the nested collection + this.bag.clear(); + } + + @Override + protected void itemsAdded(CollectionAddEvent event) { + ArrayList addedItems = new ArrayList(event.getItemsSize()); + int uniqueCount = this.bag.uniqueCount(); + for (E item : this.getItems(event)) { + this.bag.add(item); + if (this.bag.uniqueCount() > uniqueCount) { + uniqueCount = this.bag.uniqueCount(); + addedItems.add(item); + } + } + this.fireItemsAdded(VALUES, addedItems); + } + + @Override + protected void itemsRemoved(CollectionRemoveEvent event) { + ArrayList removedItems = new ArrayList(event.getItemsSize()); + int uniqueCount = this.bag.uniqueCount(); + for (E item : this.getItems(event)) { + if (this.bag.remove(item)) { + if (this.bag.uniqueCount() < uniqueCount) { + uniqueCount = this.bag.uniqueCount(); + removedItems.add(item); + } + } else { + throw new IllegalStateException("missing item: " + item); //$NON-NLS-1$ + } + } + this.fireItemsRemoved(VALUES, removedItems); + } + + @Override + protected void collectionCleared(CollectionClearEvent event) { + this.clearCollection(this.bag, VALUES); + } + + @Override + protected void collectionChanged(CollectionChangeEvent event) { + this.bag.clear(); + CollectionTools.addAll(this.bag, this.collectionHolder); + this.fireCollectionChanged(VALUES, new HashSet(this.bag)); + } + + @Override + public void toString(StringBuilder sb) { + StringTools.append(sb, this); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleCollectionValueModel.java new file mode 100644 index 0000000000..794eec1e11 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleCollectionValueModel.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.HashBag; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.WritableCollectionValueModel; + +/** + * Implementation of {@link WritableCollectionValueModel} and {@link Collection} + * that simply holds a collection and notifies listeners of any changes. + */ +public class SimpleCollectionValueModel + extends AbstractModel + implements WritableCollectionValueModel, Collection +{ + /** The collection. */ + protected final Collection collection; + + + // ********** constructors ********** + + /** + * Construct a collection value model for the specified collection. + */ + public SimpleCollectionValueModel(Collection collection) { + super(); + if (collection == null) { + throw new NullPointerException(); + } + this.collection = collection; + } + + /** + * Construct a collection value model with an empty initial collection. + */ + public SimpleCollectionValueModel() { + this(new HashBag()); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, CollectionChangeListener.class, VALUES); + } + + + // ********** CollectionValueModel implementation ********** + + public Iterator iterator() { + return new LocalIterator(this.collection.iterator()); + } + + public int size() { + return this.collection.size(); + } + + + // ********** WritableCollectionValueModel implementation ********** + + /** + * Allow the collection's elements to be replaced. + */ + public void setValues(Iterable values) { + if (values == null) { + throw new NullPointerException(); + } + this.collection.clear(); + CollectionTools.addAll(this.collection, values); + this.fireCollectionChanged(VALUES, this.collection); + } + + + // ********** Collection implementation ********** + + public boolean isEmpty() { + return this.collection.isEmpty(); + } + + public boolean contains(Object o) { + return this.collection.contains(o); + } + + public Object[] toArray() { + return this.collection.toArray(); + } + + public T[] toArray(T[] a) { + return this.collection.toArray(a); + } + + public boolean add(E o) { + return this.addItemToCollection(o, this.collection, VALUES); + } + + public boolean remove(Object o) { + return this.removeItemFromCollection(o, this.collection, VALUES); + } + + public boolean containsAll(Collection c) { + return this.collection.containsAll(c); + } + + public boolean addAll(Collection c) { + return this.addItemsToCollection(c, this.collection, VALUES); + } + + public boolean removeAll(Collection c) { + return this.removeItemsFromCollection(c, this.collection, VALUES); + } + + public boolean retainAll(Collection c) { + return this.retainItemsInCollection(c, this.collection, VALUES); + } + + public void clear() { + this.clearCollection(this.collection, VALUES); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if ((o instanceof Collection) && (o instanceof CollectionValueModel)) { + Collection c1 = CollectionTools.collection(this.collection); + @SuppressWarnings("unchecked") + Collection c2 = CollectionTools.collection(((Collection) o).iterator()); + return c1.equals(c2); + } + return false; + } + + @Override + public int hashCode() { + return CollectionTools.collection(this.collection).hashCode(); + } + + + // ********** miscellaneous ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.collection); + } + + + // ********** iterator ********** + + private class LocalIterator implements Iterator { + private final Iterator iterator; + private T next; + + LocalIterator(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public T next() { + return this.next = this.iterator.next(); + } + + @SuppressWarnings("synthetic-access") + public void remove() { + this.iterator.remove(); + SimpleCollectionValueModel.this.fireItemRemoved(VALUES, this.next); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleListValueModel.java new file mode 100644 index 0000000000..90943f9af1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleListValueModel.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.WritableListValueModel; + +/** + * Implementation of {@link ListValueModel} and {@link List} that simply holds a + * list and notifies listeners of any changes. + */ +public class SimpleListValueModel + extends AbstractModel + implements WritableListValueModel, List +{ + /** The list. */ + protected List list; + + + // ********** constructors ********** + + /** + * Construct a list value model for the specified list. + */ + public SimpleListValueModel(List list) { + super(); + if (list == null) { + throw new NullPointerException(); + } + this.list = list; + } + + /** + * Construct a list value model with an empty initial list. + */ + public SimpleListValueModel() { + this(new ArrayList()); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, ListChangeListener.class, LIST_VALUES); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return new LocalIterator(this.list.iterator()); + } + + public ListIterator listIterator() { + return new LocalListIterator(this.list.listIterator()); + } + + public int size() { + return this.list.size(); + } + + public E get(int index) { + return this.list.get(index); + } + + + // ********** WritableListValueModel implementation ********** + + /** + * Allow the list's elements to be replaced. + */ + public void setListValues(Iterable list) { + if (list == null) { + throw new NullPointerException(); + } + this.list.clear(); + CollectionTools.addAll(this.list, list); + this.fireListChanged(LIST_VALUES, this.list); + } + + + // ********** List implementation ********** + + public boolean isEmpty() { + return this.list.isEmpty(); + } + + public boolean contains(Object o) { + return this.list.contains(o); + } + + public Object[] toArray() { + return this.list.toArray(); + } + + public T[] toArray(T[] a) { + return this.list.toArray(a); + } + + public boolean add(E o) { + return this.addItemToList(o, this.list, LIST_VALUES); + } + + public boolean remove(Object o) { + return this.removeItemFromList(o, this.list, LIST_VALUES); + } + + public boolean containsAll(Collection c) { + return this.list.containsAll(c); + } + + public boolean addAll(Collection c) { + return this.addItemsToList(c, this.list, LIST_VALUES); + } + + public boolean addAll(int index, Collection c) { + return this.addItemsToList(index, c, this.list, LIST_VALUES); + } + + public boolean removeAll(Collection c) { + return this.removeItemsFromList(c, this.list, LIST_VALUES); + } + + public boolean retainAll(Collection c) { + return this.retainItemsInList(c, this.list, LIST_VALUES); + } + + public void clear() { + this.clearList(this.list, LIST_VALUES); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if ((o instanceof List) && (o instanceof ListValueModel)) { + List l1 = CollectionTools.list(this.list); + @SuppressWarnings("unchecked") + List l2 = CollectionTools.list(((List) o).iterator()); + return l1.equals(l2); + } + return false; + } + + @Override + public int hashCode() { + return this.list.hashCode(); + } + + public E set(int index, E element) { + return this.setItemInList(index, element, this.list, LIST_VALUES); + } + + public void add(int index, E element) { + this.addItemToList(index, element, this.list, LIST_VALUES); + } + + public E remove(int index) { + return this.removeItemFromList(index, this.list, LIST_VALUES); + } + + public int indexOf(Object o) { + return this.list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return this.list.lastIndexOf(o); + } + + public ListIterator listIterator(int index) { + return new LocalListIterator(this.list.listIterator(index)); + } + + public List subList(int fromIndex, int toIndex) { + // TODO hmmm ~bjv + throw new UnsupportedOperationException(); + } + + + // ********** additional behavior ********** + + /** + * Move a single element. + */ + public void move(int targetIndex, int sourceIndex) { + this.moveItemInList(targetIndex, sourceIndex, this.list, LIST_VALUES); + } + + /** + * Move a sub-list of elements. + */ + public void move(int targetIndex, int sourceIndex, int length) { + this.moveItemsInList(targetIndex, sourceIndex, length, this.list, LIST_VALUES); + } + + /** + * Remove a range of elements. + */ + public void remove(int index, int length) { + this.removeItemsFromList(index, length, this.list, LIST_VALUES); + } + + /** + * Set a range of elements. + */ + public void set(int index, List elements) { + this.setItemsInList(index, elements, this.list, LIST_VALUES); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.list); + } + + + // ********** iterators ********** + + private class LocalIterator implements Iterator { + private final Iterator iterator; + private int index = -1; + private T next; + + LocalIterator(Iterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public T next() { + this.next = this.iterator.next(); + this.index++; + return this.next; + } + + @SuppressWarnings("synthetic-access") + public void remove() { + this.iterator.remove(); + SimpleListValueModel.this.fireItemRemoved(LIST_VALUES, this.index, this.next); + } + + } + + private class LocalListIterator implements ListIterator { + private final ListIterator iterator; + private int last = -1; + private int next = 0; + private T current; + + LocalListIterator(ListIterator iterator) { + super(); + this.iterator = iterator; + } + + public boolean hasNext() { + return this.iterator.hasNext(); + } + + public T next() { + this.current = this.iterator.next(); + this.last = this.next++; + return this.current; + } + + public int nextIndex() { + return this.iterator.nextIndex(); + } + + public boolean hasPrevious() { + return this.iterator.hasPrevious(); + } + + public T previous() { + this.current = this.iterator.previous(); + this.last = --this.next; + return this.current; + } + + public int previousIndex() { + return this.iterator.previousIndex(); + } + + @SuppressWarnings("synthetic-access") + public void set(T o) { + this.iterator.set(o); + SimpleListValueModel.this.fireItemReplaced(LIST_VALUES, this.last, o, this.current); + } + + @SuppressWarnings("synthetic-access") + public void add(T o) { + this.iterator.add(o); + SimpleListValueModel.this.fireItemAdded(LIST_VALUES, this.next, o); + } + + @SuppressWarnings("synthetic-access") + public void remove() { + this.iterator.remove(); + SimpleListValueModel.this.fireItemRemoved(LIST_VALUES, this.last, this.current); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimplePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimplePropertyValueModel.java new file mode 100644 index 0000000000..844d1c693e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimplePropertyValueModel.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Implementation of {@link WritablePropertyValueModel} that simply holds on to an + * object and uses it as the value. + */ +public class SimplePropertyValueModel + extends AbstractModel + implements WritablePropertyValueModel +{ + /** The value. */ + protected T value; + + + /** + * Construct a property value model for the specified value. + */ + public SimplePropertyValueModel(T value) { + super(); + this.value = value; + } + + /** + * Construct a property value model with a starting value of null. + */ + public SimplePropertyValueModel() { + this(null); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new SingleAspectChangeSupport(this, PropertyChangeListener.class, VALUE); + } + + + public T getValue() { + return this.value; + } + + public void setValue(T value) { + T old = this.value; + this.value = value; + this.firePropertyChanged(VALUE, old, value); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.value); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelAdapter.java new file mode 100644 index 0000000000..0f1ff4cf55 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelAdapter.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.Range; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * An adapter that allows us to make a {@link CollectionValueModel} + * (or {@link ListValueModel}) behave like a {@link ListValueModel} + * that keeps its contents sorted and notifies listeners appropriately. + *

+ * The {@link Comparator} can be changed at any time; allowing the same + * adapter to be used with different sort criteria (e.g. when the user + * wants to sort a list of files first by name, then by date, then by size). + *

+ * NB: Since we only listen to the wrapped collection when we have + * listeners ourselves and we can only stay in synch with the wrapped + * collection while we are listening to it, results to various methods + * (e.g. {@link #size()}, {@link #get(int)}) will be + * unpredictable whenever + * we do not have any listeners. This should not be too painful since, + * most likely, client objects will also be listeners. + * + * @see SortedListValueModelWrapper + */ +public class SortedListValueModelAdapter + extends CollectionListValueModelAdapter +{ + /** + * A comparator used for sorting the elements; + * if it is null, we use "natural ordering". + */ + protected Comparator comparator; + + + // ********** constructors ********** + + /** + * Wrap the specified collection value model and sort its contents + * using the specified comparator. + */ + public SortedListValueModelAdapter(CollectionValueModel collectionHolder, Comparator comparator) { + super(collectionHolder); + this.comparator = comparator; + } + + /** + * Wrap the specified collection value model and sort its contents + * based on the elements' "natural ordering". + */ + public SortedListValueModelAdapter(CollectionValueModel collectionHolder) { + this(collectionHolder, null); + } + + + // ********** accessors ********** + + public void setComparator(Comparator comparator) { + this.comparator = comparator; + this.sortList(); + } + + + // ********** behavior ********** + + /** + * Sort the internal list before the superclass + * sends out change notification. + */ + @Override + protected void buildList(int size) { + super.buildList(size); + Collections.sort(this.list, this.comparator); + } + + /** + * Sort the list after adding the items. + */ + @Override + protected void itemsAdded(CollectionAddEvent event) { + @SuppressWarnings("unchecked") + ArrayList newList = (ArrayList) this.list.clone(); + newList.ensureCapacity(newList.size() + event.getItemsSize()); + CollectionTools.addAll(newList, this.getItems(event)); + Collections.sort(newList, this.comparator); + this.synchronizeList(newList, this.list, LIST_VALUES); + } + + @Override + protected Iterable buildSyncList() { + return CollectionTools.sortedSet(this.collectionHolder, this.comparator, this.collectionHolder.size()); + } + + /** + * sort the list and notify our listeners, if necessary; + */ + protected void sortList() { + // save the unsorted state of the sorted list so we can minimize the number of "replaced" items + @SuppressWarnings("unchecked") + ArrayList unsortedList = (ArrayList) this.list.clone(); + Collections.sort(this.list, this.comparator); + Range diffRange = CollectionTools.identityDiffRange(unsortedList, this.list); + if (diffRange.size > 0) { + List unsortedItems = unsortedList.subList(diffRange.start, diffRange.end + 1); + List sortedItems = this.list.subList(diffRange.start, diffRange.end + 1); + this.fireItemsReplaced(LIST_VALUES, diffRange.start, sortedItems, unsortedItems); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelWrapper.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelWrapper.java new file mode 100644 index 0000000000..8514f37266 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelWrapper.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.Range; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * A wrapper that keeps the contents of a {@link ListValueModel} sorted + * and notifies listeners appropriately. + *

+ * The {@link Comparator} can be changed at any time; allowing the same + * adapter to be used with different sort criteria (e.g. when the user + * wants to sort a list of files first by name, then by date, then by size). + *

+ * NB: Since we only listen to the wrapped collection when we have + * listeners ourselves and we can only stay in synch with the wrapped + * collection while we are listening to it, results to various methods + * (e.g. {@link #size()}, {@link #get(int)}) will be unpredictable whenever + * we do not have any listeners. This should not be too painful since, + * most likely, client objects will also be listeners. + * + * @see SortedListValueModelAdapter + */ +public class SortedListValueModelWrapper + extends ListValueModelWrapper + implements ListValueModel +{ + /** + * A comparator used for sorting the elements; + * if it is null, we use "natural ordering". + */ + protected Comparator comparator; + + /** + * Our internal list, which holds the same elements as + * the wrapped list, but keeps them sorted. + */ + // we declare this an ArrayList so we can use #clone() and #ensureCapacity(int) + protected final ArrayList sortedList; + + + // ********** constructors ********** + + /** + * Wrap the specified list value model and sort its contents + * using the specified comparator. + */ + public SortedListValueModelWrapper(ListValueModel listHolder, Comparator comparator) { + super(listHolder); + this.comparator = comparator; + this.sortedList = new ArrayList(listHolder.size()); + // postpone building the sorted list and listening to the wrapped list + // until we have listeners ourselves... + } + + /** + * Wrap the specified list value model and sort its contents + * based on the elements' "natural ordering". + */ + public SortedListValueModelWrapper(ListValueModel listHolder) { + this(listHolder, null); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.sortedList); + } + + public E get(int index) { + return this.sortedList.get(index); + } + + public int size() { + return this.sortedList.size(); + } + + public Object[] toArray() { + return this.sortedList.toArray(); + } + + + // ********** accessors ********** + + public void setComparator(Comparator comparator) { + this.comparator = comparator; + this.sortList(); + } + + + // ********** behavior ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch the sorted list *after* we start listening to the wrapped list holder, + // since its value might change when a listener is added + this.buildSortedList(); + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // clear out the sorted list when we are not listening to the wrapped list holder + this.sortedList.clear(); + } + + protected void buildSortedList() { + // if the new list is empty, do nothing + int size = this.listHolder.size(); + if (size != 0) { + this.buildSortedList(size); + } + } + + protected void buildSortedList(int size) { + this.sortedList.ensureCapacity(size); + for (E each : this.listHolder) { + this.sortedList.add(each); + } + Collections.sort(this.sortedList, this.comparator); + } + + + // ********** list change support ********** + + /** + * Items were added to the wrapped list. + */ + @Override + protected void itemsAdded(ListAddEvent event) { + // first add the items and notify our listeners... + this.addItemsToList(this.getItems(event), this.sortedList, LIST_VALUES); + // ...then sort the list and notify our listeners + this.sortList(); + } + + /** + * Items were removed from the wrapped list. + */ + @Override + protected void itemsRemoved(ListRemoveEvent event) { + this.removeItemsFromList(this.getItems(event), this.sortedList, LIST_VALUES); + // no sorting needed + } + + /** + * Items were replaced in the wrapped list. + */ + @Override + protected void itemsReplaced(ListReplaceEvent event) { + // first remove the old items and notify our listeners... + this.removeItemsFromList(this.getOldItems(event), this.sortedList, LIST_VALUES); + // then add the new items and notify our listeners... + this.addItemsToList(this.getNewItems(event), this.sortedList, LIST_VALUES); + // ...then sort the list and notify our listeners + this.sortList(); + } + + /** + * Items were moved in the wrapped list. + */ + @Override + protected void itemsMoved(ListMoveEvent event) { + // do nothing - sort order should remain unchanged + } + + /** + * The wrapped list was cleared. + */ + @Override + protected void listCleared(ListClearEvent event) { + this.clearList(this.sortedList, LIST_VALUES); + } + + /** + * The wrapped list has changed in some dramatic fashion. + * Rebuild our sorted list and notify our listeners. + */ + @Override + protected void listChanged(ListChangeEvent event) { + int size = this.listHolder.size(); + if (size == 0) { + if (this.sortedList.isEmpty()) { + // no change + } else { + this.clearList(this.sortedList, LIST_VALUES); + } + } else { + if (this.sortedList.isEmpty()) { + this.buildSortedList(size); + this.fireItemsAdded(LIST_VALUES, 0, this.sortedList); + } else { + this.sortedList.clear(); + this.buildSortedList(size); + this.fireListChanged(LIST_VALUES, this.sortedList); + } + } + } + + /** + * sort the sorted list and notify our listeners, if necessary; + */ + protected void sortList() { + // save the unsorted state of the sorted list so we can minimize the number of "replaced" items + @SuppressWarnings("unchecked") + ArrayList unsortedList = (ArrayList) this.sortedList.clone(); + Collections.sort(this.sortedList, this.comparator); + Range diffRange = CollectionTools.identityDiffRange(unsortedList, this.sortedList); + if (diffRange.size > 0) { + List unsortedItems = unsortedList.subList(diffRange.start, diffRange.end + 1); + List sortedItems = this.sortedList.subList(diffRange.start, diffRange.end + 1); + this.fireItemsReplaced(LIST_VALUES, diffRange.start, sortedItems, unsortedItems); + } + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.sortedList); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StatePropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StatePropertyValueModelAdapter.java new file mode 100644 index 0000000000..6d179ffa63 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StatePropertyValueModelAdapter.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; + +/** + * This abstract class provides the infrastructure needed to wrap + * a model, "lazily" listen to it, and convert + * its state change notifications into property value model change + * notifications. + *

+ * Subclasses must implement:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current model + *

+ * Subclasses might want to override the following methods + * to improve performance (by not recalculating the value, if possible):
    + *
  • {@link #stateChanged(StateChangeEvent event)} + *
+ */ +public abstract class StatePropertyValueModelAdapter + extends AbstractPropertyValueModelAdapter +{ + /** The wrapped model. */ + protected final Model model; + + /** A listener that allows us to synch with changes to the wrapped model. */ + protected final StateChangeListener stateChangeListener; + + + // ********** constructor/initialization ********** + + /** + * Construct a property value model with the specified wrapped model. + */ + protected StatePropertyValueModelAdapter(Model model) { + super(); + this.model = model; + this.stateChangeListener = this.buildStateChangeListener(); + } + + protected StateChangeListener buildStateChangeListener() { + return new StateChangeListener() { + public void stateChanged(StateChangeEvent event) { + StatePropertyValueModelAdapter.this.stateChanged(event); + } + @Override + public String toString() { + return "state change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the model. + */ + @Override + protected void engageModel_() { + this.model.addStateChangeListener(this.stateChangeListener); + } + + /** + * Stop listening to the model. + */ + @Override + protected void disengageModel_() { + this.model.removeStateChangeListener(this.stateChangeListener); + } + + + // ********** state change support ********** + + /** + * The model's state changed; + * propagate the change notification appropriately. + */ + protected void stateChanged(@SuppressWarnings("unused") StateChangeEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticCollectionValueModel.java new file mode 100644 index 0000000000..854528690a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticCollectionValueModel.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; + +/** + * Implementation of {@link CollectionValueModel} that can be used for + * returning an iterator on a static collection, but still allows listeners to be added. + * Listeners will never be notified of any changes, because there should be none. + */ +public class StaticCollectionValueModel + extends AbstractModel + implements CollectionValueModel +{ + /** The elements. */ + protected final Object[] elements; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a static collection value model for the specified array. + */ + public StaticCollectionValueModel(E... elements) { + super(); + this.elements = elements.clone(); + } + + /** + * Construct a static collection value model for the specified elements. + */ + public StaticCollectionValueModel(Iterable elements) { + super(); + this.elements = ArrayTools.array(elements); + } + + + // ********** CollectionValueModel implementation ********** + + public int size() { + return this.elements.length; + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + // we can cast here since our constructors require the elements to be + // of type E and ArrayIterator is read-only + return (Iterator) new ArrayIterator(this.elements); + } + + + // ********** Object overrides ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(Arrays.toString(this.elements)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticListValueModel.java new file mode 100644 index 0000000000..c08b6f42e8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticListValueModel.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.ArrayTools; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayListIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * Implementation of {@link ListValueModel} that can be used for + * returning a list iterator on a static list, but still allows listeners to be added. + * Listeners will never be notified of any changes, because there should be none. + */ +public class StaticListValueModel + extends AbstractModel + implements ListValueModel +{ + /** The elements. */ + protected final Object[] elements; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a static list value model for the specified elements. + */ + public StaticListValueModel(E... elements) { + super(); + this.elements = elements.clone(); + } + + /** + * Construct a static list value model for the specified elements. + */ + public StaticListValueModel(Iterable elements) { + super(); + this.elements = ArrayTools.array(elements); + } + + + // ********** ListValueModel implementation ********** + + @SuppressWarnings("unchecked") + public Iterator iterator() { + // we can cast here since our constructors require the elements to be + // of type E and ArrayIterator is read-only + return (Iterator) new ArrayIterator(this.elements); + } + + @SuppressWarnings("unchecked") + public ListIterator listIterator() { + // we can cast here since our constructors require the elements to be + // of type E and ArrayListIterator is read-only + return (ListIterator) new ArrayListIterator(this.elements); + } + + public int size() { + return this.elements.length; + } + + @SuppressWarnings("unchecked") + public E get(int index) { + // we can cast here since our constructors require the elements to be + // of type E + return (E) this.elements[index]; + } + + public Object[] toArray() { + return this.elements.clone(); + } + + + // ********** Object overrides ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(Arrays.toString(this.elements)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticPropertyValueModel.java new file mode 100644 index 0000000000..678f5aaefd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticPropertyValueModel.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * Implementation of {@link PropertyValueModel} that can be used for + * returning a static value, but still allows listeners to be added. + * Listeners will never be notified of any changes, because there should be none. + */ +public class StaticPropertyValueModel + extends AbstractModel + implements PropertyValueModel +{ + /** The value. */ + protected final T value; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a static property value model for the specified value. + */ + public StaticPropertyValueModel(T value) { + super(); + this.value = value; + } + + + // ********** PropertyValueModel implementation ********** + + public T getValue() { + return this.value; + } + + + // ********** Object overrides ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.value); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticTreeValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticTreeValueModel.java new file mode 100644 index 0000000000..576e608985 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticTreeValueModel.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.model.value.TreeValueModel; + +/** + * Implementation of {@link TreeValueModel} that can be used for + * returning an iterator on a static tree, but still allows listeners to be added. + * Listeners will never be notified of any changes, because there should be none. + */ +public class StaticTreeValueModel + extends AbstractModel + implements TreeValueModel +{ + /** The tree's nodes. */ + protected final Iterable nodes; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a read-only tree value model for the specified nodes. + */ + public StaticTreeValueModel(Iterable nodes) { + super(); + if (nodes == null) { + throw new NullPointerException(); + } + this.nodes = nodes; + } + + // ********** TreeValueModel implementation ********** + + public Iterator nodes() { + return new ReadOnlyIterator(this.nodes.iterator()); + } + + + // ********** Object overrides ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.nodes); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationListValueModel.java new file mode 100644 index 0000000000..4a241b3004 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationListValueModel.java @@ -0,0 +1,309 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.internal.iterators.ReadOnlyListIterator; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * An adapter that allows us to transform a {@link ListValueModel} + * (or adapted {@link CollectionValueModel}) into a read-only {@link ListValueModel} + * whose items are tranformations of the items in the wrapped + * {@link ListValueModel}. It will keep its contents in synch with + * the contents of the wrapped {@link ListValueModel} and notifies its + * listeners of any changes. + *

+ * The {@link Transformer} can be changed at any time; allowing the same + * adapter to be used with different transformations. + *

+ * As an alternative to building a {@link Transformer}, + * a subclass of TransformationListValueModelAdapter can + * either override {@link #transformItem_(Object)} or, + * if something other than null should be returned when the wrapped item + * is null, override {@link #transformItem(Object)}. + *

+ * NB: Since we only listen to the wrapped list when we have + * listeners ourselves and we can only stay in synch with the wrapped + * list while we are listening to it, results to various methods + * (e.g. {@link #size()}, {@link #get(int)}) will be unpredictable whenever + * we do not have any listeners. This should not be too painful since, + * most likely, clients will also be listeners. + * + * @see Transformer + */ +public class TransformationListValueModel + extends ListValueModelWrapper + implements ListValueModel +{ + + /** This transforms the items, unless the subclass overrides {@link #transformItem(Object)}). */ + protected Transformer transformer; + + /** The list of transformed items. */ + protected final List transformedList; + + + // ********** constructors ********** + + /** + * Construct a list value model with the specified nested + * list value model and transformer. + */ + public TransformationListValueModel(ListValueModel listHolder, Transformer transformer) { + super(listHolder); + this.transformer = transformer; + this.transformedList = new ArrayList(); + } + + /** + * Construct a list value model with the specified nested + * list value model and the default transformer. + * Use this constructor if you want to override + * {@link #transformItem_(Object)} or {@link #transformItem(Object)} + * method instead of building a {@link Transformer}. + */ + public TransformationListValueModel(ListValueModel listHolder) { + super(listHolder); + this.transformer = this.buildTransformer(); + this.transformedList = new ArrayList(); + } + + /** + * Construct a list value model with the specified nested + * collection value model and transformer. + */ + public TransformationListValueModel(CollectionValueModel collectionHolder, Transformer transformer) { + this(new CollectionListValueModelAdapter(collectionHolder), transformer); + } + + /** + * Construct a list value model with the specified nested + * collection value model and the default transformer. + * Use this constructor if you want to override + * {@link #transformItem_(Object)} or {@link #transformItem(Object)} + * method instead of building a {@link Transformer}. + */ + public TransformationListValueModel(CollectionValueModel collectionHolder) { + this(new CollectionListValueModelAdapter(collectionHolder)); + } + + protected Transformer buildTransformer() { + return new DefaultTransformer(); + } + + + // ********** ListValueModel implementation ********** + + public Iterator iterator() { + return this.listIterator(); + } + + public ListIterator listIterator() { + return new ReadOnlyListIterator(this.transformedList); + } + + public E2 get(int index) { + return this.transformedList.get(index); + } + + public int size() { + return this.transformedList.size(); + } + + public Object[] toArray() { + return this.transformedList.toArray(); + } + + // ********** behavior ********** + + @Override + protected void engageModel() { + super.engageModel(); + // synch the transformed list *after* we start listening to the list holder, + // since its value might change when a listener is added + this.transformedList.addAll(this.transformItems(this.listHolder)); + } + + @Override + protected void disengageModel() { + super.disengageModel(); + // clear out the list when we are not listening to the collection holder + this.transformedList.clear(); + } + + /** + * Transform the items in the specified list value model. + */ + protected List transformItems(ListValueModel lvm) { + return this.transformItems(lvm, lvm.size()); + } + + /** + * Transform the items associated with the specified event. + */ + protected List transformItems(ListAddEvent event) { + return this.transformItems(this.getItems(event), event.getItemsSize()); + } + + /** + * Transform the items associated with the specified event. + */ + protected List transformItems(ListRemoveEvent event) { + return this.transformItems(this.getItems(event), event.getItemsSize()); + } + + /** + * Transform the new items associated with the specified event. + */ + protected List transformNewItems(ListReplaceEvent event) { + return this.transformItems(this.getNewItems(event), event.getItemsSize()); + } + + /** + * Transform the old items associated with the specified event. + */ + protected List transformOldItems(ListReplaceEvent event) { + return this.transformItems(this.getOldItems(event), event.getItemsSize()); + } + + /** + * Transform the specified items. + */ + protected List transformItems(Iterable items, int size) { + List result = new ArrayList(size); + for (E1 item : items) { + result.add(this.transformItem(item)); + } + return result; + } + + /** + * Transform the specified item. + */ + protected E2 transformItem(E1 item) { + return this.transformer.transform(item); + } + + /** + * Transform the specified, non-null, item and return the result. + */ + protected E2 transformItem_(@SuppressWarnings("unused") E1 item) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * Change the transformer and rebuild the collection. + */ + public void setTransformer(Transformer transformer) { + this.transformer = transformer; + this.rebuildTransformedList(); + } + + /** + * Synchronize our cache with the wrapped collection. + */ + protected void rebuildTransformedList() { + this.synchronizeList(this.transformItems(this.listHolder), this.transformedList, LIST_VALUES); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.transformedList); + } + + + // ********** list change support ********** + + /** + * Items were added to the wrapped list holder. + * Transform them, add them to our transformation list, + * and notify our listeners. + */ + @Override + protected void itemsAdded(ListAddEvent event) { + this.addItemsToList(event.getIndex(), this.transformItems(event), this.transformedList, LIST_VALUES); + } + + /** + * Items were removed from the wrapped list holder. + * Remove the corresponding items from our transformation list + * and notify our listeners. + */ + @Override + protected void itemsRemoved(ListRemoveEvent event) { + this.removeItemsFromList(event.getIndex(), event.getItemsSize(), this.transformedList, LIST_VALUES); + } + + /** + * Items were replaced in the wrapped list holder. + * Replace the corresponding items in our transformation list + * and notify our listeners. + */ + @Override + protected void itemsReplaced(ListReplaceEvent event) { + this.setItemsInList(event.getIndex(), this.transformNewItems(event), this.transformedList, LIST_VALUES); + } + + /** + * Items were moved in the wrapped list holder. + * Move the corresponding items in our transformation list + * and notify our listeners. + */ + @Override + protected void itemsMoved(ListMoveEvent event) { + this.moveItemsInList(event.getTargetIndex(), event.getSourceIndex(), event.getLength(), this.transformedList, LIST_VALUES); + } + + /** + * The wrapped list holder was cleared. + * Clear our transformation list and notify our listeners. + */ + @Override + protected void listCleared(ListClearEvent event) { + this.clearList(this.transformedList, LIST_VALUES); + } + + /** + * The wrapped list holder has changed in some dramatic fashion. + * Rebuild our transformation list and notify our listeners. + */ + @Override + protected void listChanged(ListChangeEvent event) { + this.rebuildTransformedList(); + } + + + // ********** default transformer ********** + + /** + * The default transformer will return null if the wrapped item is null. + * If the wrapped item is not null, it is transformed by a subclass + * implementation of {@link TransformationListValueModel#transformItem_(Object)}. + */ + protected class DefaultTransformer implements Transformer { + public E2 transform(E1 item) { + return (item == null) ? null : TransformationListValueModel.this.transformItem_(item); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationPropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationPropertyValueModel.java new file mode 100644 index 0000000000..6c7dadb7c4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationPropertyValueModel.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.Transformer; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * A TransformationPropertyValueModel wraps another + * {@link PropertyValueModel} and uses a {@link Transformer} + * to transform the wrapped value before it is returned by {@link #getValue()}. + *

+ * As an alternative to building a {@link Transformer}, + * a subclass of TransformationPropertyValueModel can + * either override {@link #transform_(Object)} or, + * if something other than null should be returned when the wrapped value + * is null, override {@link #transform(Object)}. + * + * @see Transformer + */ +public class TransformationPropertyValueModel + extends PropertyValueModelWrapper + implements PropertyValueModel +{ + protected final Transformer transformer; + + + // ********** constructors/initialization ********** + + /** + * Construct a property value model with the specified nested + * property value model and the default transformer. + * Use this constructor if you want to override + * {@link #transform_(Object)} or {@link #transform(Object)} + * method instead of building a {@link Transformer}. + */ + public TransformationPropertyValueModel(PropertyValueModel valueHolder) { + super(valueHolder); + this.transformer = this.buildTransformer(); + } + + /** + * Construct a property value model with the specified nested + * property value model and transformer. + */ + public TransformationPropertyValueModel(PropertyValueModel valueHolder, Transformer transformer) { + super(valueHolder); + this.transformer = transformer; + } + + protected Transformer buildTransformer() { + return new DefaultTransformer(); + } + + + // ********** PropertyValueModel implementation ********** + + public T2 getValue() { + // transform the object returned by the nested value model before returning it + return this.transform(this.valueHolder.getValue()); + } + + + // ********** PropertyValueModelWrapper implementation ********** + + @Override + protected void valueChanged(PropertyChangeEvent event) { + // transform the values before propagating the change event + @SuppressWarnings("unchecked") + T1 eventOldValue = (T1) event.getOldValue(); + Object oldValue = this.transformOld(eventOldValue); + @SuppressWarnings("unchecked") + T1 eventNewValue = (T1) event.getNewValue(); + Object newValue = this.transformNew(eventNewValue); + this.firePropertyChanged(VALUE, oldValue, newValue); + } + + + // ********** behavior ********** + + /** + * Transform the specified value and return the result. + * This is called by + * {@link #getValue()}, + * {@link #transformOld(Object)}, and + * {@link #transformNew(Object)}. + */ + protected T2 transform(T1 value) { + return this.transformer.transform(value); + } + + /** + * Transform the specified, non-null, value and return the result. + */ + protected T2 transform_(@SuppressWarnings("unused") T1 value) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + /** + * Transform the specified old value and return the result. + * By default, call {@link #transform(Object)}. + * This is called by {@link #valueChanged(PropertyChangeEvent)}. + */ + protected T2 transformOld(T1 value) { + return this.transform(value); + } + + /** + * Transform the specified new value and return the result. + * By default, call {@link #transform(Object)}. + * This is called by {@link #valueChanged(PropertyChangeEvent)}. + */ + protected T2 transformNew(T1 value) { + return this.transform(value); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + + + // ********** default transformer ********** + + /** + * The default transformer will return null if the wrapped value is null. + * If the wrapped value is not null, it is transformed by a subclass + * implementation of {@link TransformationPropertyValueModel#transform_(Object)}. + */ + protected class DefaultTransformer implements Transformer { + public T2 transform(T1 value) { + return (value == null) ? null : TransformationPropertyValueModel.this.transform_(value); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationWritablePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationWritablePropertyValueModel.java new file mode 100644 index 0000000000..1d42d4c54e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationWritablePropertyValueModel.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.BidiTransformer; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * A TransformationWritablePropertyValueModel wraps another + * {@link WritablePropertyValueModel} and uses a {@link BidiTransformer} + * to:

    + *
  • transform the wrapped value before it is returned by {@link #getValue()} + *
  • "reverse-transform" the new value that comes in via + * {@link #setValue(Object)} + *
+ * As an alternative to building a {@link BidiTransformer}, + * a subclass of TransformationWritablePropertyValueModel can + * override {@link #transform_(Object)} and {@link #reverseTransform_(Object)}; + * or, if something other than null should be returned when the wrapped value + * is null or the new value is null, override {@link #transform(Object)} + * and {@link #reverseTransform(Object)}. + */ +public class TransformationWritablePropertyValueModel + extends TransformationPropertyValueModel + implements WritablePropertyValueModel +{ + + // ********** constructors/initialization ********** + + /** + * Construct a writable property value model with the specified nested + * writable property value model and the default bidi transformer. + * Use this constructor if you want to override the + * {@link #transform_(Object)} and {@link #reverseTransform_(Object)} + * (or {@link #transform(Object)} and {@link #reverseTransform(Object)}) + * methods instead of building a {@link BidiTransformer}. + */ + public TransformationWritablePropertyValueModel(WritablePropertyValueModel valueHolder) { + super(valueHolder); + } + + /** + * Construct a writable property value model with the specified nested + * writable property value model and bidi transformer. + */ + public TransformationWritablePropertyValueModel(WritablePropertyValueModel valueHolder, BidiTransformer transformer) { + super(valueHolder, transformer); + } + + @Override + protected BidiTransformer buildTransformer() { + return new DefaultBidiTransformer(); + } + + + // ********** WritablePropertyValueModel implementation ********** + + public void setValue(T2 value) { + // "reverse-transform" the object before passing it to the the nested value model + this.getValueHolder().setValue(this.reverseTransform(value)); + } + + + // ********** behavior ********** + + /** + * "Reverse-transform" the specified value and return the result. + * This is called by {@link #setValue(Object)}. + */ + protected T1 reverseTransform(T2 value) { + return this.getTransformer().reverseTransform(value); + } + + /** + * "Reverse-transform" the specified, non-null, + * value and return the result. + */ + protected T1 reverseTransform_(@SuppressWarnings("unused") T2 value) { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + + + // ********** queries ********** + + /** + * Our constructors accept only a {@link WritablePropertyValueModel}, + * so this cast should be safe. + */ + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + + /** + * Our constructors accept only a {@link BidiTransformer}, + * so this cast should be safe. + */ + protected BidiTransformer getTransformer() { + return (BidiTransformer) this.transformer; + } + + + // ********** default bidi transformer ********** + + /** + * The default bidi transformer will return null if the + * wrapped value is null. + * If the wrapped value is not null, it is transformed by a subclass + * implementation of {@link #transform_(Object)}. + * The default bidi transformer will also return null + * if the new value is null. + * If the new value is not null, it is reverse-transformed + * by a subclass implementation of {@link #reverseTransform_(Object)}. + */ + protected class DefaultBidiTransformer + extends DefaultTransformer + implements BidiTransformer + { + public T1 reverseTransform(T2 value) { + return (value == null) ? null : TransformationWritablePropertyValueModel.this.reverseTransform_(value); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreeAspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreeAspectAdapter.java new file mode 100644 index 0000000000..178d1ed2bb --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreeAspectAdapter.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import java.util.Collection; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This extension of {@link AspectTreeValueModelAdapter} provides + * basic tree change support. + * This converts a set of one or more trees into + * a single {@link #NODES} tree. + *

+ * The typical subclass will override the following methods (see the descriptions + * in {@link AspectTreeValueModelAdapter}):

    + *
  • {@link #nodes_()} + *
  • {@link #nodes()} + *
+ */ +public abstract class TreeAspectAdapter + extends AspectTreeValueModelAdapter +{ + /** + * The name of the subject's trees that we use for the value. + */ + protected final String[] treeNames; + protected static final String[] EMPTY_TREE_NAMES = new String[0]; + + /** A listener that listens to the subject's tree aspects. */ + protected final TreeChangeListener treeChangeListener; + + + // ********** constructors ********** + + /** + * Construct a tree aspect adapter for the specified subject + * and tree. + */ + protected TreeAspectAdapter(String treeName, S subject) { + this(new String[] {treeName}, subject); + } + + /** + * Construct a tree aspect adapter for the specified subject + * and trees. + */ + protected TreeAspectAdapter(String[] treeNames, S subject) { + this(new StaticPropertyValueModel(subject), treeNames); + } + + /** + * Construct a tree aspect adapter for the specified subject holder + * and trees. + */ + protected TreeAspectAdapter(PropertyValueModel subjectHolder, String... treeNames) { + super(subjectHolder); + this.treeNames = treeNames; + this.treeChangeListener = this.buildTreeChangeListener(); + } + + /** + * Construct a tree aspect adapter for the specified subject holder + * and trees. + */ + protected TreeAspectAdapter(PropertyValueModel subjectHolder, Collection treeNames) { + this(subjectHolder, treeNames.toArray(new String[treeNames.size()])); + } + + /** + * Construct a tree aspect adapter for an "unchanging" tree in + * the specified subject. This is useful for a tree aspect that does not + * change for a particular subject; but the subject will change, resulting in + * a new tree. + */ + protected TreeAspectAdapter(PropertyValueModel subjectHolder) { + this(subjectHolder, EMPTY_TREE_NAMES); + } + + + // ********** initialization ********** + + protected TreeChangeListener buildTreeChangeListener() { + // transform the subject's tree change events into VALUE tree change events + return new TreeChangeListener() { + public void nodeAdded(TreeAddEvent event) { + TreeAspectAdapter.this.nodeAdded(event); + } + public void nodeRemoved(TreeRemoveEvent event) { + TreeAspectAdapter.this.nodeRemoved(event); + } + public void treeCleared(TreeClearEvent event) { + TreeAspectAdapter.this.treeCleared(event); + } + public void treeChanged(TreeChangeEvent event) { + TreeAspectAdapter.this.treeChanged(event); + } + @Override + public String toString() { + return "tree change listener: " + Arrays.asList(TreeAspectAdapter.this.treeNames); //$NON-NLS-1$ + } + }; + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected void engageSubject_() { + for (String treeName : this.treeNames) { + ((Model) this.subject).addTreeChangeListener(treeName, this.treeChangeListener); + } + } + + @Override + protected void disengageSubject_() { + for (String treeName : this.treeNames) { + ((Model) this.subject).removeTreeChangeListener(treeName, this.treeChangeListener); + } + } + + + // ********** behavior ********** + + protected void nodeAdded(TreeAddEvent event) { + this.fireNodeAdded(event.clone(this, NODES)); + } + + protected void nodeRemoved(TreeRemoveEvent event) { + this.fireNodeRemoved(event.clone(this, NODES)); + } + + protected void treeCleared(TreeClearEvent event) { + this.fireTreeCleared(event.clone(this, NODES)); + } + + protected void treeChanged(TreeChangeEvent event) { + this.fireTreeChanged(event.clone(this, NODES)); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreePropertyValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreePropertyValueModelAdapter.java new file mode 100644 index 0000000000..66258c2108 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreePropertyValueModelAdapter.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; +import org.eclipse.jpt.common.utility.model.value.TreeValueModel; + +/** + * This abstract class provides the infrastructure needed to wrap + * a tree value model, "lazily" listen to it, and convert + * its change notifications into property value model change + * notifications. + *

+ * Subclasses must override:

    + *
  • {@link #buildValue()}

    + * to return the current property value, as derived from the + * current collection value + *

+ * Subclasses might want to override the following methods + * to improve performance (by not recalculating the value, if possible):
    + *
  • {@link #nodeAdded(TreeChangeEvent event)} + *
  • {@link #nodeRemoved(TreeChangeEvent event)} + *
  • {@link #treeCleared(TreeChangeEvent event)} + *
  • {@link #treeChanged(TreeChangeEvent event)} + *
+ */ +public abstract class TreePropertyValueModelAdapter + extends AbstractPropertyValueModelAdapter +{ + /** The wrapped tree value model. */ + protected final TreeValueModel treeHolder; + + /** A listener that allows us to synch with changes to the wrapped tree holder. */ + protected final TreeChangeListener treeChangeListener; + + + // ********** constructor/initialization ********** + + /** + * Construct a property value model with the specified wrapped + * tree value model. + */ + protected TreePropertyValueModelAdapter(TreeValueModel treeHolder) { + super(); + this.treeHolder = treeHolder; + this.treeChangeListener = this.buildTreeChangeListener(); + } + + protected TreeChangeListener buildTreeChangeListener() { + return new TreeChangeListener() { + public void nodeAdded(TreeAddEvent event) { + TreePropertyValueModelAdapter.this.nodeAdded(event); + } + public void nodeRemoved(TreeRemoveEvent event) { + TreePropertyValueModelAdapter.this.nodeRemoved(event); + } + public void treeCleared(TreeClearEvent event) { + TreePropertyValueModelAdapter.this.treeCleared(event); + } + public void treeChanged(TreeChangeEvent event) { + TreePropertyValueModelAdapter.this.treeChanged(event); + } + @Override + public String toString() { + return "tree change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** behavior ********** + + /** + * Start listening to the tree holder. + */ + @Override + protected void engageModel_() { + this.treeHolder.addTreeChangeListener(TreeValueModel.NODES, this.treeChangeListener); + } + + /** + * Stop listening to the tree holder. + */ + @Override + protected void disengageModel_() { + this.treeHolder.removeTreeChangeListener(TreeValueModel.NODES, this.treeChangeListener); + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.treeHolder); + } + + + // ********** state change support ********** + + /** + * Nodes were added to the wrapped tree holder; + * propagate the change notification appropriately. + */ + protected void nodeAdded(@SuppressWarnings("unused") TreeAddEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * Nodes were removed from the wrapped tree holder; + * propagate the change notification appropriately. + */ + protected void nodeRemoved(@SuppressWarnings("unused") TreeRemoveEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The wrapped tree holder was cleared; + * propagate the change notification appropriately. + */ + protected void treeCleared(@SuppressWarnings("unused") TreeClearEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + + /** + * The wrapped tree holder changed; + * propagate the change notification appropriately. + */ + protected void treeChanged(@SuppressWarnings("unused") TreeChangeEvent event) { + // by default, simply recalculate the value and fire an event + this.propertyChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueAspectAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueAspectAdapter.java new file mode 100644 index 0000000000..5876d5e444 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueAspectAdapter.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Abstract class that provides support for wrapping a {@link WritablePropertyValueModel} + * and listening for changes to aspects of the value contained + * by the {@link WritablePropertyValueModel}. Changes to the {@link WritablePropertyValueModel}'s + * value are also monitored. + *

+ * This is useful if you have a value that may change, but whose aspects can also + * change in a fashion that might be of interest to the client. + *

+ * NB: Clients will need to listen for two different change notifications: + * a property change event will be be fired when the value changes; + * a state change event will be fired when an aspect of the value changes. + *

+ * Subclasses need to override two methods:

    + *
  • {@link #engageValue_()}

    + * begin listening to the appropriate aspect of the value and call + * {@link #valueAspectChanged()} whenever the aspect changes + * (this will fire a state change event) + *

  • {@link #disengageValue_()}

    + * stop listening to the appropriate aspect of the value + *

+ */ +public abstract class ValueAspectAdapter + extends PropertyValueModelWrapper + implements WritablePropertyValueModel +{ + /** Cache the value so we can disengage. Null until we have a listener*/ + protected V value; + + + // ********** constructors/initialization ********** + + /** + * Constructor - the value holder is required. + */ + protected ValueAspectAdapter(WritablePropertyValueModel valueHolder) { + super(valueHolder); + this.value = null; + } + + /** + * Override to allow both property and state change listeners. + */ + @Override + protected ChangeSupport buildChangeSupport() { + return new ChangeSupport(this); + } + + + // ********** PropertyValueModel implementation ********** + + public V getValue() { + return this.value; + } + + + // ********** WritablePropertyValueModel implementation ********** + + public void setValue(V value) { + this.getValueHolder().setValue(value); + } + + + // ********** PropertyValueModelWrapper implementation ********** + + @Override + protected void valueChanged(PropertyChangeEvent event) { + this.disengageValue(); + this.engageValue(); + this.firePropertyChanged(event.clone(this)); + } + + + // ********** extend change support ********** + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public synchronized void addStateChangeListener(StateChangeListener listener) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addStateChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public synchronized void removeStateChangeListener(StateChangeListener listener) { + super.removeStateChangeListener(listener); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + + // ********** AbstractPropertyValueModel overrides ********** + + /** + * Extend to check for state change listeners. + */ + @Override + protected boolean hasListeners() { + return this.hasAnyStateChangeListeners() || super.hasListeners(); + } + + + // ********** PropertyValueModelWrapper overrides ********** + + /** + * Extend to engage an aspect of the value model's value. + */ + @Override + protected void engageModel() { + super.engageModel(); + this.engageValue(); + } + + /** + * Extend to disengage an aspect of the value model's value. + */ + @Override + protected void disengageModel() { + this.disengageValue(); + super.disengageModel(); + } + + + // ********** behavior ********** + + /** + * Start listening to an aspect of the current value. + */ + protected void engageValue() { + this.value = this.valueHolder.getValue(); + if (this.value != null) { + this.engageValue_(); + } + } + + /** + * Start listening to some aspect of the current value. + * At this point we can be sure the value is not null. + */ + protected abstract void engageValue_(); + + /** + * Stop listening to an aspect of the current value. + */ + protected void disengageValue() { + if (this.value != null) { + this.disengageValue_(); + this.value = null; + } + } + + /** + * Stop listening to an aspect of the current value. + * At this point we can be sure the value is not null. + */ + protected abstract void disengageValue_(); + + /** + * Subclasses should call this method whenever the value's aspect changes. + */ + protected void valueAspectChanged() { + this.fireStateChanged(); + } + + /** + * Our constructor accepts only a {@link WritablePropertyValueModel}{@code}. + */ + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + + @Override + public void toString(StringBuilder sb) { + sb.append(this.getValue()); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueChangeAdapter.java new file mode 100644 index 0000000000..36dca8e8dd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueChangeAdapter.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.SimpleChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to all the aspects + * of the value in the wrapped value model. + */ +public class ValueChangeAdapter + extends ValueAspectAdapter +{ + /** Listener that listens to the value. */ + protected final ChangeListener valueAspectListener; + + + // ********** constructors ********** + + /** + * Construct a change adapter for the specified value. + */ + public ValueChangeAdapter(WritablePropertyValueModel valueHolder) { + super(valueHolder); + this.valueAspectListener = this.buildValueAspectListener(); + } + + + // ********** initialization ********** + + protected ChangeListener buildValueAspectListener() { + return new SimpleChangeListener() { + @Override + protected void modelChanged(ChangeEvent event) { + ValueChangeAdapter.this.valueAspectChanged(event); + } + @Override + public String toString() { + return "value change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + this.value.addChangeListener(this.valueAspectListener); + } + + @Override + protected void disengageValue_() { + this.value.removeChangeListener(this.valueAspectListener); + } + + + // ********** change events ********** + + protected void valueAspectChanged(@SuppressWarnings("unused") ChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueCollectionAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueCollectionAdapter.java new file mode 100644 index 0000000000..a06dfbb2f3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueCollectionAdapter.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to one or more collection + * aspects of the value in the wrapped value model. + */ +public class ValueCollectionAdapter + extends ValueAspectAdapter +{ + /** The names of the value's collections that we listen to. */ + protected final String[] collectionNames; + + /** Listener that listens to the value. */ + protected final CollectionChangeListener valueCollectionListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified value collections. + */ + public ValueCollectionAdapter(WritablePropertyValueModel valueHolder, String... collectionNames) { + super(valueHolder); + this.collectionNames = collectionNames; + this.valueCollectionListener = this.buildValueCollectionListener(); + } + + + // ********** initialization ********** + + protected CollectionChangeListener buildValueCollectionListener() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + ValueCollectionAdapter.this.itemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + ValueCollectionAdapter.this.itemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + ValueCollectionAdapter.this.collectionCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + ValueCollectionAdapter.this.collectionChanged(event); + } + @Override + public String toString() { + return "value collection listener: " + Arrays.asList(ValueCollectionAdapter.this.collectionNames); //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + for (String collectionName : this.collectionNames) { + this.value.addCollectionChangeListener(collectionName, this.valueCollectionListener); + } + } + + @Override + protected void disengageValue_() { + for (String collectionName : this.collectionNames) { + this.value.removeCollectionChangeListener(collectionName, this.valueCollectionListener); + } + } + + + // ********** change events ********** + + protected void itemsAdded(@SuppressWarnings("unused") CollectionAddEvent event) { + this.valueAspectChanged(); + } + + protected void itemsRemoved(@SuppressWarnings("unused") CollectionRemoveEvent event) { + this.valueAspectChanged(); + } + + protected void collectionCleared(@SuppressWarnings("unused") CollectionClearEvent event) { + this.valueAspectChanged(); + } + + protected void collectionChanged(@SuppressWarnings("unused") CollectionChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueListAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueListAdapter.java new file mode 100644 index 0000000000..ba69a17da3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueListAdapter.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to one or more list + * aspects of the value in the wrapped value model. + */ +public class ValueListAdapter + extends ValueAspectAdapter +{ + /** The names of the value's lists that we listen to. */ + protected final String[] listNames; + + /** Listener that listens to the value. */ + protected final ListChangeListener valueListListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified value lists. + */ + public ValueListAdapter(WritablePropertyValueModel valueHolder, String... listNames) { + super(valueHolder); + this.listNames = listNames; + this.valueListListener = this.buildValueListListener(); + } + + + // ********** initialization ********** + + protected ListChangeListener buildValueListListener() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ValueListAdapter.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ValueListAdapter.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ValueListAdapter.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ValueListAdapter.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ValueListAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ValueListAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "value list listener: " + Arrays.asList(ValueListAdapter.this.listNames); //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + for (String listName : this.listNames) { + this.value.addListChangeListener(listName, this.valueListListener); + } + } + + @Override + protected void disengageValue_() { + for (String listName : this.listNames) { + this.value.removeListChangeListener(listName, this.valueListListener); + } + } + + + // ********** change events ********** + + protected void itemsAdded(@SuppressWarnings("unused") ListAddEvent event) { + this.valueAspectChanged(); + } + + protected void itemsRemoved(@SuppressWarnings("unused") ListRemoveEvent event) { + this.valueAspectChanged(); + } + + protected void itemsReplaced(@SuppressWarnings("unused") ListReplaceEvent event) { + this.valueAspectChanged(); + } + + protected void itemsMoved(@SuppressWarnings("unused") ListMoveEvent event) { + this.valueAspectChanged(); + } + + protected void listCleared(@SuppressWarnings("unused") ListClearEvent event) { + this.valueAspectChanged(); + } + + protected void listChanged(@SuppressWarnings("unused") ListChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValuePropertyAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValuePropertyAdapter.java new file mode 100644 index 0000000000..cbb6be7df8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValuePropertyAdapter.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to one or more + * properties of the value in the wrapped value model. + */ +public class ValuePropertyAdapter + extends ValueAspectAdapter +{ + /** The names of the value's properties we listen to. */ + protected final String[] propertyNames; + + /** Listener that listens to the value. */ + protected final PropertyChangeListener valuePropertyListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified value properties. + */ + public ValuePropertyAdapter(WritablePropertyValueModel valueHolder, String... propertyNames) { + super(valueHolder); + this.propertyNames = propertyNames; + this.valuePropertyListener = this.buildValuePropertyListener(); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildValuePropertyListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + ValuePropertyAdapter.this.propertyChanged(event); + } + @Override + public String toString() { + return "value property listener: " + Arrays.asList(ValuePropertyAdapter.this.propertyNames); //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + for (String propertyName : this.propertyNames) { + this.value.addPropertyChangeListener(propertyName, this.valuePropertyListener); + } + } + + @Override + protected void disengageValue_() { + for (String propertyName : this.propertyNames) { + this.value.removePropertyChangeListener(propertyName, this.valuePropertyListener); + } + } + + + // ********** change events ********** + + protected void propertyChanged(@SuppressWarnings("unused") PropertyChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueStateAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueStateAdapter.java new file mode 100644 index 0000000000..ed9e6372c2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueStateAdapter.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to the + * "state" of the value in the wrapped value model. + */ +public class ValueStateAdapter + extends ValueAspectAdapter +{ + /** Listener that listens to value. */ + protected final StateChangeListener valueStateListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the value state. + */ + public ValueStateAdapter(WritablePropertyValueModel valueHolder) { + super(valueHolder); + this.valueStateListener = this.buildValueStateListener(); + } + + + // ********** initialization ********** + + protected StateChangeListener buildValueStateListener() { + return new StateChangeListener() { + public void stateChanged(StateChangeEvent event) { + ValueStateAdapter.this.stateChanged(event); + } + @Override + public String toString() { + return "value state listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + this.value.addStateChangeListener(this.valueStateListener); + } + + @Override + protected void disengageValue_() { + this.value.removeStateChangeListener(this.valueStateListener); + } + + + // ********** change events ********** + + protected void stateChanged(@SuppressWarnings("unused") StateChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueTreeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueTreeAdapter.java new file mode 100644 index 0000000000..a8ec0af59a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueTreeAdapter.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Arrays; + +import org.eclipse.jpt.common.utility.model.Model; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * Extend {@link ValueAspectAdapter} to listen to one or more + * tree aspects of the value in the wrapped value model. + */ +public class ValueTreeAdapter + extends ValueAspectAdapter +{ + /** The names of the value's trees that we listen to. */ + protected final String[] treeNames; + + /** Listener that listens to the value. */ + protected final TreeChangeListener valueTreeListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified value trees. + */ + public ValueTreeAdapter(WritablePropertyValueModel valueHolder, String... treeNames) { + super(valueHolder); + this.treeNames = treeNames; + this.valueTreeListener = this.buildValueTreeListener(); + } + + + // ********** initialization ********** + + protected TreeChangeListener buildValueTreeListener() { + return new TreeChangeListener() { + public void nodeAdded(TreeAddEvent event) { + ValueTreeAdapter.this.nodeAdded(event); + } + public void nodeRemoved(TreeRemoveEvent event) { + ValueTreeAdapter.this.nodeRemoved(event); + } + public void treeCleared(TreeClearEvent event) { + ValueTreeAdapter.this.treeCleared(event); + } + public void treeChanged(TreeChangeEvent event) { + ValueTreeAdapter.this.treeChanged(event); + } + @Override + public String toString() { + return "value tree listener: " + Arrays.asList(ValueTreeAdapter.this.treeNames); //$NON-NLS-1$ + } + }; + } + + + // ********** ValueAspectAdapter implementation ********** + + @Override + protected void engageValue_() { + for (String treeName : this.treeNames) { + this.value.addTreeChangeListener(treeName, this.valueTreeListener); + } + } + + @Override + protected void disengageValue_() { + for (String treeName : this.treeNames) { + this.value.removeTreeChangeListener(treeName, this.valueTreeListener); + } + } + + + // ********** change events ********** + + protected void nodeAdded(@SuppressWarnings("unused") TreeAddEvent event) { + this.valueAspectChanged(); + } + + protected void nodeRemoved(@SuppressWarnings("unused") TreeRemoveEvent event) { + this.valueAspectChanged(); + } + + protected void treeCleared(@SuppressWarnings("unused") TreeClearEvent event) { + this.valueAspectChanged(); + } + + protected void treeChanged(@SuppressWarnings("unused") TreeChangeEvent event) { + this.valueAspectChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyCollectionValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyCollectionValueModelAdapter.java new file mode 100644 index 0000000000..be61d4904b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyCollectionValueModelAdapter.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.model.value.WritableCollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * An adapter that allows us to make a {@link WritablePropertyValueModel} behave like + * a single-element {@link WritableCollectionValueModel}, sorta. + *

+ * If the property's value is null, an empty iterator is returned + * (i.e. you can't have a collection with a null element). + * Also, only a single-element collection can be written to the adapter. + */ +public class WritablePropertyCollectionValueModelAdapter + extends PropertyCollectionValueModelAdapter + implements WritableCollectionValueModel +{ + + // ********** constructor ********** + + /** + * Convert the specified writable property value model to a writable + * collection value model. + */ + public WritablePropertyCollectionValueModelAdapter(WritablePropertyValueModel valueHolder) { + super(valueHolder); + } + + + // ********** WritableCollectionValueModel implementation ********** + + public void setValues(Iterable values) { + Iterator stream = values.iterator(); + if (stream.hasNext()) { + E newValue = stream.next(); + if (stream.hasNext()) { + throw new IllegalArgumentException("non-singleton collection: " + values); //$NON-NLS-1$ + } + this.getValueHolder().setValue(newValue); + } else { + this.getValueHolder().setValue(null); + } + } + + // our constructor takes only writable property value models + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyListValueModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyListValueModelAdapter.java new file mode 100644 index 0000000000..c9f30847cd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyListValueModelAdapter.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value; + +import java.util.Iterator; + +import org.eclipse.jpt.common.utility.model.value.WritableListValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * An adapter that allows us to make a {@link WritablePropertyValueModel} behave like + * a single-element {@link WritableListValueModel}, sorta. + *

+ * If the property's value is null, an empty iterator is returned + * (i.e. you can't have a list with a null element). + * Also, only a single-element list can be written to the adapter. + */ +public class WritablePropertyListValueModelAdapter + extends PropertyListValueModelAdapter + implements WritableListValueModel +{ + + // ********** constructor ********** + + /** + * Convert the specified writable property value model to a writable + * collection value model. + */ + public WritablePropertyListValueModelAdapter(WritablePropertyValueModel valueHolder) { + super(valueHolder); + } + + + // ********** WritableListValueModel implementation ********** + + public void setListValues(Iterable listValues) { + Iterator stream = listValues.iterator(); + if (stream.hasNext()) { + E newValue = stream.next(); + if (stream.hasNext()) { + throw new IllegalArgumentException("non-singleton list: " + listValues); //$NON-NLS-1$ + } + this.getValueHolder().setValue(newValue); + } else { + this.getValueHolder().setValue(null); + } + } + + // our constructor takes only writable property value models + @SuppressWarnings("unchecked") + protected WritablePropertyValueModel getValueHolder() { + return (WritablePropertyValueModel) this.valueHolder; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencePropertyValueModel.java new file mode 100644 index 0000000000..2b6670abe4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencePropertyValueModel.java @@ -0,0 +1,346 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.prefs; + +import java.util.EventListener; +import java.util.prefs.PreferenceChangeEvent; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; + +import org.eclipse.jpt.common.utility.internal.BidiStringConverter; +import org.eclipse.jpt.common.utility.internal.model.value.AspectAdapter; +import org.eclipse.jpt.common.utility.internal.model.value.StaticPropertyValueModel; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This adapter wraps a Preference and converts it into a PropertyValueModel. + * It listens for the appropriate "preference" changes and converts them into + * VALUE property changes. It also allows the specification of a default value + * for the Preference, which, by default, is null (and is probably *not* a very + * good default). + * + * You can configure whether the preference's value is returned, + * unchanged, as a string or as some other object (e.g. an Integer) by + * setting the adapter's converter. Internally, the preference's value + * is stored as the converted object; and the conversions take place + * when reading or writing from the preferences node or retrieving the + * value from an event fired by the preferences node. + * + * This adapter is a bit different from most other adapters because the + * change events fired off by a Preferences node are asynchronous from + * the change itself. (AbstractPreferences uses an event dispatch daemon.) + * As a result, a client can set our value with #setValue(Object) and we + * will return from that method before we ever receive notification from + * the Preferences node that *it* has changed. This means we cannot + * rely on that event to keep our internally cached value in synch. + */ +public class PreferencePropertyValueModel

+ extends AspectAdapter + implements WritablePropertyValueModel

+{ + /** The key to the preference we use for the value. */ + protected final String key; + + /** + * Cache the current (object) value of the preference so we + * can pass an "old value" when we fire a property change event. + */ + protected P value; + + /** + * The default (object) value returned if there is no value + * associated with the preference. + */ + protected final P defaultValue; + + /** + * This converter is used to convert the preference's + * string value to and from an object. + */ + protected final BidiStringConverter

converter; + + /** A listener that listens to the appropriate preference. */ + protected final PreferenceChangeListener preferenceChangeListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified preference. + * The default value of the preference will be null. + */ + public PreferencePropertyValueModel(Preferences preferences, String key) { + this(preferences, key, null); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public PreferencePropertyValueModel(Preferences preferences, String key, P defaultValue) { + this(preferences, key, defaultValue, BidiStringConverter.Default.

instance()); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public PreferencePropertyValueModel(Preferences preferences, String key, P defaultValue, BidiStringConverter

converter) { + this(new StaticPropertyValueModel(preferences), key, defaultValue, converter); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public static PreferencePropertyValueModel forBoolean(Preferences preferences, String key, boolean defaultValue) { + return new PreferencePropertyValueModel( + preferences, + key, + defaultValue ? Boolean.TRUE : Boolean.FALSE, + BidiStringConverter.BooleanConverter.instance() + ); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public static PreferencePropertyValueModel forInteger(Preferences preferences, String key, int defaultValue) { + return new PreferencePropertyValueModel( + preferences, + key, + Integer.valueOf(defaultValue), + BidiStringConverter.IntegerConverter.instance() + ); + } + + /** + * Construct an adapter for the specified preference. + * The default value of the preference will be null. + */ + public PreferencePropertyValueModel(PropertyValueModel preferencesHolder, String key) { + this(preferencesHolder, key, null); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public PreferencePropertyValueModel(PropertyValueModel preferencesHolder, String key, P defaultValue) { + this(preferencesHolder, key, defaultValue, BidiStringConverter.Default.

instance()); + } + + /** + * Construct an adapter for the specified preference with + * the specified default value for the preference. + */ + public PreferencePropertyValueModel(PropertyValueModel preferencesHolder, String key, P defaultValue, BidiStringConverter

converter) { + super(preferencesHolder); + this.key = key; + this.defaultValue = defaultValue; + this.converter = converter; + this.preferenceChangeListener = this.buildPreferenceChangeListener(); + // our value is null when we are not listening to the preference + this.value = null; + } + + + // ********** initialization ********** + + /** + * A preference has changed, notify the listeners if necessary. + */ + protected PreferenceChangeListener buildPreferenceChangeListener() { + // transform the preference change events into VALUE property change events + return new PreferenceChangeListener() { + public void preferenceChange(PreferenceChangeEvent event) { + PreferencePropertyValueModel.this.preferenceChanged(event.getKey(), event.getNewValue()); + } + @Override + public String toString() { + return "preference change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ValueModel implementation ********** + + /** + * Return the cached (converted) value. + */ + @Override + public synchronized P getValue() { + return this.value; + } + + + // ********** PropertyValueModel implementation ********** + + /** + * Set the cached value, then set the appropriate preference value. + */ + public synchronized void setValue(P value) { + if (this.hasNoListeners()) { + return; // no changes allowed when we have no listeners + } + + Object old = this.value; + this.value = value; + this.fireAspectChanged(old, value); + + if ((this.subject != null) && this.preferenceIsToBeSet(old, value)) { + this.setValue_(value); + } + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected Class getListenerClass() { + return PropertyChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return VALUE; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyPropertyChangeListeners(VALUE); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + this.firePropertyChanged(VALUE, oldValue, newValue); + } + + @Override + protected synchronized void engageSubject_() { + this.subject.addPreferenceChangeListener(this.preferenceChangeListener); + this.value = this.buildValue(); + } + + @Override + protected synchronized void disengageSubject_() { + try { + this.subject.removePreferenceChangeListener(this.preferenceChangeListener); + } catch (IllegalStateException ex) { + // for some odd reason, we are not allowed to remove a listener from a "dead" + // preferences node; so handle the exception that gets thrown here + if ( ! ex.getMessage().equals("Node has been removed.")) { //$NON-NLS-1$ + // if it is not the expected exception, re-throw it + throw ex; + } + } + this.value = null; + } + + + // ********** AbstractModel implementation ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.key); + sb.append(" => "); //$NON-NLS-1$ + sb.append(this.value); + } + + + // ********** public API ********** + + /** + * Return the preference's key. + */ + public String getKey() { + return this.key; + } + + + // ********** internal methods ********** + + /** + * Return the preference's value. + * At this point the subject may be null. + */ + protected P buildValue() { + return (this.subject == null) ? null : this.buildValue_(); + } + + /** + * Return the appropriate preference, converted to the appropriate object. + * At this point we can be sure that the subject is not null. + */ + protected P buildValue_() { + return this.convertToObject(this.subject.get(this.key, this.convertToString(this.defaultValue))); + } + + /** + * Set the appropriate preference after converting the value to a string. + * At this point we can be sure that the subject is not null. + */ + protected void setValue_(P value) { + this.subject.put(this.key, this.convertToString(value)); + } + + /** + * Return whether the specified new value should be passed + * through to the preference. By default, only if the value has changed, + * will it be passed through to the preference. This also has the + * effect of not creating new preferences in the "backing store" + * if the new value is the same as the default value. + * + * Subclasses can override this method to return true if they + * would like to ALWAYS pass through the new value to the preference. + */ + protected boolean preferenceIsToBeSet(Object oldValue, Object newValue) { + return this.attributeValueHasChanged(oldValue, newValue); + } + + /** + * Convert the specified object to a string that can be stored as + * the value of the preference. + */ + protected String convertToString(P o) { + return this.converter.convertToString(o); + } + + /** + * Convert the specified preference value string to an + * appropriately-typed object to be returned to the client. + */ + protected P convertToObject(String s) { + return this.converter.convertToObject(s); + } + + protected void preferenceChanged(String prefKey, @SuppressWarnings("unused") String newValue) { + if (prefKey.equals(this.key)) { + this.preferenceChanged(); + } + } + + /** + * The underlying preference changed; either because we changed it + * in #setValue_(Object) or a third-party changed it. + * If this is called because of our own change, the event will be + * swallowed because the old and new values are the same. + */ + protected synchronized void preferenceChanged() { + Object old = this.value; + this.value = this.buildValue(); + this.fireAspectChanged(old, this.value); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencesCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencesCollectionValueModel.java new file mode 100644 index 0000000000..f12a84f138 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencesCollectionValueModel.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.prefs; + +import java.util.EventListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.prefs.BackingStoreException; +import java.util.prefs.PreferenceChangeEvent; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; + +import org.eclipse.jpt.common.utility.internal.CollectionTools; +import org.eclipse.jpt.common.utility.internal.iterators.ArrayIterator; +import org.eclipse.jpt.common.utility.internal.iterators.TransformationIterator; +import org.eclipse.jpt.common.utility.internal.model.value.AspectAdapter; +import org.eclipse.jpt.common.utility.internal.model.value.StaticPropertyValueModel; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; + +/** + * This adapter wraps a Preferences node and converts its preferences into a + * CollectionValueModel of PreferencePropertyValueModels. It listens for + * "preference" changes and converts them into VALUE collection changes. + */ +public class PreferencesCollectionValueModel

+ extends AspectAdapter + implements CollectionValueModel> +{ + + /** Cache the current preferences, stored in models and keyed by name. */ + protected final HashMap> preferences; + + /** A listener that listens to the preferences node for added or removed preferences. */ + protected final PreferenceChangeListener preferenceChangeListener; + + + // ********** constructors ********** + + /** + * Construct an adapter for the specified preferences node. + */ + public PreferencesCollectionValueModel(Preferences preferences) { + this(new StaticPropertyValueModel(preferences)); + } + + /** + * Construct an adapter for the specified preferences node. + */ + public PreferencesCollectionValueModel(PropertyValueModel preferencesHolder) { + super(preferencesHolder); + this.preferences = new HashMap>(); + this.preferenceChangeListener = this.buildPreferenceChangeListener(); + } + + + // ********** initialization ********** + + /** + * A preferences have changed, notify the listeners. + */ + protected PreferenceChangeListener buildPreferenceChangeListener() { + // transform the preference change events into VALUE collection change events + return new PreferenceChangeListener() { + public void preferenceChange(PreferenceChangeEvent event) { + PreferencesCollectionValueModel.this.preferenceChanged(event.getKey(), event.getNewValue()); + } + @Override + public String toString() { + return "preference change listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** CollectionValueModel implementation ********** + + /** + * Return an iterator on the preference models. + */ + public synchronized Iterator> iterator() { + return this.preferences.values().iterator(); + } + + public synchronized int size() { + return this.preferences.size(); + } + + + // ********** AspectAdapter implementation ********** + + @Override + protected Object getValue() { + return this.iterator(); + } + + @Override + protected Class getListenerClass() { + return CollectionChangeListener.class; + } + + @Override + protected String getListenerAspectName() { + return VALUES; + } + + @Override + protected boolean hasListeners() { + return this.hasAnyCollectionChangeListeners(VALUES); + } + + @Override + protected void fireAspectChanged(Object oldValue, Object newValue) { + @SuppressWarnings("unchecked") Iterator> iterator = (Iterator>) newValue; + this.fireCollectionChanged(VALUES, CollectionTools.collection(iterator)); + } + + @Override + protected void engageSubject_() { + this.subject.addPreferenceChangeListener(this.preferenceChangeListener); + for (Iterator> stream = this.preferenceModels(); stream.hasNext(); ) { + PreferencePropertyValueModel

preferenceModel = stream.next(); + this.preferences.put(preferenceModel.getKey(), preferenceModel); + } + } + + @Override + protected void disengageSubject_() { + try { + this.subject.removePreferenceChangeListener(this.preferenceChangeListener); + } catch (IllegalStateException ex) { + // for some odd reason, we are not allowed to remove a listener from a "dead" + // preferences node; so handle the exception that gets thrown here + if ( ! ex.getMessage().equals("Node has been removed.")) { //$NON-NLS-1$ + // if it is not the expected exception, re-throw it + throw ex; + } + } + this.preferences.clear(); + } + + + // ********** AbstractModel implementation ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.subject); + } + + + // ********** internal methods ********** + + /** + * Return an iterator on the preference models. + * At this point we can be sure that the subject is not null. + */ + protected Iterator> preferenceModels() { + String[] keys; + try { + keys = this.subject.keys(); + } catch (BackingStoreException ex) { + throw new RuntimeException(ex); + } + return new TransformationIterator>(new ArrayIterator(keys)) { + @Override + protected PreferencePropertyValueModel

transform(String key) { + return PreferencesCollectionValueModel.this.buildPreferenceModel(key); + } + }; + } + + /** + * Override this method to tweak the model used to wrap the + * specified preference (e.g. to customize the model's converter). + */ + protected PreferencePropertyValueModel

buildPreferenceModel(String key) { + return new PreferencePropertyValueModel

(this.subjectHolder, key); + } + + protected synchronized void preferenceChanged(String key, String newValue) { + if (newValue == null) { + // a preference was removed + PreferencePropertyValueModel

preferenceModel = this.preferences.remove(key); + this.fireItemRemoved(VALUES, preferenceModel); + } else if ( ! this.preferences.containsKey(key)) { + // a preference was added + PreferencePropertyValueModel

preferenceModel = this.buildPreferenceModel(key); + this.preferences.put(key, preferenceModel); + this.fireItemAdded(VALUES, preferenceModel); + } else { + // a preference's value changed - do nothing + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/AbstractTreeModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/AbstractTreeModel.java new file mode 100644 index 0000000000..0a3ab42d06 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/AbstractTreeModel.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.io.Serializable; +import javax.swing.event.EventListenerList; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; + +/** + * Abstract class that should have been provided by the JDK + * (à la javax.swing.AbstractListModel). This class provides: + * - support for a collection of listeners + * - a number of convenience methods for firing events for those listeners + */ +public abstract class AbstractTreeModel + implements TreeModel, Serializable +{ + /** Our listeners. */ + protected final EventListenerList listenerList; + + + // ********** constructors/initialization ********** + + protected AbstractTreeModel() { + super(); + this.listenerList = new EventListenerList(); + } + + + // ********** partial TreeModel implementation ********** + + public void addTreeModelListener(TreeModelListener l) { + this.listenerList.add(TreeModelListener.class, l); + } + + public void removeTreeModelListener(TreeModelListener l) { + this.listenerList.remove(TreeModelListener.class, l); + } + + + // ********** queries ********** + + /** + * Return the model's current collection of listeners. + * (There seems to be a pattern of making this type of method public; + * although it should probably be protected....) + */ + public TreeModelListener[] treeModelListeners() { + return this.listenerList.getListeners(TreeModelListener.class); + } + + /** + * Return whether this model has no listeners. + */ + protected boolean hasNoTreeModelListeners() { + return this.listenerList.getListenerCount(TreeModelListener.class) == 0; + } + + /** + * Return whether this model has any listeners. + */ + protected boolean hasTreeModelListeners() { + return ! this.hasNoTreeModelListeners(); + } + + + // ********** behavior ********** + + /** + * Notify listeners of a model change. + * A significant property of the nodes changed, but the nodes themselves + * are still the same objects. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodesChanged(Object[] path, int[] childIndices, Object[] children) { + // guaranteed to return a non-null array + Object[] listeners = this.listenerList.getListenerList(); + TreeModelEvent event = null; + // process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // lazily create the event + if (event == null) { + event = new TreeModelEvent(this, path, childIndices, children); + } + ((TreeModelListener) listeners[i+1]).treeNodesChanged(event); + } + } + } + + + /** + * Notify listeners of a model change. + * A significant property of the node changed, but the node itself is the same object. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodeChanged(Object[] path, int childIndex, Object child) { + this.fireTreeNodesChanged(path, new int[] {childIndex}, new Object[] {child}); + } + + /** + * Notify listeners of a model change. + * A significant property of the root changed, but the root itself is the same object. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeRootChanged(Object root) { + this.fireTreeNodesChanged(new Object[] {root}, null, null); + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodesInserted(Object[] path, int[] childIndices, Object[] children) { + // guaranteed to return a non-null array + Object[] listeners = this.listenerList.getListenerList(); + TreeModelEvent event = null; + // process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // lazily create the event + if (event == null) { + event = new TreeModelEvent(this, path, childIndices, children); + } + ((TreeModelListener) listeners[i+1]).treeNodesInserted(event); + } + } + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodeInserted(Object[] path, int childIndex, Object child) { + this.fireTreeNodesInserted(path, new int[] {childIndex}, new Object[] {child}); + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodesRemoved(Object[] path, int[] childIndices, Object[] children) { + // guaranteed to return a non-null array + Object[] listeners = this.listenerList.getListenerList(); + TreeModelEvent event = null; + // process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // lazily create the event + if (event == null) { + event = new TreeModelEvent(this, path, childIndices, children); + } + ((TreeModelListener) listeners[i+1]).treeNodesRemoved(event); + } + } + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeNodeRemoved(Object[] path, int childIndex, Object child) { + this.fireTreeNodesRemoved(path, new int[] {childIndex}, new Object[] {child}); + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeStructureChanged(Object[] path) { + // guaranteed to return a non-null array + Object[] listeners = this.listenerList.getListenerList(); + TreeModelEvent event = null; + // process the listeners last to first, notifying + // those that are interested in this event + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==TreeModelListener.class) { + // lazily create the event + if (event == null) { + event = new TreeModelEvent(this, path); + } + ((TreeModelListener) listeners[i+1]).treeStructureChanged(event); + } + } + } + + /** + * Notify listeners of a model change. + * @see javax.swing.event.TreeModelEvent + * @see javax.swing.event.TreeModelListener + */ + protected void fireTreeRootReplaced(Object newRoot) { + this.fireTreeStructureChanged(new Object[] {newRoot}); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/CheckBoxModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/CheckBoxModelAdapter.java new file mode 100644 index 0000000000..78069cd6b0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/CheckBoxModelAdapter.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.ButtonModel can be used to keep a listener + * (e.g. a JCheckBox) in synch with a PropertyValueModel that + * holds a boolean. + * + * Maybe not the richest class in our toolbox, but it was the + * victim of refactoring.... ~bjv + */ +public class CheckBoxModelAdapter + extends ToggleButtonModelAdapter +{ + + // ********** constructors ********** + + /** + * Constructor - the boolean holder is required. + */ + public CheckBoxModelAdapter(WritablePropertyValueModel booleanHolder, boolean defaultValue) { + super(booleanHolder, defaultValue); + } + + /** + * Constructor - the boolean holder is required. + * The default value will be false. + */ + public CheckBoxModelAdapter(WritablePropertyValueModel booleanHolder) { + super(booleanHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ColumnAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ColumnAdapter.java new file mode 100644 index 0000000000..e101823abc --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ColumnAdapter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + + +/** + * This adapter is used by the table model adapter to + * convert a model object into the models used for each of + * the cells for the object's corresponding row in the table. + */ +public interface ColumnAdapter { + /** + * Return the number of columns in the table. + * Typically this is static. + */ + int columnCount(); + + /** + * Return the name of the specified column. + */ + String columnName(int index); + + /** + * Return the class of the specified column. + */ + Class columnClass(int index); + + /** + * Return whether the specified column is editable. + * Typically this is the same for every row. + */ + boolean columnIsEditable(int index); + + /** + * Return the cell models for the specified subject + * that corresponds to a single row in the table. + */ + WritablePropertyValueModel[] cellModels(Object subject); + +} \ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ComboBoxModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ComboBoxModelAdapter.java new file mode 100644 index 0000000000..7cb5f7d88e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ComboBoxModelAdapter.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import javax.swing.ComboBoxModel; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.ComboBoxModel can be used to keep a ListDataListener + * (e.g. a JComboBox) in synch with a ListValueModel (or a CollectionValueModel). + * For combo boxes, the model object that holds the current selection is + * typically a different model object than the one that holds the collection + * of choices. + * + * For example, a MWReference (the selectionOwner) has an attribute + * "sourceTable" (the collectionOwner) + * which holds on to a collection of MWDatabaseFields. When the selection + * is changed this model will keep the listeners aware of the changes. + * The inherited list model will keep its listeners aware of changes to the + * collection model + * + * In addition to the collection holder required by the superclass, + * an instance of this ComboBoxModel must be supplied with a + * selection holder, which is a PropertyValueModel that provides access + * to the selection (typically a PropertyAspectAdapter). + */ +public class ComboBoxModelAdapter + extends ListModelAdapter + implements ComboBoxModel +{ + protected final WritablePropertyValueModel selectionHolder; + protected final PropertyChangeListener selectionListener; + + + // ********** constructors ********** + + /** + * Constructor - the list holder and selection holder are required; + */ + public ComboBoxModelAdapter(ListValueModel listHolder, WritablePropertyValueModel selectionHolder) { + super(listHolder); + if (selectionHolder == null) { + throw new NullPointerException(); + } + this.selectionHolder = selectionHolder; + this.selectionListener = this.buildSelectionListener(); + } + + /** + * Constructor - the collection holder and selection holder are required; + */ + public ComboBoxModelAdapter(CollectionValueModel collectionHolder, WritablePropertyValueModel selectionHolder) { + super(collectionHolder); + if (selectionHolder == null) { + throw new NullPointerException(); + } + this.selectionHolder = selectionHolder; + this.selectionListener = this.buildSelectionListener(); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildSelectionListener() { + return new AWTPropertyChangeListenerWrapper(this.buildSelectionListener_()); + } + + protected PropertyChangeListener buildSelectionListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + // notify listeners that the selection has changed + ComboBoxModelAdapter.this.fireSelectionChanged(); + } + @Override + public String toString() { + return "selection listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ComboBoxModel implementation ********** + + public Object getSelectedItem() { + return this.selectionHolder.getValue(); + } + + public void setSelectedItem(Object selectedItem) { + this.selectionHolder.setValue(selectedItem); + } + + + // ********** behavior ********** + + /** + * Extend to engage the selection holder. + */ + @Override + protected void engageModel() { + super.engageModel(); + this.selectionHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.selectionListener); + } + + /** + * Extend to disengage the selection holder. + */ + @Override + protected void disengageModel() { + this.selectionHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.selectionListener); + super.disengageModel(); + } + + /** + * Notify the listeners that the selection has changed. + */ + protected void fireSelectionChanged() { + // I guess this will work... + this.fireContentsChanged(this, -1, -1); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.selectionHolder + ":" + this.listHolder); //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DateSpinnerModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DateSpinnerModelAdapter.java new file mode 100644 index 0000000000..61ef8cee8f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DateSpinnerModelAdapter.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.Calendar; +import java.util.Date; +import javax.swing.SpinnerDateModel; +import javax.swing.event.ChangeListener; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.SpinnerDateModel can be used to keep a ChangeListener + * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a date. + * + * This class must be a sub-class of SpinnerDateModel because of some + * crappy jdk code.... ~bjv + * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel) + * + * If this class needs to be modified, it would behoove us to review the + * other, similar classes: + * @see ListSpinnerModelAdapter + * @see NumberSpinnerModelAdapter + */ +public class DateSpinnerModelAdapter + extends SpinnerDateModel +{ + + /** + * The default spinner value; used when the underlying model date value is null. + * The default is the current date. + */ + private final Date defaultValue; + + /** A value model on the underlying date. */ + private final WritablePropertyValueModel dateHolder; + + /** A listener that allows us to synchronize with changes made to the underlying date. */ + private final PropertyChangeListener dateChangeListener; + + + // ********** constructors ********** + + /** + * Constructor - the date holder is required. + * The default spinner value is the current date. + */ + public DateSpinnerModelAdapter(WritablePropertyValueModel dateHolder) { + this(dateHolder, new Date()); + } + + /** + * Constructor - the date holder and default value are required. + */ + public DateSpinnerModelAdapter(WritablePropertyValueModel dateHolder, Date defaultValue) { + this(dateHolder, null, null, Calendar.DAY_OF_MONTH, defaultValue); + } + + /** + * Constructor - the date holder is required. + * The default spinner value is the current date. + */ + public DateSpinnerModelAdapter(WritablePropertyValueModel dateHolder, Comparable start, Comparable end, int calendarField) { + this(dateHolder, start, end, calendarField, new Date()); + } + + /** + * Constructor - the date holder is required. + */ + public DateSpinnerModelAdapter(WritablePropertyValueModel dateHolder, Comparable start, Comparable end, int calendarField, Date defaultValue) { + super(dateHolder.getValue() == null ? defaultValue : (Date) dateHolder.getValue(), start, end, calendarField); + this.dateHolder = dateHolder; + this.dateChangeListener = this.buildDateChangeListener(); + // postpone listening to the underlying date + // until we have listeners ourselves... + this.defaultValue = defaultValue; + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildDateChangeListener() { + return new AWTPropertyChangeListenerWrapper(this.buildDateChangeListener_()); + } + + protected PropertyChangeListener buildDateChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DateSpinnerModelAdapter.this.synchronize(event.getNewValue()); + } + @Override + public String toString() { + return "date listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** SpinnerModel implementation ********** + + /** + * Extend to check whether this method is being called before we + * have any listeners. + * This is necessary because some crappy jdk code gets the value + * from the model *before* listening to the model. ~bjv + * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner) + */ + @Override + public Object getValue() { + if (this.getChangeListeners().length == 0) { + // sorry about this "lateral" call to super ~bjv + super.setValue(this.spinnerValueOf(this.dateHolder.getValue())); + } + return super.getValue(); + } + + /** + * Extend to update the underlying date directly. + * The resulting event will be ignored: @see #synchronize(Object). + */ + @Override + public void setValue(Object value) { + super.setValue(value); + this.dateHolder.setValue(value); + } + + /** + * Extend to start listening to the underlying date if necessary. + */ + @Override + public void addChangeListener(ChangeListener listener) { + if (this.getChangeListeners().length == 0) { + this.dateHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.dateChangeListener); + this.synchronize(this.dateHolder.getValue()); + } + super.addChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying date if appropriate. + */ + @Override + public void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.getChangeListeners().length == 0) { + this.dateHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.dateChangeListener); + } + } + + + // ********** queries ********** + + protected Date getDefaultValue() { + return this.defaultValue; + } + + /** + * Convert to a non-null value. + */ + protected Object spinnerValueOf(Object value) { + return (value == null) ? this.getDefaultValue() : value; + } + + + // ********** behavior ********** + + /** + * Set the spinner value if it has changed. + */ + void synchronize(Object value) { + Object newValue = this.spinnerValueOf(value); + // check to see whether the spinner date has already been synchronized + // (via #setValue()) + if ( ! this.getValue().equals(newValue)) { + this.setValue(newValue); + } + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.dateHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DocumentAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DocumentAdapter.java new file mode 100644 index 0000000000..1f7f4043de --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DocumentAdapter.java @@ -0,0 +1,375 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.EventObject; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.EventListenerList; +import javax.swing.event.UndoableEditEvent; +import javax.swing.event.UndoableEditListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; +import javax.swing.text.Position; +import javax.swing.text.Segment; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.text.Document can be used to keep a DocumentListener + * (e.g. a JTextField) in synch with a PropertyValueModel that holds a string. + * + * NB: This model should only be used for "small" documents; + * i.e. documents used by text fields, not text panes. + * @see #synchronizeDelegate(String) + */ +public class DocumentAdapter + implements Document, Serializable +{ + + /** The delegate document whose behavior we "enhance". */ + protected final Document delegate; + + /** A listener that allows us to forward any changes made to the delegate document. */ + protected final CombinedListener delegateListener; + + /** A value model on the underlying model string. */ + protected final WritablePropertyValueModel stringHolder; + + /** A listener that allows us to synchronize with changes made to the underlying model string. */ + protected transient PropertyChangeListener stringListener; + + /** The event listener list for the document. */ + protected final EventListenerList listenerList = new EventListenerList(); + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Constructor - the string holder is required. + * Wrap the specified document. + */ + public DocumentAdapter(WritablePropertyValueModel stringHolder, Document delegate) { + super(); + if (stringHolder == null || delegate == null) { + throw new NullPointerException(); + } + this.stringHolder = stringHolder; + // postpone listening to the underlying model string + // until we have listeners ourselves... + this.delegate = delegate; + this.stringListener = this.buildStringListener(); + this.delegateListener = this.buildDelegateListener(); + } + + /** + * Constructor - the string holder is required. + * Wrap a plain document. + */ + public DocumentAdapter(WritablePropertyValueModel stringHolder) { + this(stringHolder, new PlainDocument()); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildStringListener() { + return new AWTPropertyChangeListenerWrapper(this.buildStringListener_()); + } + + protected PropertyChangeListener buildStringListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DocumentAdapter.this.stringChanged(event); + } + @Override + public String toString() { + return "string listener"; //$NON-NLS-1$ + } + }; + } + + protected CombinedListener buildDelegateListener() { + return new InternalListener(); + } + + + // ********** Document implementation ********** + + public int getLength() { + return this.delegate.getLength(); + } + + /** + * Extend to start listening to the underlying models if necessary. + */ + public void addDocumentListener(DocumentListener listener) { + if (this.listenerList.getListenerCount(DocumentListener.class) == 0) { + this.delegate.addDocumentListener(this.delegateListener); + this.engageStringHolder(); + } + this.listenerList.add(DocumentListener.class, listener); + } + + /** + * Extend to stop listening to the underlying models if appropriate. + */ + public void removeDocumentListener(DocumentListener listener) { + this.listenerList.remove(DocumentListener.class, listener); + if (this.listenerList.getListenerCount(DocumentListener.class) == 0) { + this.disengageStringHolder(); + this.delegate.removeDocumentListener(this.delegateListener); + } + } + + /** + * Extend to start listening to the delegate document if necessary. + */ + public void addUndoableEditListener(UndoableEditListener listener) { + if (this.listenerList.getListenerCount(UndoableEditListener.class) == 0) { + this.delegate.addUndoableEditListener(this.delegateListener); + } + this.listenerList.add(UndoableEditListener.class, listener); + } + + /** + * Extend to stop listening to the delegate document if appropriate. + */ + public void removeUndoableEditListener(UndoableEditListener listener) { + this.listenerList.remove(UndoableEditListener.class, listener); + if (this.listenerList.getListenerCount(UndoableEditListener.class) == 0) { + this.delegate.removeUndoableEditListener(this.delegateListener); + } + } + + public Object getProperty(Object key) { + return this.delegate.getProperty(key); + } + + public void putProperty(Object key, Object value) { + this.delegate.putProperty(key, value); + } + + /** + * Extend to update the underlying model string directly. + * The resulting event will be ignored: @see #synchronizeDelegate(String). + */ + public void remove(int offset, int len) throws BadLocationException { + this.delegate.remove(offset, len); + this.stringHolder.setValue(this.delegate.getText(0, this.delegate.getLength())); + } + + /** + * Extend to update the underlying model string directly. + * The resulting event will be ignored: @see #synchronizeDelegate(String). + */ + public void insertString(int offset, String insertedString, AttributeSet a) throws BadLocationException { + this.delegate.insertString(offset, insertedString, a); + this.stringHolder.setValue(this.delegate.getText(0, this.delegate.getLength())); + } + + public String getText(int offset, int length) throws BadLocationException { + return this.delegate.getText(offset, length); + } + + public void getText(int offset, int length, Segment txt) throws BadLocationException { + this.delegate.getText(offset, length, txt); + } + + public Position getStartPosition() { + return this.delegate.getStartPosition(); + } + + public Position getEndPosition() { + return this.delegate.getEndPosition(); + } + + public Position createPosition(int offs) throws BadLocationException { + return this.delegate.createPosition(offs); + } + + public Element[] getRootElements() { + return this.delegate.getRootElements(); + } + + public Element getDefaultRootElement() { + return this.delegate.getDefaultRootElement(); + } + + public void render(Runnable r) { + this.delegate.render(r); + } + + + // ********** queries ********** + + public DocumentListener[] documentListeners() { + return this.listenerList.getListeners(DocumentListener.class); + } + + public UndoableEditListener[] undoableEditListeners() { + return this.listenerList.getListeners(UndoableEditListener.class); + } + + + // ********** behavior ********** + + /** + * A third party has modified the underlying model string. + * Synchronize the delegate document accordingly. + */ + protected void stringChanged(PropertyChangeEvent event) { + this.synchronizeDelegate((String) event.getNewValue()); + } + + /** + * Replace the document's entire text string with the new string. + */ + protected void synchronizeDelegate(String s) { + try { + int len = this.delegate.getLength(); + // check to see whether the delegate has already been synchronized + // (via #insertString() or #remove()) + if ( ! this.delegate.getText(0, len).equals(s)) { + this.delegate.remove(0, len); + this.delegate.insertString(0, s, null); + } + } catch (BadLocationException ex) { + throw new IllegalStateException(ex.getMessage()); // this should not happen... + } + } + + protected void engageStringHolder() { + this.stringHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.stringListener); + this.synchronizeDelegate(this.stringHolder.getValue()); + } + + protected void disengageStringHolder() { + this.stringHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.stringListener); + } + + protected void delegateChangedUpdate(DocumentEvent event) { + // no need to lazy-initialize the event; + // we wouldn't get here if we did not have listeners... + DocumentEvent ee = new InternalDocumentEvent(this, event); + DocumentListener[] listeners = this.documentListeners(); + for (int i = listeners.length; i-- > 0; ) { + listeners[i].changedUpdate(ee); + } + } + + protected void delegateInsertUpdate(DocumentEvent event) { + // no need to lazy-initialize the event; + // we wouldn't get here if we did not have listeners... + DocumentEvent ee = new InternalDocumentEvent(this, event); + DocumentListener[] listeners = this.documentListeners(); + for (int i = listeners.length; i-- > 0; ) { + listeners[i].insertUpdate(ee); + } + } + + protected void delegateRemoveUpdate(DocumentEvent event) { + // no need to lazy-initialize the event; + // we wouldn't get here if we did not have listeners... + DocumentEvent ee = new InternalDocumentEvent(this, event); + DocumentListener[] listeners = this.documentListeners(); + for (int i = listeners.length; i-- > 0; ) { + listeners[i].removeUpdate(ee); + } + } + + protected void delegateUndoableEditHappened(UndoableEditEvent event) { + // no need to lazy-initialize the event; + // we wouldn't get here if we did not have listeners... + UndoableEditEvent ee = new UndoableEditEvent(this, event.getEdit()); + UndoableEditListener[] listeners = this.undoableEditListeners(); + for (int i = listeners.length; i-- > 0; ) { + listeners[i].undoableEditHappened(ee); + } + } + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.stringHolder); + } + + private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { + // read in any hidden stuff + s.defaultReadObject(); + this.stringListener = this.buildStringListener(); + } + + +// ********** inner class ********** + + protected interface CombinedListener extends DocumentListener, UndoableEditListener, Serializable { + // just consolidate the two interfaces + } + + protected class InternalListener implements CombinedListener { + private static final long serialVersionUID = 1L; + public void changedUpdate(DocumentEvent event) { + DocumentAdapter.this.delegateChangedUpdate(event); + } + public void insertUpdate(DocumentEvent event) { + DocumentAdapter.this.delegateInsertUpdate(event); + } + public void removeUpdate(DocumentEvent event) { + DocumentAdapter.this.delegateRemoveUpdate(event); + } + public void undoableEditHappened(UndoableEditEvent event) { + DocumentAdapter.this.delegateUndoableEditHappened(event); + } + } + + protected static class InternalDocumentEvent + extends EventObject + implements DocumentEvent + { + protected DocumentEvent delegate; + + protected InternalDocumentEvent(Document document, DocumentEvent delegate) { + super(document); + this.delegate = delegate; + } + public ElementChange getChange(Element elem) { + return this.delegate.getChange(elem); + } + public Document getDocument() { + return (Document) this.source; + } + public int getLength() { + return this.delegate.getLength(); + } + public int getOffset() { + return this.delegate.getOffset(); + } + public EventType getType() { + return this.delegate.getType(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListModelAdapter.java new file mode 100644 index 0000000000..2d1c1e4b5d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListModelAdapter.java @@ -0,0 +1,292 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import javax.swing.AbstractListModel; +import javax.swing.event.ListDataListener; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.value.CollectionListValueModelAdapter; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This javax.swing.ListModel can be used to keep a ListDataListener + * (e.g. a JList) in synch with a ListValueModel (or a CollectionValueModel). + * + * An instance of this ListModel *must* be supplied with a value model, + * which is a ListValueModel on the bound list or a CollectionValueModel + * on the bound collection. This is required - the list (or collection) + * itself can be null, but the value model that holds it cannot. + */ +public class ListModelAdapter + extends AbstractListModel +{ + /** A value model on the underlying model list. */ + protected ListValueModel listHolder; + + /** + * Cache the size of the list for "dramatic" changes. + * @see #listChanged(ListChangeEvent) + */ + protected int listSize; + + /** A listener that allows us to forward changes made to the underlying model list. */ + protected final ListChangeListener listChangeListener; + + + // ********** constructors ********** + + /** + * Default constructor - initialize stuff. + */ + private ListModelAdapter() { + super(); + this.listSize = 0; + this.listChangeListener = this.buildListChangeListener(); + } + + /** + * Constructor - the list holder is required. + */ + public ListModelAdapter(ListValueModel listHolder) { + this(); + this.setModel(listHolder); + } + + /** + * Constructor - the collection holder is required. + */ + public ListModelAdapter(CollectionValueModel collectionHolder) { + this(); + this.setModel(collectionHolder); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new AWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListModelAdapter.this.itemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListModelAdapter.this.itemsRemoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListModelAdapter.this.itemsReplaced(event); + } + public void itemsMoved(ListMoveEvent event) { + ListModelAdapter.this.itemsMoved(event); + } + public void listCleared(ListClearEvent event) { + ListModelAdapter.this.listCleared(); + } + public void listChanged(ListChangeEvent event) { + ListModelAdapter.this.listChanged(); + } + @Override + public String toString() { + return "list listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ListModel implementation ********** + + public int getSize() { + return this.listHolder.size(); + } + + public Object getElementAt(int index) { + return this.listHolder.get(index); + } + + /** + * Extend to start listening to the underlying model list if necessary. + */ + @Override + public void addListDataListener(ListDataListener l) { + if (this.hasNoListDataListeners()) { + this.engageModel(); + this.listSize = this.listHolder.size(); + } + super.addListDataListener(l); + } + + /** + * Extend to stop listening to the underlying model list if appropriate. + */ + @Override + public void removeListDataListener(ListDataListener l) { + super.removeListDataListener(l); + if (this.hasNoListDataListeners()) { + this.disengageModel(); + this.listSize = 0; + } + } + + + // ********** public API ********** + + /** + * Return the underlying list model. + */ + public ListValueModel model() { + return this.listHolder; + } + + /** + * Set the underlying list model. + */ + public void setModel(ListValueModel listHolder) { + if (listHolder == null) { + throw new NullPointerException(); + } + boolean hasListeners = this.hasListDataListeners(); + if (hasListeners) { + this.disengageModel(); + } + this.listHolder = listHolder; + if (hasListeners) { + this.engageModel(); + this.listChanged(); + } + } + + /** + * Set the underlying collection model. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setModel(CollectionValueModel collectionHolder) { + this.setModel(new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** queries ********** + + /** + * Return whether this model has no listeners. + */ + protected boolean hasNoListDataListeners() { + return this.getListDataListeners().length == 0; + } + + /** + * Return whether this model has any listeners. + */ + protected boolean hasListDataListeners() { + return ! this.hasNoListDataListeners(); + } + + + // ********** behavior ********** + + protected void engageModel() { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + protected void disengageModel() { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + + + // ********** list change support ********** + + /** + * Items were added to the underlying model list. + * Notify listeners of the changes. + */ + protected void itemsAdded(ListAddEvent event) { + int start = event.getIndex(); + int end = start + event.getItemsSize() - 1; + this.fireIntervalAdded(this, start, end); + this.listSize += event.getItemsSize(); + } + + /** + * Items were removed from the underlying model list. + * Notify listeners of the changes. + */ + protected void itemsRemoved(ListRemoveEvent event) { + int start = event.getIndex(); + int end = start + event.getItemsSize() - 1; + this.fireIntervalRemoved(this, start, end); + this.listSize -= event.getItemsSize(); + } + + /** + * Items were replaced in the underlying model list. + * Notify listeners of the changes. + */ + protected void itemsReplaced(ListReplaceEvent event) { + int start = event.getIndex(); + int end = start + event.getItemsSize() - 1; + this.fireContentsChanged(this, start, end); + } + + /** + * Items were moved in the underlying model list. + * Notify listeners of the changes. + */ + protected void itemsMoved(ListMoveEvent event) { + int start = Math.min(event.getSourceIndex(), event.getTargetIndex()); + int end = Math.max(event.getSourceIndex(), event.getTargetIndex()) + event.getLength() - 1; + this.fireContentsChanged(this, start, end); + } + + /** + * The underlying model list was cleared. + * Notify listeners of the changes. + */ + protected void listCleared() { + if (this.listSize != 0) { + this.fireIntervalRemoved(this, 0, this.listSize - 1); + this.listSize = 0; + } + } + + /** + * The underlying model list has changed "dramatically". + * Notify listeners of the changes. + */ + protected void listChanged() { + if (this.listSize != 0) { + this.fireIntervalRemoved(this, 0, this.listSize - 1); + } + this.listSize = this.listHolder.size(); + if (this.listSize != 0) { + this.fireIntervalAdded(this, 0, this.listSize - 1); + } + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListSpinnerModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListSpinnerModelAdapter.java new file mode 100644 index 0000000000..af3e0b0187 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListSpinnerModelAdapter.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.Arrays; +import java.util.List; +import javax.swing.SpinnerListModel; +import javax.swing.event.ChangeListener; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.SpinnerListModel can be used to keep a ChangeListener + * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a value + * in the list. + * + * This class must be a sub-class of SpinnerListModel because of some + * crappy jdk code.... ~bjv + * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel) + * + * NB: This model should only be used for values that have a reasonably + * inexpensive #equals() implementation. + * @see #synchronize(Object) + * + * If this class needs to be modified, it would behoove us to review the + * other, similar classes: + * @see DateSpinnerModelAdapter + * @see NumberSpinnerModelAdapter + */ +public class ListSpinnerModelAdapter + extends SpinnerListModel +{ + + /** + * The default spinner value; used when the underlying model value is null. + * The default is the first item on the list. + */ + private final Object defaultValue; + + /** A value model on the underlying value. */ + private final WritablePropertyValueModel valueHolder; + + /** A listener that allows us to synchronize with changes made to the underlying value. */ + private final PropertyChangeListener valueChangeListener; + + + // ********** constructors ********** + + /** + * Constructor - the value holder is required. + * Use the model value itself as the default spinner value. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder) { + this(valueHolder, valueHolder.getValue()); + } + + /** + * Constructor - the value holder is required. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder, Object defaultValue) { + this(valueHolder, new Object[] {defaultValue}, defaultValue); + } + + /** + * Constructor - the value holder is required. + * Use the first item in the list of values as the default spinner value. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder, Object[] values) { + this(valueHolder, values, values[0]); + } + + /** + * Constructor - the value holder is required. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder, Object[] values, Object defaultValue) { + this(valueHolder, Arrays.asList(values), defaultValue); + } + + /** + * Constructor - the value holder is required. + * Use the first item in the list of values as the default spinner value. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder, List values) { + this(valueHolder, values, values.get(0)); + } + + /** + * Constructor - the value holder is required. + */ + public ListSpinnerModelAdapter(WritablePropertyValueModel valueHolder, List values, Object defaultValue) { + super(values); + this.valueHolder = valueHolder; + this.valueChangeListener = this.buildValueChangeListener(); + // postpone listening to the underlying value + // until we have listeners ourselves... + this.defaultValue = defaultValue; + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildValueChangeListener() { + return new AWTPropertyChangeListenerWrapper(this.buildValueChangeListener_()); + } + + protected PropertyChangeListener buildValueChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + ListSpinnerModelAdapter.this.synchronize(event.getNewValue()); + } + @Override + public String toString() { + return "value listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** SpinnerModel implementation ********** + + /** + * Extend to check whether this method is being called before we + * have any listeners. + * This is necessary because some crappy jdk code gets the value + * from the model *before* listening to the model. ~bjv + * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner) + */ + @Override + public Object getValue() { + if (this.getChangeListeners().length == 0) { + // sorry about this "lateral" call to super ~bjv + super.setValue(this.spinnerValueOf(this.valueHolder.getValue())); + } + return super.getValue(); + } + + /** + * Extend to update the underlying value directly. + * The resulting event will be ignored: @see #synchronize(Object). + */ + @Override + public void setValue(Object value) { + super.setValue(value); + this.valueHolder.setValue(value); + } + + /** + * Extend to start listening to the underlying value if necessary. + */ + @Override + public void addChangeListener(ChangeListener listener) { + if (this.getChangeListeners().length == 0) { + this.valueHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.valueChangeListener); + this.synchronize(this.valueHolder.getValue()); + } + super.addChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying value if appropriate. + */ + @Override + public void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.getChangeListeners().length == 0) { + this.valueHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.valueChangeListener); + } + } + + + // ********** queries ********** + + protected Object getDefaultValue() { + return this.defaultValue; + } + + /** + * Convert to a non-null value. + */ + protected Object spinnerValueOf(Object value) { + return (value == null) ? this.getDefaultValue() : value; + } + + + // ********** behavior ********** + + /** + * Set the spinner value if it has changed. + */ + void synchronize(Object value) { + Object newValue = this.spinnerValueOf(value); + // check to see whether the spinner value has already been synchronized + // (via #setValue()) + if ( ! this.getValue().equals(newValue)) { + this.setValue(newValue); + } + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.valueHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java new file mode 100644 index 0000000000..4a624639b5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeListener; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.SpinnerNumberModel can be used to keep a ChangeListener + * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a number. + * + * This class must be a sub-class of SpinnerNumberModel because of some + * crappy jdk code.... ~bjv + * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel) + * + * If this class needs to be modified, it would behoove us to review the + * other, similar classes: + * @see DateSpinnerModelAdapter + * @see ListSpinnerModelAdapter + */ +public class NumberSpinnerModelAdapter + extends SpinnerNumberModel +{ + + /** + * The default spinner value; used when the + * underlying model number value is null. + */ + private final Number defaultValue; + + /** A value model on the underlying number. */ + private final WritablePropertyValueModel numberHolder; + + /** + * A listener that allows us to synchronize with + * changes made to the underlying number. + */ + private final PropertyChangeListener numberChangeListener; + + + // ********** constructors ********** + + /** + * Constructor - the number holder is required. + * The default spinner value is zero. + * The step size is one. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder) { + this(numberHolder, 0); + } + + /** + * Constructor - the number holder is required. + * The step size is one. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, int defaultValue) { + this(numberHolder, null, null, Integer.valueOf(1), Integer.valueOf(defaultValue)); + } + + /** + * Constructor - the number holder is required. + * Use the minimum value as the default spinner value. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, int minimum, int maximum, int stepSize) { + this(numberHolder, minimum, maximum, stepSize, minimum); + } + + /** + * Constructor - the number holder is required. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, int minimum, int maximum, int stepSize, int defaultValue) { + this(numberHolder, Integer.valueOf(minimum), Integer.valueOf(maximum), Integer.valueOf(stepSize), Integer.valueOf(defaultValue)); + } + + /** + * Constructor - the number holder is required. + * Use the minimum value as the default spinner value. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, double minimum, double maximum, double stepSize) { + this(numberHolder, minimum, maximum, stepSize, minimum); + } + + /** + * Constructor - the number holder is required. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, double minimum, double maximum, double stepSize, double defaultValue) { + this(numberHolder, Double.valueOf(minimum), Double.valueOf(maximum), Double.valueOf(stepSize), Double.valueOf(defaultValue)); + } + + /** + * Constructor - the number holder is required. + */ + public NumberSpinnerModelAdapter(WritablePropertyValueModel numberHolder, Comparable minimum, Comparable maximum, Number stepSize, Number defaultValue) { + super(numberHolder.getValue() == null ? defaultValue : (Number) numberHolder.getValue(), minimum, maximum, stepSize); + this.numberHolder = numberHolder; + this.numberChangeListener = this.buildNumberChangeListener(); + // postpone listening to the underlying number + // until we have listeners ourselves... + this.defaultValue = defaultValue; + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildNumberChangeListener() { + return new AWTPropertyChangeListenerWrapper(this.buildNumberChangeListener_()); + } + + protected PropertyChangeListener buildNumberChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + NumberSpinnerModelAdapter.this.synchronize(event.getNewValue()); + } + @Override + public String toString() { + return "number listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** SpinnerModel implementation ********** + + /** + * Extend to check whether this method is being called before we + * have any listeners. + * This is necessary because some crappy jdk code gets the value + * from the model *before* listening to the model. ~bjv + * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner) + */ + @Override + public Object getValue() { + if (this.getChangeListeners().length == 0) { + // sorry about this "lateral" call to super ~bjv + super.setValue(this.spinnerValueOf(this.numberHolder.getValue())); + } + return super.getValue(); + } + + /** + * Extend to update the underlying number directly. + * The resulting event will be ignored: @see #synchronizeDelegate(Object). + */ + @Override + public void setValue(Object value) { + super.setValue(value); + this.numberHolder.setValue((Number) value); + } + + /** + * Extend to start listening to the underlying number if necessary. + */ + @Override + public void addChangeListener(ChangeListener listener) { + if (this.getChangeListeners().length == 0) { + this.numberHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.numberChangeListener); + this.synchronize(this.numberHolder.getValue()); + } + super.addChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying number if appropriate. + */ + @Override + public void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.getChangeListeners().length == 0) { + this.numberHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.numberChangeListener); + } + } + + + // ********** queries ********** + + protected Number getDefaultValue() { + return this.defaultValue; + } + + /** + * Convert to a non-null value. + */ + protected Object spinnerValueOf(Object value) { + return (value == null) ? this.getDefaultValue() : value; + } + + + // ********** behavior ********** + + /** + * Set the spinner value if it has changed. + */ + void synchronize(Object value) { + Object newValue = this.spinnerValueOf(value); + // check to see whether the date has already been synchronized + // (via #setValue()) + if ( ! this.getValue().equals(newValue)) { + this.setValue(newValue); + } + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.numberHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ObjectListSelectionModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ObjectListSelectionModel.java new file mode 100644 index 0000000000..81647b2842 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ObjectListSelectionModel.java @@ -0,0 +1,427 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import javax.swing.DefaultListSelectionModel; +import javax.swing.ListModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionListener; +import org.eclipse.jpt.common.utility.internal.CollectionTools; + +/** + * This ListSelectionModel is aware of the ListModel and + * provides convenience methods to access and set the + * selected *objects*, as opposed to the selected *indexes*. + */ +public class ObjectListSelectionModel + extends DefaultListSelectionModel +{ + /** The list model referenced by the list selection model. */ + private final ListModel listModel; + + /** A listener that allows us to clear the selection when the list model has changed. */ + private final ListDataListener listDataListener; + + + // ********** constructors ********** + + /** + * Construct a list selection model for the specified list model. + */ + public ObjectListSelectionModel(ListModel listModel) { + super(); + this.listModel = listModel; + this.listDataListener = this.buildListDataListener(); + } + + + // ********** initialization ********** + + private ListDataListener buildListDataListener() { + return new ListDataListener() { + public void intervalAdded(ListDataEvent event) { + // this does not affect the selection + } + public void intervalRemoved(ListDataEvent event) { + // this does not affect the selection + } + public void contentsChanged(ListDataEvent event) { + ObjectListSelectionModel.this.listModelContentsChanged(event); + } + @Override + public String toString() { + return "list data listener"; //$NON-NLS-1$ + } + }; + } + + /** + * Typically, the selection does not need to be cleared when the + * contents of the list have changed. Most of the time this just + * means an item has changed in a way that affects its display string + * or icon. We typically only use the class for edits involving + * single selection. + * A subclass can override this method if the selection + * should be cleared because a change could mean the selection is invalid. + */ + protected void listModelContentsChanged(@SuppressWarnings("unused") ListDataEvent event) { + /**this.clearSelection();*/ + } + + + // ********** ListSelectionModel implementation ********** + + @Override + public void addListSelectionListener(ListSelectionListener l) { + if (this.hasNoListSelectionListeners()) { + this.listModel.addListDataListener(this.listDataListener); + } + super.addListSelectionListener(l); + } + + @Override + public void removeListSelectionListener(ListSelectionListener l) { + super.removeListSelectionListener(l); + if (this.hasNoListSelectionListeners()) { + this.listModel.removeListDataListener(this.listDataListener); + } + } + + + // ********** queries ********** + + /** + * Return whether this model has no listeners. + */ + protected boolean hasNoListSelectionListeners() { // private-protected + return this.getListSelectionListeners().length == 0; + } + + /** + * Return the list model referenced by the list selection model. + */ + public ListModel getListModel() { + return this.listModel; + } + + public int selectedValuesSize() { + int min = this.getMinSelectionIndex(); + int max = this.getMaxSelectionIndex(); + + if ((min < 0) || (max < 0)) { + return 0; + } + + int n = 0; + int count = this.getListModel().getSize(); + for (int i = min; i <= max; i++) { + if (this.isSelectedIndex(i) && (i < count)) { + n++; + } + } + return n; + } + + /** + * Return the first selected value. + * Return null if the selection is empty. + */ + public Object selectedValue() { + int index = this.getMinSelectionIndex(); + if (index == -1) { + return null; + } + if (this.getListModel().getSize() <= index) { + return null; + } + return this.getListModel().getElementAt(index); + } + + /** + * Return an array of the selected values. + */ + public Object[] selectedValues() { + int min = this.getMinSelectionIndex(); + int max = this.getMaxSelectionIndex(); + + if ((min < 0) || (max < 0)) { + return new Object[0]; + } + + int maxSize = (max - min) + 1; + Object[] temp = new Object[maxSize]; + int n = 0; + int count = this.getListModel().getSize(); + for (int i = min; i <= max; i++) { + if (this.isSelectedIndex(i) && (i < count)) { + temp[n++] = this.getListModel().getElementAt(i); + } + } + if (n == maxSize) { + // all the elements in the range were selected + return temp; + } + // only some of the elements in the range were selected + Object[] result = new Object[n]; + System.arraycopy(temp, 0, result, 0, n); + return result; + } + + /** + * Return an array of the selected indices in order. + */ + public int[] selectedIndices() { + int min = this.getMinSelectionIndex(); + int max = this.getMaxSelectionIndex(); + + if ((min < 0) || (max < 0)) { + return new int[0]; + } + + int maxSize = (max - min) + 1; + int[] temp = new int[maxSize]; + int n = 0; + int count = this.getListModel().getSize(); + for (int i = min; i <= max; i++) { + if (this.isSelectedIndex(i) && (i < count)) { + temp[n++] = i; + } + } + if (n == maxSize) { + // all the elements in the range were selected + Arrays.sort(temp); + return temp; + } + // only some of the elements in the range were selected + int[] result = new int[n]; + System.arraycopy(temp, 0, result, 0, n); + Arrays.sort(result); + return result; + } + + /** + * Set the selected value. + */ + public void setSelectedValue(Object object) { + this.setSelectedValues(CollectionTools.singletonIterator(object)); + } + + /** + * Set the current set of selected objects to the specified objects. + * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int) + */ + public void setSelectedValues(Iterator objects) { + this.setValueIsAdjusting(true); + this.clearSelection(); + this.addSelectedValuesInternal(objects); + this.setValueIsAdjusting(false); + } + + /** + * Set the current set of selected objects to the specified objects. + * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int) + */ + public void setSelectedValues(Collection objects) { + this.setSelectedValues(objects.iterator()); + } + + /** + * Set the current set of selected objects to the specified objects. + * @see javax.swing.ListSelectionModel#setSelectionInterval(int, int) + */ + public void setSelectedValues(Object[] objects) { + this.setSelectedValues(CollectionTools.iterator(objects)); + } + + /** + * Add the specified object to the current set of selected objects. + * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int) + */ + public void addSelectedValue(Object object) { + this.addSelectedValues(CollectionTools.singletonIterator(object)); + } + + /** + * Add the specified objects to the current set of selected objects. + * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int) + */ + public void addSelectedValues(Iterator objects) { + this.setValueIsAdjusting(true); + this.addSelectedValuesInternal(objects); + this.setValueIsAdjusting(false); + } + + /** + * Add the specified objects to the current set of selected objects. + * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int) + */ + public void addSelectedValues(Collection objects) { + this.addSelectedValues(objects.iterator()); + } + + /** + * Add the specified objects to the current set of selected objects. + * @see javax.swing.ListSelectionModel#addSelectionInterval(int, int) + */ + public void addSelectedValues(Object[] objects) { + this.addSelectedValues(CollectionTools.iterator(objects)); + } + + /** + * Remove the specified object from the current set of selected objects. + * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int) + */ + public void removeSelectedValue(Object object) { + this.removeSelectedValues(CollectionTools.singletonIterator(object)); + } + + /** + * Remove the specified objects from the current set of selected objects. + * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int) + */ + public void removeSelectedValues(Iterator objects) { + this.setValueIsAdjusting(true); + ListModel lm = this.getListModel(); + int lmSize = lm.getSize(); + while (objects.hasNext()) { + int index = this.indexOf(objects.next(), lm, lmSize); + this.removeSelectionInterval(index, index); + } + this.setValueIsAdjusting(false); + } + + /** + * Remove the specified objects from the current set of selected objects. + * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int) + */ + public void removeSelectedValues(Collection objects) { + this.removeSelectedValues(objects.iterator()); + } + + /** + * Remove the specified objects from the current set of selected objects. + * @see javax.swing.ListSelectionModel#removeSelectionInterval(int, int) + */ + public void removeSelectedValues(Object[] objects) { + this.removeSelectedValues(CollectionTools.iterator(objects)); + } + + /** + * @see javax.swing.ListSelectionModel#getAnchorSelectionIndex() + * Return null if the anchor selection is empty. + */ + public Object getAnchorSelectedValue() { + int index = this.getAnchorSelectionIndex(); + if (index == -1) { + return null; + } + return this.getListModel().getElementAt(index); + } + + /** + * @see javax.swing.ListSelectionModel#setAnchorSelectionIndex(int) + */ + public void setAnchorSelectedValue(Object object) { + this.setAnchorSelectionIndex(this.indexOf(object)); + } + + /** + * @see javax.swing.ListSelectionModel#getLeadSelectionIndex() + * Return null if the lead selection is empty. + */ + public Object getLeadSelectedValue() { + int index = this.getLeadSelectionIndex(); + if (index == -1) { + return null; + } + return this.getListModel().getElementAt(index); + } + + /** + * @see javax.swing.ListSelectionModel#setLeadSelectionIndex(int) + */ + public void setLeadSelectedValue(Object object) { + this.setLeadSelectionIndex(this.indexOf(object)); + } + + /** + * @see javax.swing.ListSelectionModel#getMaxSelectionIndex() + * Return null if the max selection is empty. + */ + public Object getMaxSelectedValue() { + int index = this.getMaxSelectionIndex(); + if (index == -1) { + return null; + } + return this.getListModel().getElementAt(index); + } + + /** + * @see javax.swing.ListSelectionModel#getMinSelectionIndex() + * Return null if the min selection is empty. + */ + public Object getMinSelectedValue() { + int index = this.getMinSelectionIndex(); + if (index == -1) { + return null; + } + return this.getListModel().getElementAt(index); + } + + /** + * @see javax.swing.ListSelectionModel#isSelectedIndex(int) + */ + public boolean valueIsSelected(Object object) { + return this.isSelectedIndex(this.indexOf(object)); + } + + /** + * Add the specified objects to the current set of selected objects, + * without wrapping the actions in "adjusting" events. + */ + private void addSelectedValuesInternal(Iterator objects) { + ListModel lm = this.getListModel(); + int listModelSize = lm.getSize(); + while (objects.hasNext()) { + int index = this.indexOf(objects.next(), lm, listModelSize); + this.addSelectionInterval(index, index); + } + } + + /** + * Return the index in the list model of the specified object. + * Return -1 if the object is not in the list model. + */ + private int indexOf(Object object) { + ListModel lm = this.getListModel(); + return this.indexOf(object, lm, lm.getSize()); + } + + /** + * Return the index in the list model of the specified object. + * Return -1 if the object is not in the list model. + */ + // we're just jerking around with performance optimizations here + // (in memory of Phil...); + // call this method inside loops that do not modify the listModel + private int indexOf(Object object, ListModel lm, int listModelSize) { + for (int i = listModelSize; i-- > 0; ) { + if (lm.getElementAt(i).equals(object)) { + return i; + } + } + return -1; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/PrimitiveListTreeModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/PrimitiveListTreeModel.java new file mode 100644 index 0000000000..9fe9fca0e8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/PrimitiveListTreeModel.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.event.TreeModelListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; + +/** + * This TreeModel implementation provides a tree with a "null" root that + * has a set of "primitive" children. These "primitive" children do not have + * children themselves, making the tree a maximum of 2 levels deep. + * This model automatically synchronizes the root's children with a + * ListValueModel that holds a collection of primitive (non-model) objects + * (e.g. Strings). + * + * This is useful for providing an "editable" list of primitives. Since the JDK + * does not provide us with an editable listbox, we must use an editable tree. + * We wrap everything in DefaultMutableTreeNodes. + * + * Subclasses must implement #primitiveChanged(int, Object) and update + * the model appropriately. This method is called when the user edits the + * list directly and presses . + * + * The JTree using this model must be configured as "editable": + * tree.setEditable(true); + */ +// TODO convert to use an adapter instead of requiring subclass +public abstract class PrimitiveListTreeModel + extends DefaultTreeModel +{ + /** a model on the list of primitives */ + private final ListValueModel listHolder; + + /** a listener that handles the adding, removing, and replacing of the primitives */ + private final ListChangeListener listChangeListener; + + + // ********** constructors ********** + + /** + * Public constructor - the list holder is required + */ + public PrimitiveListTreeModel(ListValueModel listHolder) { + super(new DefaultMutableTreeNode(null, true)); // true = the root can have children + if (listHolder == null) { + throw new NullPointerException(); + } + this.listHolder = listHolder; + this.listChangeListener = this.buildListChangeListener(); + // postpone listening to the model until we have listeners ourselves + } + + protected ListChangeListener buildListChangeListener() { + return new AWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new PrimitiveListChangeListener(); + } + + + // ********** behavior ********** + + /** + * Subclasses should override this method to update the + * model appropriately. The primitive at the specified index was + * edited directly by the user and the new value is as specified. + * Convert the value appropriately and place it in the model. + */ + protected abstract void primitiveChanged(int index, Object newValue); + + + // ********** TreeModel implementation ********** + + /** + * Override to change the underlying model instead of changing the node directly. + */ + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + TreeNode node = (TreeNode) path.getLastPathComponent(); + int index = ((TreeNode) this.getRoot()).getIndex(node); + this.primitiveChanged(index, newValue); + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addTreeModelListener(TreeModelListener l) { + if (this.getTreeModelListeners().length == 0) { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + this.synchronizeList(); + } + super.addTreeModelListener(l); + } + + /** + * Extend to stop listening to the underlying model if appropriate. + */ + @Override + public void removeTreeModelListener(TreeModelListener l) { + super.removeTreeModelListener(l); + if (this.getTreeModelListeners().length == 0) { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + } + + + // ********** behavior ********** + + /** + * Synchronize our list of nodes with the list of primitives + */ + void synchronizeList() { + this.clearList(); + this.buildList(); + } + + void clearList() { + int childcount = this.root.getChildCount(); + for (int i = childcount - 1; i >= 0; i--) { + this.removeNodeFromParent((MutableTreeNode)this.root.getChildAt(i)); + } + } + + private void buildList() { + for (Iterator stream = this.listHolder.iterator(); stream.hasNext(); ) { + this.addPrimitive(stream.next()); + } + } + + /** + * Add the specified primitive to the end of the list. + */ + private void addPrimitive(Object primitive) { + this.insertPrimitive(this.root.getChildCount(), primitive); + } + + /** + * Create a node for the specified primitive + * and insert it as a child of the root. + */ + void insertPrimitive(int index, Object primitive) { + DefaultMutableTreeNode node = new DefaultMutableTreeNode(primitive, false); // don't allow children on the child node + this.insertNodeInto(node, (MutableTreeNode) this.root, index); + } + + /** + * Remove node at the specified index. + */ + MutableTreeNode removeNode(int index) { + MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index); + this.removeNodeFromParent(node); + return node; + } + + /** + * Replace the user object of the node at childIndex. + */ + void replacePrimitive(int index, Object primitive) { + MutableTreeNode node = (MutableTreeNode) this.root.getChildAt(index); + node.setUserObject(primitive); + this.nodeChanged(node); + } + + + // ********** inner class ********** + + private class PrimitiveListChangeListener implements ListChangeListener { + PrimitiveListChangeListener() { + super(); + } + + public void itemsAdded(ListAddEvent event) { + int i = event.getIndex(); + for (Object item : event.getItems()) { + PrimitiveListTreeModel.this.insertPrimitive(i++, item); + } + } + + public void itemsRemoved(ListRemoveEvent event) { + for (int i = 0; i < event.getItemsSize(); i++) { + PrimitiveListTreeModel.this.removeNode(event.getIndex()); + } + } + + public void itemsReplaced(ListReplaceEvent event) { + int i = event.getIndex(); + for (Object item : event.getNewItems()) { + PrimitiveListTreeModel.this.replacePrimitive(i++, item); + } + } + + public void itemsMoved(ListMoveEvent event) { + ArrayList temp = new ArrayList(event.getLength()); + for (int i = 0; i < event.getLength(); i++) { + temp.add(PrimitiveListTreeModel.this.removeNode(event.getSourceIndex())); + } + int i = event.getTargetIndex(); + for (MutableTreeNode node : temp) { + PrimitiveListTreeModel.this.insertPrimitive(i++, node); + } + } + + public void listCleared(ListClearEvent event) { + PrimitiveListTreeModel.this.clearList(); + } + + public void listChanged(ListChangeEvent event) { + PrimitiveListTreeModel.this.synchronizeList(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/RadioButtonModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/RadioButtonModelAdapter.java new file mode 100644 index 0000000000..83d3113454 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/RadioButtonModelAdapter.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import org.eclipse.jpt.common.utility.internal.BidiFilter; +import org.eclipse.jpt.common.utility.internal.BidiTransformer; +import org.eclipse.jpt.common.utility.internal.model.value.FilteringWritablePropertyValueModel; +import org.eclipse.jpt.common.utility.internal.model.value.TransformationWritablePropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.ButtonModel can be used to keep a listener + * (e.g. a JRadioButton) in synch with a (typically shared) + * PropertyValueModel that holds one value out of a set of values. + * + * NOTE: Do *not* use this model with a ButtonGroup, since the + * shared value holder and the wrappers built by this adapter will + * keep the appropriate radio button checked. Also, this allows + * us to uncheck all the radio buttons in a group when the shared + * value is null. + */ +public class RadioButtonModelAdapter + extends ToggleButtonModelAdapter +{ + + // ********** constructors ********** + + /** + * Constructor - the value holder is required. + */ + public RadioButtonModelAdapter(WritablePropertyValueModel valueHolder, Object buttonValue, boolean defaultValue) { + super(buildBooleanHolder(valueHolder, buttonValue), defaultValue); + } + + /** + * Constructor - the value holder is required. + * The default value will be false. + */ + public RadioButtonModelAdapter(WritablePropertyValueModel valueHolder, Object buttonValue) { + super(buildBooleanHolder(valueHolder, buttonValue)); + } + + + // ********** static methods ********** + + /** + * Build up a set of wrappers that will convert the + * specified value holder and button value to/from a boolean. + * + * If the value holder's value matches the button value, + * the wrapper will return true. Likewise, if the value holder's + * value is set to true, the wrapper will set the value holder's + * value to the button value. + */ + public static WritablePropertyValueModel buildBooleanHolder(WritablePropertyValueModel valueHolder, Object buttonValue) { + WritablePropertyValueModel filteringPVM = new FilteringWritablePropertyValueModel(valueHolder, new RadioButtonFilter(buttonValue)); + return new TransformationWritablePropertyValueModel(filteringPVM, new RadioButtonTransformer(buttonValue)); + } + + + // ********** overrides ********** + + /** + * The user cannot de-select a radio button - the user + * can only *select* a radio button. Only the model can + * cause a radio button to be de-selected. We use the + * ARMED flag to indicate whether we are being de-selected + * by the user. + */ + @Override + public void setSelected(boolean b) { + // do not allow the user to de-select a radio button + // radio buttons can + if ((b == false) && this.isArmed()) { + return; + } + super.setSelected(b); + } + + + // ********** inner classes ********** + + /** + * This filter will only pass through a new value to the wrapped + * value holder when it matches the configured button value. + */ + public static class RadioButtonFilter implements BidiFilter { + private Object buttonValue; + + public RadioButtonFilter(Object buttonValue) { + super(); + this.buttonValue = buttonValue; + } + + /** + * always return the wrapped value + */ + public boolean accept(Object value) { + return true; + } + + /** + * pass through the value to the wrapped property value model + * *only* when it matches our button value + */ + public boolean reverseAccept(Object value) { + return (value != null) && value.equals(this.buttonValue); + } + + } + + /** + * This transformer will convert the wrapped value to Boolean.TRUE + * when it matches the configured button value. + */ + public static class RadioButtonTransformer implements BidiTransformer { + private Object buttonValue; + + public RadioButtonTransformer(Object buttonValue) { + super(); + this.buttonValue = buttonValue; + } + + /** + * if the wrapped value matches our button value return true, + * if it is some other value return false; + * but if it is null simply pass it through because it will cause the + * button model's default value to be used + */ + public Boolean transform(Object value) { + return (value == null) ? null : Boolean.valueOf(value.equals(this.buttonValue)); + } + + /** + * if the new value is true, pass through the our button value; + * otherwise pass through null + */ + public Object reverseTransform(Boolean value) { + return (value.booleanValue()) ? this.buttonValue : null; + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/SpinnerModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/SpinnerModelAdapter.java new file mode 100644 index 0000000000..2490cae9d4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/SpinnerModelAdapter.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import javax.swing.AbstractSpinnerModel; +import javax.swing.SpinnerModel; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.SpinnerModel can be used to keep a ChangeListener + * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a value. + * + * Note: it is likely you want to use one of the following classes instead of + * this one: + * DateSpinnerModelAdapter + * NumberSpinnerModelAdapter + * ListSpinnerModelAdapter + * + * NB: This model should only be used for values that have a fairly + * inexpensive #equals() implementation. + * @see #synchronizeDelegate(Object) + */ +public class SpinnerModelAdapter + extends AbstractSpinnerModel +{ + /** The delegate spinner model whose behavior we "enhance". */ + protected final SpinnerModel delegate; + + /** A listener that allows us to forward any changes made to the delegate spinner model. */ + protected final ChangeListener delegateListener; + + /** A value model on the underlying value. */ + protected final WritablePropertyValueModel valueHolder; + + /** A listener that allows us to synchronize with changes made to the underlying value. */ + protected final PropertyChangeListener valueListener; + + + // ********** constructors ********** + + /** + * Constructor - the value holder and delegate are required. + */ + public SpinnerModelAdapter(WritablePropertyValueModel valueHolder, SpinnerModel delegate) { + super(); + if (valueHolder == null || delegate == null) { + throw new NullPointerException(); + } + this.valueHolder = valueHolder; + this.delegate = delegate; + // postpone listening to the underlying value + // until we have listeners ourselves... + this.valueListener = this.buildValueListener(); + this.delegateListener = this.buildDelegateListener(); + } + + /** + * Constructor - the value holder is required. + * This will wrap a simple number spinner model. + */ + public SpinnerModelAdapter(WritablePropertyValueModel valueHolder) { + this(valueHolder, new SpinnerNumberModel()); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildValueListener() { + return new AWTPropertyChangeListenerWrapper(this.buildValueListener_()); + } + + protected PropertyChangeListener buildValueListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + SpinnerModelAdapter.this.valueChanged(event); + } + @Override + public String toString() { + return "value listener"; //$NON-NLS-1$ + } + }; + } + + /** + * expand access a bit for inner class + */ + @Override + protected void fireStateChanged() { + super.fireStateChanged(); + } + + protected ChangeListener buildDelegateListener() { + return new ChangeListener() { + public void stateChanged(ChangeEvent event) { + // forward the event, with this as the source + SpinnerModelAdapter.this.fireStateChanged(); + } + @Override + public String toString() { + return "delegate listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** SpinnerModel implementation ********** + + public Object getValue() { + return this.delegate.getValue(); + } + + /** + * Extend to update the underlying value directly. + * The resulting event will be ignored: @see #synchronizeDelegate(Object). + */ + public void setValue(Object value) { + this.delegate.setValue(value); + this.valueHolder.setValue(value); + } + + public Object getNextValue() { + return this.delegate.getNextValue(); + } + + public Object getPreviousValue() { + return this.delegate.getPreviousValue(); + } + + /** + * Extend to start listening to the underlying value if necessary. + */ + @Override + public void addChangeListener(ChangeListener listener) { + if (this.listenerList.getListenerCount(ChangeListener.class) == 0) { + this.delegate.addChangeListener(this.delegateListener); + this.engageValueHolder(); + } + super.addChangeListener(listener); + } + + /** + * Extend to stop listening to the underlying value if appropriate. + */ + @Override + public void removeChangeListener(ChangeListener listener) { + super.removeChangeListener(listener); + if (this.listenerList.getListenerCount(ChangeListener.class) == 0) { + this.disengageValueHolder(); + this.delegate.removeChangeListener(this.delegateListener); + } + } + + + // ********** behavior ********** + + /** + * A third party has modified the underlying value. + * Synchronize the delegate model accordingly. + */ + protected void valueChanged(PropertyChangeEvent event) { + this.synchronizeDelegate(event.getNewValue()); + } + + /** + * Set the delegate's value if it has changed. + */ + protected void synchronizeDelegate(Object value) { + // check to see whether the delegate has already been synchronized + // (via #setValue()) + if ( ! this.delegate.getValue().equals(value)) { + this.delegate.setValue(value); + } + } + + protected void engageValueHolder() { + this.valueHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.valueListener); + this.synchronizeDelegate(this.valueHolder.getValue()); + } + + protected void disengageValueHolder() { + this.valueHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.valueListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.valueHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java new file mode 100644 index 0000000000..8c29c1364b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java @@ -0,0 +1,420 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.event.TableModelListener; +import javax.swing.table.AbstractTableModel; + +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.value.CollectionListValueModelAdapter; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This TableModel can be used to keep a TableModelListener (e.g. a JTable) + * in synch with a ListValueModel that holds a collection of model objects, + * each of which corresponds to a row in the table. + * Typically, each column of the table will be bound to a different aspect + * of the contained model objects. + * + * For example, a MWTable has an attribute 'databaseFields' that holds + * a collection of MWDatabaseFields that would correspond to the rows of + * a JTable; and each MWDatabaseField has a number + * of attributes (e.g. name, type, size) that can be bound to the columns of + * a row in the JTable. As these database fields are added, removed, and + * changed, this model will keep the listeners aware of the changes. + * + * An instance of this TableModel must be supplied with a + * list holder (e.g. the 'databaseFields'), which is a value + * model on the bound collection This is required - the + * collection itself can be null, but the list value model that + * holds it is required. Typically this list will be sorted (@see + * SortedListValueModelAdapter). + * + * This TableModel must also be supplied with a ColumnAdapter that + * will be used to configure the headers, renderers, editors, and contents + * of the various columns. + * + * Design decision: + * Cell listener options (from low space/high time to high space/low time): + * - 1 cell listener listening to every cell (this is the current implementation) + * - 1 cell listener per row + * - 1 cell listener per cell + */ +public class TableModelAdapter + extends AbstractTableModel +{ + /** + * a list of user objects that are converted to + * rows via the column adapter + */ + private ListValueModel listHolder; + private final ListChangeListener listChangeListener; + + /** + * each row is an array of cell models + */ + // declare as ArrayList so we can use #ensureCapacity(int) + private final ArrayList[]> rows; + + /** + * client-supplied adapter that provides with the various column + * settings and converts the objects in the LVM + * into an array of cell models + */ + private final ColumnAdapter columnAdapter; + + /** + * the single listener that listens to every cell's model + */ + private final PropertyChangeListener cellListener; + + + // ********** constructors ********** + + /** + * Construct a table model adapter for the specified objects + * and adapter. + */ + public TableModelAdapter(ListValueModel listHolder, ColumnAdapter columnAdapter) { + super(); + if (listHolder == null) { + throw new NullPointerException(); + } + this.listHolder = listHolder; + this.columnAdapter = columnAdapter; + this.listChangeListener = this.buildListChangeListener(); + this.rows = new ArrayList[]>(); + this.cellListener = this.buildCellListener(); + } + + /** + * Construct a table model adapter for the specified objects + * and adapter. + */ + public TableModelAdapter(CollectionValueModel collectionHolder, ColumnAdapter columnAdapter) { + this(new CollectionListValueModelAdapter(collectionHolder), columnAdapter); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new AWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + TableModelAdapter.this.addRows(event.getIndex(), event.getItemsSize(), this.getItems(event)); + } + public void itemsRemoved(ListRemoveEvent event) { + TableModelAdapter.this.removeRows(event.getIndex(), event.getItemsSize()); + } + public void itemsReplaced(ListReplaceEvent event) { + TableModelAdapter.this.replaceRows(event.getIndex(), this.getNewItems(event)); + } + public void itemsMoved(ListMoveEvent event) { + TableModelAdapter.this.moveRows(event.getTargetIndex(), event.getSourceIndex(), event.getLength()); + } + public void listCleared(ListClearEvent event) { + TableModelAdapter.this.clearTable(); + } + public void listChanged(ListChangeEvent event) { + TableModelAdapter.this.rebuildTable(); + } + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getItems(ListAddEvent event) { + return (Iterable) event.getItems(); + } + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable getNewItems(ListReplaceEvent event) { + return (Iterable) event.getNewItems(); + } + @Override + public String toString() { + return "list listener"; //$NON-NLS-1$ + } + }; + } + + + protected PropertyChangeListener buildCellListener() { + return new AWTPropertyChangeListenerWrapper(this.buildCellListener_()); + } + + protected PropertyChangeListener buildCellListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent event) { + TableModelAdapter.this.cellChanged((WritablePropertyValueModel) event.getSource()); + } + @Override + public String toString() { + return "cell listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** TableModel implementation ********** + + public int getColumnCount() { + return this.columnAdapter.columnCount(); + } + + public int getRowCount() { + return this.rows.size(); + } + + @Override + public String getColumnName(int column) { + return this.columnAdapter.columnName(column); + } + + @Override + public Class getColumnClass(int columnIndex) { + return this.columnAdapter.columnClass(columnIndex); + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return this.columnAdapter.columnIsEditable(columnIndex); + } + + public Object getValueAt(int rowIndex, int columnIndex) { + WritablePropertyValueModel[] row = this.rows.get(rowIndex); + return row[columnIndex].getValue(); + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + WritablePropertyValueModel[] row = this.rows.get(rowIndex); + row[columnIndex].setValue(value); + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addTableModelListener(TableModelListener l) { + if (this.hasNoTableModelListeners()) { + this.engageModel(); + } + super.addTableModelListener(l); + } + + /** + * Extend to stop listening to the underlying model if necessary. + */ + @Override + public void removeTableModelListener(TableModelListener l) { + super.removeTableModelListener(l); + if (this.hasNoTableModelListeners()) { + this.disengageModel(); + } + } + + + // ********** public API ********** + + /** + * Return the underlying list model. + */ + public ListValueModel getModel() { + return this.listHolder; + } + + /** + * Set the underlying list model. + */ + public void setModel(ListValueModel listHolder) { + if (listHolder == null) { + throw new NullPointerException(); + } + boolean hasListeners = this.hasTableModelListeners(); + if (hasListeners) { + this.disengageModel(); + } + this.listHolder = listHolder; + if (hasListeners) { + this.engageModel(); + this.fireTableDataChanged(); + } + } + + /** + * Set the underlying collection model. + */ + public void setModel(CollectionValueModel collectionHolder) { + this.setModel(new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** queries ********** + + /** + * Return whether this model has no listeners. + */ + protected boolean hasNoTableModelListeners() { + return this.listenerList.getListenerCount(TableModelListener.class) == 0; + } + + /** + * Return whether this model has any listeners. + */ + protected boolean hasTableModelListeners() { + return ! this.hasNoTableModelListeners(); + } + + + // ********** behavior ********** + + /** + * Start listening to the list of objects and the various aspects + * of the objects that make up the rows. + */ + private void engageModel() { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + this.engageAllCells(); + } + + /** + * Convert the objects into rows and listen to the cells. + */ + private void engageAllCells() { + this.rows.ensureCapacity(this.listHolder.size()); + for (Iterator stream = this.listHolder.iterator(); stream.hasNext(); ) { + WritablePropertyValueModel[] row = this.columnAdapter.cellModels(stream.next()); + this.engageRow(row); + this.rows.add(row); + } + } + + /** + * Listen to the cells in the specified row. + */ + private void engageRow(WritablePropertyValueModel[] row) { + for (int i = row.length; i-- > 0; ) { + row[i].addPropertyChangeListener(PropertyValueModel.VALUE, this.cellListener); + } + } + + /** + * Stop listening. + */ + private void disengageModel() { + this.disengageAllCells(); + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + private void disengageAllCells() { + for (WritablePropertyValueModel[] row : this.rows) { + this.disengageRow(row); + } + this.rows.clear(); + } + + private void disengageRow(WritablePropertyValueModel[] row) { + for (int i = row.length; i-- > 0; ) { + row[i].removePropertyChangeListener(PropertyValueModel.VALUE, this.cellListener); + } + } + + /** + * brute-force search for the cell(s) that changed... + */ + void cellChanged(WritablePropertyValueModel cellHolder) { + for (int i = this.rows.size(); i-- > 0; ) { + WritablePropertyValueModel[] row = this.rows.get(i); + for (int j = row.length; j-- > 0; ) { + if (row[j] == cellHolder) { + this.fireTableCellUpdated(i, j); + } + } + } + } + + /** + * convert the items to rows + */ + void addRows(int index, int size, Iterable items) { + List[]> newRows = new ArrayList[]>(size); + for (Object item : items) { + WritablePropertyValueModel[] row = this.columnAdapter.cellModels(item); + this.engageRow(row); + newRows.add(row); + } + this.rows.addAll(index, newRows); + this.fireTableRowsInserted(index, index + size - 1); + } + + void removeRows(int index, int size) { + for (int i = 0; i < size; i++) { + this.disengageRow(this.rows.remove(index)); + } + this.fireTableRowsDeleted(index, index + size - 1); + } + + void replaceRows(int index, Iterable items) { + int i = index; + for (Object item : items) { + WritablePropertyValueModel[] row = this.rows.get(i); + this.disengageRow(row); + row = this.columnAdapter.cellModels(item); + this.engageRow(row); + this.rows.set(i, row); + i++; + } + this.fireTableRowsUpdated(index, i - 1); + } + + void moveRows(int targetIndex, int sourceIndex, int length) { + ArrayList[]> temp = new ArrayList[]>(length); + for (int i = 0; i < length; i++) { + temp.add(this.rows.remove(sourceIndex)); + } + this.rows.addAll(targetIndex, temp); + + int start = Math.min(targetIndex, sourceIndex); + int end = Math.max(targetIndex, sourceIndex) + length - 1; + this.fireTableRowsUpdated(start, end); + } + + void clearTable() { + this.disengageAllCells(); + this.fireTableDataChanged(); + } + + void rebuildTable() { + this.disengageAllCells(); + this.engageAllCells(); + this.fireTableDataChanged(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ToggleButtonModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ToggleButtonModelAdapter.java new file mode 100644 index 0000000000..044b2658a5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ToggleButtonModelAdapter.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.awt.event.ActionListener; +import java.awt.event.ItemListener; +import javax.swing.JToggleButton.ToggleButtonModel; +import javax.swing.event.ChangeListener; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.WritablePropertyValueModel; + +/** + * This javax.swing.ButtonModel can be used to keep a listener + * (e.g. a JCheckBox or a JRadioButton) in synch with a PropertyValueModel + * on a boolean. + */ +public class ToggleButtonModelAdapter + extends ToggleButtonModel +{ + /** + * The default setting for the toggle button; for when the underlying model is null. + * The default [default value] is false (i.e. the toggle button is unchecked/empty). + */ + protected final boolean defaultValue; + + /** A value model on the underlying model boolean. */ + protected final WritablePropertyValueModel booleanHolder; + + /** + * A listener that allows us to synchronize with + * changes made to the underlying model boolean. + */ + protected final PropertyChangeListener booleanChangeListener; + + + // ********** constructors ********** + + /** + * Constructor - the boolean holder is required. + */ + public ToggleButtonModelAdapter(WritablePropertyValueModel booleanHolder, boolean defaultValue) { + super(); + if (booleanHolder == null) { + throw new NullPointerException(); + } + this.booleanHolder = booleanHolder; + this.booleanChangeListener = this.buildBooleanChangeListener(); + // postpone listening to the underlying model + // until we have listeners ourselves... + this.defaultValue = defaultValue; + } + + /** + * Constructor - the boolean holder is required. + * The default value will be false. + */ + public ToggleButtonModelAdapter(WritablePropertyValueModel booleanHolder) { + this(booleanHolder, false); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildBooleanChangeListener() { + return new AWTPropertyChangeListenerWrapper(this.buildBooleanChangeListener_()); + } + + protected PropertyChangeListener buildBooleanChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + ToggleButtonModelAdapter.this.booleanChanged(event); + } + @Override + public String toString() { + return "boolean listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** ButtonModel implementation ********** + + /** + * Extend to update the underlying model if necessary. + */ + @Override + public void setSelected(boolean b) { + if (this.isSelected() != b) { // stop the recursion! + super.setSelected(b);//put the super call first, otherwise the following gets called twice + this.booleanHolder.setValue(Boolean.valueOf(b)); + } + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addActionListener(ActionListener l) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addActionListener(l); + } + + /** + * Extend to stop listening to the underlying model if appropriate. + */ + @Override + public void removeActionListener(ActionListener l) { + super.removeActionListener(l); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addItemListener(ItemListener l) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addItemListener(l); + } + + /** + * Extend to stop listening to the underlying model if appropriate. + */ + @Override + public void removeItemListener(ItemListener l) { + super.removeItemListener(l); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addChangeListener(ChangeListener l) { + if (this.hasNoListeners()) { + this.engageModel(); + } + super.addChangeListener(l); + } + + /** + * Extend to stop listening to the underlying model if appropriate. + */ + @Override + public void removeChangeListener(ChangeListener l) { + super.removeChangeListener(l); + if (this.hasNoListeners()) { + this.disengageModel(); + } + } + + + // ********** queries ********** + + /** + * Return whether we have no listeners at all. + */ + protected boolean hasNoListeners() { + return this.listenerList.getListenerCount() == 0; + } + + protected boolean getDefaultValue() { + return this.defaultValue; + } + + + // ********** behavior ********** + + /** + * Synchronize with the specified value. + * If it is null, use the default value (which is typically false). + */ + protected void setSelected(Boolean value) { + if (value == null) { + this.setSelected(this.getDefaultValue()); + } else { + this.setSelected(value.booleanValue()); + } + } + + /** + * The underlying model has changed - synchronize accordingly. + */ + protected void booleanChanged(PropertyChangeEvent event) { + this.setSelected((Boolean) event.getNewValue()); + } + + protected void engageModel() { + this.booleanHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + this.setSelected(this.booleanHolder.getValue()); + } + + protected void disengageModel() { + this.booleanHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.booleanHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TreeModelAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TreeModelAdapter.java new file mode 100644 index 0000000000..12f0faf3a7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TreeModelAdapter.java @@ -0,0 +1,914 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.model.value.swing; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreePath; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTListChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.listener.awt.AWTStateChangeListenerWrapper; +import org.eclipse.jpt.common.utility.internal.model.value.StaticPropertyValueModel; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.value.ListValueModel; +import org.eclipse.jpt.common.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.common.utility.model.value.TreeNodeValueModel; + +/** + * This javax.swing.tree.TreeModel can be used to keep a TreeModelListener + * (e.g. a JTree) in synch with a tree of TreeNodeValueModel objects. Unlike + * javax.swing.tree.DefaultTreeModel, you do not add and remove nodes with + * methods implemented here. You can add and remove nodes by adding and + * removing them directly to/from the nodes (or, more typically, the domain + * objects the nodes are wrapping and listening to). + * + * Due to limitations in JTree, the root of the tree can never be null, + * which, typically, should not be a problem. (If you want to display an empty + * tree you can set the JTree's treeModel to null.) + */ +public class TreeModelAdapter + extends AbstractTreeModel +{ + /** + * A value model on the underlying tree's root node and its + * corresponding listener. This allows clients to swap out + * the entire tree. Due to limitations in JTree, the root should + * never be set to null while we have listeners. + */ + private final PropertyValueModel> rootHolder; + private final PropertyChangeListener rootListener; + + /** + * A listener that notifies us when a node's internal + * "state" changes (as opposed to the node's value or list of + * children), allowing us to forward notification to our listeners. + */ + private final StateChangeListener nodeStateListener; + + /** + * A listener that notifies us when a node's "value" + * changes (as opposed to the node's state or list of + * children), allowing us to forward notification to our listeners. + * Typically, this will only happen with nodes that hold + * primitive data. + */ + private final PropertyChangeListener nodeValueListener; + + /** + * A listener that notifies us when an underlying node's + * "list" of children changes, allowing us to keep our + * internal tree in synch with the underlying tree model. + */ + private final ListChangeListener childrenListener; + + /* these attributes make up our internal tree */ + /** + * The root cannot be null while we have listeners, which is + * most of the time. The root is cached so we can disengage + * from it when it has been swapped out. + */ + private TreeNodeValueModel root; + + /** + * Map the nodes to their lists of children. + * We cache these so we can swap out the entire list of children + * when we receive a #listChanged() event (which does not include + * the items that were affected). + * @see EventChangePolicy#rebuildChildren() + */ + final IdentityHashMap, List>> childrenLists; + + /** + * Map the children models to their parents. + * We cache these so we can figure out the "real" source of the + * list change events (the parent). + * @see EventChangePolicy#parent() + */ + final IdentityHashMap>, TreeNodeValueModel> parents; + + + // ********** constructors ********** + + /** + * Construct a tree model for the specified root. + */ + public TreeModelAdapter(PropertyValueModel> rootHolder) { + super(); + if (rootHolder == null) { + throw new NullPointerException(); + } + this.rootHolder = rootHolder; + this.rootListener = this.buildRootListener(); + this.nodeStateListener = this.buildNodeStateListener(); + this.nodeValueListener = this.buildNodeValueListener(); + this.childrenListener = this.buildChildrenListener(); + this.childrenLists = new IdentityHashMap, List>>(); + this.parents = new IdentityHashMap>, TreeNodeValueModel>(); + } + + /** + * Construct a tree model for the specified root. + */ + public TreeModelAdapter(TreeNodeValueModel root) { + this(new StaticPropertyValueModel>(root)); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildRootListener() { + return new AWTPropertyChangeListenerWrapper(this.buildRootListener_()); + } + + protected PropertyChangeListener buildRootListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + TreeModelAdapter.this.rootChanged(); + } + @Override + public String toString() { + return "root listener"; //$NON-NLS-1$ + } + }; + } + + protected PropertyChangeListener buildNodeValueListener() { + return new AWTPropertyChangeListenerWrapper(this.buildNodeValueListener_()); + } + + protected PropertyChangeListener buildNodeValueListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent event) { + TreeModelAdapter.this.nodeChanged((TreeNodeValueModel) event.getSource()); + } + @Override + public String toString() { + return "node value listener"; //$NON-NLS-1$ + } + }; + } + + protected StateChangeListener buildNodeStateListener() { + return new AWTStateChangeListenerWrapper(this.buildNodeStateListener_()); + } + + protected StateChangeListener buildNodeStateListener_() { + return new StateChangeListener() { + @SuppressWarnings("unchecked") + public void stateChanged(StateChangeEvent event) { + TreeModelAdapter.this.nodeChanged((TreeNodeValueModel) event.getSource()); + } + @Override + public String toString() { + return "node state listener"; //$NON-NLS-1$ + } + }; + } + + protected ListChangeListener buildChildrenListener() { + return new AWTListChangeListenerWrapper(this.buildChildrenListener_()); + } + + protected ListChangeListener buildChildrenListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + new AddEventChangePolicy(event).addChildren(); + } + public void itemsRemoved(ListRemoveEvent event) { + new RemoveEventChangePolicy(event).removeChildren(); + } + public void itemsReplaced(ListReplaceEvent event) { + new ReplaceEventChangePolicy(event).replaceChildren(); + } + public void itemsMoved(ListMoveEvent event) { + new MoveEventChangePolicy(event).moveChildren(); + } + public void listCleared(ListClearEvent event) { + new ClearEventChangePolicy(event).clearChildren(); + } + public void listChanged(ListChangeEvent event) { + new ChangeEventChangePolicy(event).rebuildChildren(); + } + @Override + public String toString() { + return "children listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** TreeModel implementation ********** + + public Object getRoot() { + return this.root; + } + + @SuppressWarnings("unchecked") + public Object getChild(Object parent, int index) { + return ((TreeNodeValueModel) parent).child(index); + } + + @SuppressWarnings("unchecked") + public int getChildCount(Object parent) { + return ((TreeNodeValueModel) parent).childrenSize(); + } + + @SuppressWarnings("unchecked") + public boolean isLeaf(Object node) { + return ((TreeNodeValueModel) node).isLeaf(); + } + + @SuppressWarnings("unchecked") + public void valueForPathChanged(TreePath path, Object newValue) { + ((TreeNodeValueModel) path.getLastPathComponent()).setValue((T) newValue); + } + + @SuppressWarnings("unchecked") + public int getIndexOfChild(Object parent, Object child) { + return ((TreeNodeValueModel) parent).indexOfChild((TreeNodeValueModel) child); + } + + /** + * Extend to start listening to the underlying model if necessary. + */ + @Override + public void addTreeModelListener(TreeModelListener l) { + if (this.hasNoTreeModelListeners()) { + this.engageModel(); + } + super.addTreeModelListener(l); + } + + /** + * Extend to stop listening to the underlying model if appropriate. + */ + @Override + public void removeTreeModelListener(TreeModelListener l) { + super.removeTreeModelListener(l); + if (this.hasNoTreeModelListeners()) { + this.disengageModel(); + } + } + + + // ********** behavior ********** + + /** + * Listen to the root and all the other nodes + * in the underlying tree model. + */ + private void engageModel() { + this.rootHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.rootListener); + this.root = this.rootHolder.getValue(); + if (this.root == null) { + throw new NullPointerException(); // the root cannot be null while we have listeners + } + this.engageNode(this.root); + this.addRoot(); + } + + /** + * Add the root and all of the nodes to the underlying tree. + */ + private void addRoot() { + this.addNode(0, this.root); + } + + /** + * Stop listening to the root and all the other + * nodes in the underlying tree model. + */ + private void disengageModel() { + this.removeRoot(); + this.disengageNode(this.root); + this.root = null; + this.rootHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.rootListener); + } + + /** + * Remove the root and all of the nodes from the underlying tree. + */ + private void removeRoot() { + this.removeNode(0, this.root); + } + + /** + * The root has been swapped. + * This method is a bit gnarly because the API for notifying listeners + * that the root has changed is a bit inconsistent with that used for + * non-root nodes. + */ + void rootChanged() { + TreeNodeValueModel newRoot = this.rootHolder.getValue(); + if (newRoot == null) { + throw new NullPointerException(); // the root cannot be null while we have listeners + } + // remove all the current root's children from the tree + // and remove the it from the internal tree + this.removeRoot(); + + // save the old root and swap in the new root + TreeNodeValueModel oldRoot = this.root; + this.root = newRoot; + + // we must be listening to both the old and new roots when we fire the event + // because their values can be affected by whether they have listeners + this.engageNode(this.root); + this.fireTreeRootReplaced(this.root); + // now we can stop listening to the old root + this.disengageNode(oldRoot); + + // add the new root to the internal tree and + // add all its children to the tree also + this.addRoot(); + } + + /** + * Either the "value" or the "state" of the specified node has changed, + * forward notification to our listeners. + */ + void nodeChanged(TreeNodeValueModel node) { + TreeNodeValueModel parent = node.parent(); + if (parent == null) { + this.fireTreeRootChanged(node); + } else { + this.fireTreeNodeChanged(parent.path(), parent.indexOfChild(node), node); + } + } + + /** + * Listen to the nodes, notify our listeners that the nodes were added, + * and then add the nodes to our internal tree. + * We must listen to the nodes before notifying anybody, because + * adding a listener can change the value of a node. + */ + void addChildren(TreeNodeValueModel[] path, int[] childIndices, TreeNodeValueModel[] children) { + int len = childIndices.length; + for (int i = 0; i < len; i++) { + this.engageNode(children[i]); + } + this.fireTreeNodesInserted(path, childIndices, children); + for (int i = 0; i < len; i++) { + this.addNode(childIndices[i], children[i]); + } + } + + /** + * Listen to the node and its children model. + */ + private void engageNode(TreeNodeValueModel node) { + node.addStateChangeListener(this.nodeStateListener); + node.addPropertyChangeListener(PropertyValueModel.VALUE, this.nodeValueListener); + node.childrenModel().addListChangeListener(ListValueModel.LIST_VALUES, this.childrenListener); + } + + /** + * Add the node to our internal tree; + * then recurse down through the node's children, + * adding them to the internal tree also. + */ + private void addNode(int index, TreeNodeValueModel node) { + this.addNodeToInternalTree(node.parent(), index, node, node.childrenModel()); + new NodeChangePolicy(node).addChildren(); + } + + /** + * Add the specified node to our internal tree. + */ + private void addNodeToInternalTree(TreeNodeValueModel parent, int index, TreeNodeValueModel node, ListValueModel> childrenModel) { + List> siblings = this.childrenLists.get(parent); + if (siblings == null) { + siblings = new ArrayList>(); + this.childrenLists.put(parent, siblings); + } + siblings.add(index, node); + + this.parents.put(childrenModel, node); + } + + /** + * Remove nodes from our internal tree, notify our listeners that the + * nodes were removed, then stop listening to the nodes. + * We must listen to the nodes until after notifying anybody, because + * removing a listener can change the value of a node. + */ + void removeChildren(TreeNodeValueModel[] path, int[] childIndices, TreeNodeValueModel[] children) { + int len = childIndices.length; + for (int i = 0; i < len; i++) { + // the indices slide down a notch each time we remove a child + this.removeNode(childIndices[i] - i, children[i]); + } + this.fireTreeNodesRemoved(path, childIndices, children); + for (int i = 0; i < len; i++) { + this.disengageNode(children[i]); + } + } + + /** + * First, recurse down through the node's children, + * removing them from our internal tree; + * then remove the node itself from our internal tree. + */ + private void removeNode(int index, TreeNodeValueModel node) { + new NodeChangePolicy(node).removeChildren(); + this.removeNodeFromInternalTree(node.parent(), index, node.childrenModel()); + } + + /** + * Remove the specified node from our internal tree. + */ + private void removeNodeFromInternalTree(TreeNodeValueModel parent, int index, ListValueModel> childrenModel) { + this.parents.remove(childrenModel); + + List> siblings = this.childrenLists.get(parent); + siblings.remove(index); + if (siblings.isEmpty()) { + this.childrenLists.remove(parent); + } + } + + /** + * Stop listening to the node and its children model. + */ + private void disengageNode(TreeNodeValueModel node) { + node.childrenModel().removeListChangeListener(ListValueModel.LIST_VALUES, this.childrenListener); + node.removePropertyChangeListener(PropertyValueModel.VALUE, this.nodeValueListener); + node.removeStateChangeListener(this.nodeStateListener); + } + + void moveChildren(TreeNodeValueModel parent, int targetIndex, int sourceIndex, int length) { + List> childrenList = this.childrenLists.get(parent); + ArrayList> temp = new ArrayList>(length); + for (int i = 0; i < length; i++) { + temp.add(childrenList.remove(sourceIndex)); + } + childrenList.addAll(targetIndex, temp); + + this.fireTreeStructureChanged(parent.path()); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.root); + } + + + // ********** inner classes ********** + + /** + * Coalesce some of the common change policy behavior. + */ + abstract class ChangePolicy { + + ChangePolicy() { + super(); + } + + /** + * Add the current set of children. + */ + void addChildren() { + TreeModelAdapter.this.addChildren(this.parent().path(), this.childIndices(), this.childArray()); + } + + /** + * Remove the current set of children. + */ + void removeChildren() { + TreeModelAdapter.this.removeChildren(this.parent().path(), this.childIndices(), this.childArray()); + } + + /** + * Return an array of the indices of the current set of children, + * which should be contiguous. + */ + int[] childIndices() { + return this.buildIndices(this.childrenStartIndex(), this.childrenSize()); + } + + /** + * Return an array of the current set of children. + */ + TreeNodeValueModel[] childArray() { + return this.buildArray(this.getChildren(), this.childrenSize()); + } + + /** + * Build an array to hold the elements in the specified iterator. + * If they are different sizes, something is screwed up... + */ + TreeNodeValueModel[] buildArray(Iterable> elements, int size) { + @SuppressWarnings("unchecked") + TreeNodeValueModel[] array = new TreeNodeValueModel[size]; + int i = 0; + for (TreeNodeValueModel element : elements) { + array[i++] = element; + } + return array; + } + + /** + * Return a set of indices, starting at zero and + * continuing for the specified size. + */ + int[] buildIndices(int size) { + return buildIndices(0, size); + } + + /** + * Return a set of indices, starting at the specified index and + * continuing for the specified size. + */ + int[] buildIndices(int start, int size) { + int[] indices = new int[size]; + int index = start; + for (int i = 0; i < size; i++) { + indices[i] = index++; + } + return indices; + } + + /** + * Return the parent of the current set of children. + */ + abstract TreeNodeValueModel parent(); + + /** + * Return the starting index for the current set of children. + */ + abstract int childrenStartIndex(); + + /** + * Return the size of the current set of children. + */ + abstract int childrenSize(); + + /** + * Return the current set of children. + */ + abstract Iterable> getChildren(); + + } + + + /** + * Wraps a ListEvent for adding, removing, replacing, + * and changing children. + */ + abstract class EventChangePolicy extends ChangePolicy { + final ListEvent event; + + EventChangePolicy(ListEvent event) { + super(); + this.event = event; + } + + /** + * Map the ListChangeEvent's source to the corresponding parent. + */ + @Override + TreeNodeValueModel parent() { + return TreeModelAdapter.this.parents.get(this.event.getSource()); + } + + } + + + /** + * Wraps a ListAddEvent for adding children. + */ + class AddEventChangePolicy extends EventChangePolicy { + + AddEventChangePolicy(ListAddEvent event) { + super(event); + } + + private ListAddEvent getEvent() { + return (ListAddEvent) this.event; + } + + /** + * The ListAddEvent's item index is the children start index. + */ + @Override + int childrenStartIndex() { + return this.getEvent().getIndex(); + } + + /** + * The ListAddEvent's size is the children size. + */ + @Override + int childrenSize() { + return this.getEvent().getItemsSize(); + } + + /** + * The ListAddEvent's items are the children. + */ + @Override + @SuppressWarnings("unchecked") + Iterable> getChildren() { + return (Iterable>) this.getEvent().getItems(); + } + + } + + + /** + * Wraps a ListRemoveEvent for adding children. + */ + class RemoveEventChangePolicy extends EventChangePolicy { + + RemoveEventChangePolicy(ListRemoveEvent event) { + super(event); + } + + private ListRemoveEvent getEvent() { + return (ListRemoveEvent) this.event; + } + + /** + * The ListRemoveEvent's item index is the children start index. + */ + @Override + int childrenStartIndex() { + return this.getEvent().getIndex(); + } + + /** + * The ListRemoveEvent's size is the children size. + */ + @Override + int childrenSize() { + return this.getEvent().getItemsSize(); + } + + /** + * The ListRemoveEvent's items are the children. + */ + @Override + @SuppressWarnings("unchecked") + Iterable> getChildren() { + return (Iterable>) this.getEvent().getItems(); + } + + } + + + /** + * Wraps a ListReplaceEvent for replacing children. + */ + class ReplaceEventChangePolicy extends EventChangePolicy { + + ReplaceEventChangePolicy(ListReplaceEvent event) { + super(event); + } + + private ListReplaceEvent getEvent() { + return (ListReplaceEvent) this.event; + } + + /** + * The ListReplaceEvent's item index is the children start index. + */ + @Override + int childrenStartIndex() { + return this.getEvent().getIndex(); + } + + /** + * The ListReplaceEvent's size is the children size. + */ + @Override + int childrenSize() { + return this.getEvent().getItemsSize(); + } + + /** + * The ListReplaceEvent's items are the children. + */ + @Override + @SuppressWarnings("unchecked") + Iterable> getChildren() { + return (Iterable>) this.getEvent().getNewItems(); + } + + /** + * Remove the old nodes and add the new ones. + */ + void replaceChildren() { + TreeNodeValueModel[] parentPath = this.parent().path(); + int[] childIndices = this.childIndices(); + TreeModelAdapter.this.removeChildren(parentPath, childIndices, this.getOldChildren()); + TreeModelAdapter.this.addChildren(parentPath, childIndices, this.childArray()); + } + + TreeNodeValueModel[] getOldChildren() { + return this.buildArray(this.getOldItems(), this.getEvent().getItemsSize()); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable> getOldItems() { + return (Iterable>) this.getEvent().getOldItems(); + } + + } + + + /** + * Wraps a ListMoveEvent for moving children. + */ + class MoveEventChangePolicy extends EventChangePolicy { + + MoveEventChangePolicy(ListMoveEvent event) { + super(event); + } + + private ListMoveEvent getEvent() { + return (ListMoveEvent) this.event; + } + + void moveChildren() { + TreeModelAdapter.this.moveChildren(this.parent(), this.getEvent().getTargetIndex(), this.getEvent().getSourceIndex(), this.getEvent().getLength()); + } + + @Override + int childrenStartIndex() { + throw new UnsupportedOperationException(); + } + + @Override + int childrenSize() { + throw new UnsupportedOperationException(); + } + + @Override + Iterable> getChildren() { + throw new UnsupportedOperationException(); + } + + } + + + /** + * Wraps a ListClearEvent for clearing children. + */ + class ClearEventChangePolicy extends EventChangePolicy { + + ClearEventChangePolicy(ListClearEvent event) { + super(event); + } + + /** + * Clear all the nodes. + */ + void clearChildren() { + TreeNodeValueModel parent = this.parent(); + TreeNodeValueModel[] parentPath = parent.path(); + List> childrenList = TreeModelAdapter.this.childrenLists.get(parent); + int[] childIndices = this.buildIndices(childrenList.size()); + TreeNodeValueModel[] childArray = this.buildArray(childrenList, childrenList.size()); + TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray); + } + + @Override + int childrenStartIndex() { + throw new UnsupportedOperationException(); + } + + @Override + int childrenSize() { + throw new UnsupportedOperationException(); + } + + @Override + Iterable> getChildren() { + throw new UnsupportedOperationException(); + } + + } + + + /** + * Wraps a ListChangeEvent for clearing children. + */ + class ChangeEventChangePolicy extends EventChangePolicy { + + ChangeEventChangePolicy(ListChangeEvent event) { + super(event); + } + + /** + * Remove all the old nodes and add all the new nodes. + */ + void rebuildChildren() { + TreeNodeValueModel parent = this.parent(); + TreeNodeValueModel[] parentPath = parent.path(); + List> childrenList = TreeModelAdapter.this.childrenLists.get(parent); + int[] childIndices = this.buildIndices(childrenList.size()); + TreeNodeValueModel[] childArray = this.buildArray(childrenList, childrenList.size()); + TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray); + + childIndices = this.buildIndices(parent.childrenModel().size()); + childArray = this.buildArray(parent.childrenModel(), parent.childrenSize()); + TreeModelAdapter.this.addChildren(parentPath, childIndices, childArray); + } + + @Override + int childrenStartIndex() { + throw new UnsupportedOperationException(); + } + + @Override + int childrenSize() { + throw new UnsupportedOperationException(); + } + + @Override + Iterable> getChildren() { + throw new UnsupportedOperationException(); + } + + } + + + /** + * Wraps a TreeNodeValueModel for adding and removing its children. + */ + class NodeChangePolicy extends ChangePolicy { + private final TreeNodeValueModel node; + + NodeChangePolicy(TreeNodeValueModel node) { + super(); + this.node = node; + } + + /** + * The node itself is the parent. + */ + @Override + TreeNodeValueModel parent() { + return this.node; + } + + /** + * Since we will always be dealing with all of the node's + * children, the children start index is always zero. + */ + @Override + int childrenStartIndex() { + return 0; + } + + /** + * Since we will always be dealing with all of the node's + * children, the children size is always equal to the size + * of the children model. + */ + @Override + int childrenSize() { + return this.node.childrenModel().size(); + } + + /** + * Since we will always be dealing with all of the node's + * children, the children are all the objects held by + * the children model. + */ + @Override + Iterable> getChildren() { + return this.node.childrenModel(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AbstractNode.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AbstractNode.java new file mode 100644 index 0000000000..3f32a57bfd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AbstractNode.java @@ -0,0 +1,941 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.Vector; + +import org.eclipse.jpt.common.utility.internal.iterators.CloneIterator; +import org.eclipse.jpt.common.utility.internal.iterators.CloneListIterator; +import org.eclipse.jpt.common.utility.internal.iterators.FilteringIterator; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; +import org.eclipse.jpt.common.utility.internal.model.AspectChangeSupport; +import org.eclipse.jpt.common.utility.internal.model.ChangeSupport; + +/** + * Base class for Node classes. + * Provides support for the following: + * initialization + * enforced object identity wrt #equals()/#hashCode() + * containment hierarchy (parent/child) + * user comment + * dirty flag + * problems + * sorting + * + * Typically, subclasses should consider implementing the following methods: + * the appropriate constructors + * (with the appropriately-restrictive type declaration for parent) + * #initialize() + * #initialize(Node parentNode) + * #checkParent(Node parentNode) + * #addChildrenTo(List list) + * #nodeRemoved(Node) + * #validator() + * #transientAspectNames() or + * #addTransientAspectNamesTo(Set transientAspectNames) + * #addProblemsTo(List currentProblems) + * #nonValidatedAspectNames() + * #addNonValidatedAspectNamesTo(Set nonValidatedAspectNames) + * #displayString() + * #toString(StringBuilder sb) + */ +public abstract class AbstractNode + extends AbstractModel + implements Node +{ + + /** Containment hierarchy. */ + private Node parent; // pseudo-final + + /** Track whether the node has changed. */ + private volatile boolean dirty; + private volatile boolean dirtyBranch; + + /** + * The node's problems, as calculated during validation. + * This list should only be modified via a ProblemSynchronizer, + * allowing for asynchronous modification from another thread. + */ + private Vector problems; // pseudo-final + private static final Object[] EMPTY_PROBLEM_MESSAGE_ARGUMENTS = new Object[0]; + + /** + * Cache the node's "branch" problems, as calculated during validation. + * This list should only be modified via a ProblemSynchronizer, + * allowing for asynchronous modification from another thread. + * This must be recalculated every time this node or one of its + * descendants changes it problems. + */ + private Vector branchProblems; // pseudo-final + + /** User comment. */ + private volatile String comment; + + + // ********** static fields ********** + + /** + * Sets of transient aspect names, keyed by class. + * This is built up lazily, as the objects are modified. + */ + private static final HashMap, HashSet> transientAspectNameSets = new HashMap, HashSet>(); + + /** + * Sets of non-validated aspect names, keyed by class. + * This is built up lazily, as the objects are modified. + */ + private static final HashMap, HashSet> nonValidatedAspectNameSets = new HashMap, HashSet>(); + + + // ********** constructors ********** + + /** + * Most objects must have a parent. + * Use this constructor to create a new node. + * @see #initialize(Node) + */ + protected AbstractNode(Node parent) { + super(); + this.initialize(); + this.initialize(parent); + } + + + // ********** initialization ********** + + /** + * Initialize a newly-created instance. + * @see #initialize(Node) + */ + protected void initialize() { + this.comment = ""; //$NON-NLS-1$ + + // a new object is dirty, by definition + this.dirty = true; + this.dirtyBranch = true; + + this.problems = new Vector(); + this.branchProblems = new Vector(); + + // when you override this method, don't forget to include: + // super.initialize(); + } + + /** + * Initialize a newly-created instance. + * @see #initialize() + */ + protected void initialize(Node parentNode) { + this.checkParent(parentNode); + this.parent = parentNode; + // when you override this method, don't forget to include: + // super.initialize(parentNode); + } + + @Override + protected ChangeSupport buildChangeSupport() { + return new AspectChangeSupport(this, this.buildChangeSupportListener()); + } + + protected AspectChangeSupport.Listener buildChangeSupportListener() { + return new AspectChangeSupport.Listener() { + public void aspectChanged(String aspectName) { + AbstractNode.this.aspectChanged(aspectName); + } + }; + } + + + // ********** equality ********** + + /** + * Enforce object identity - do not allow objects to be equal unless + * they are the same object. + * Do NOT override this method - we rely on object identity extensively. + */ + @Override + public final boolean equals(Object o) { + return this == o; + } + + /** + * Enforce object identity - do not allow objects to be equal unless + * they are the same object. + * Do NOT override this method - we rely on object identity extensively. + */ + @Override + public final int hashCode() { + return super.hashCode(); + } + + + // ********** containment hierarchy (parent/children) ********** + + /** + * INTRA-TREE API? + * Return the node's parent in the containment hierarchy. + * Most nodes must have a parent. + * @see #children() + */ + public Node getParent() { + return this.parent; + } + + /** + * Throw an IllegalArgumentException if the parent is not valid + * for the node. + * By default require a non-null parent. Override if other restrictions exist + * or the parent should be null. + * NB: Root node model implementations will need to override this method. + */ + protected void checkParent(Node parentNode) { + if (parentNode == null) { + throw new IllegalArgumentException("The parent node cannot be null"); //$NON-NLS-1$ + } + } + + /** + * INTRA-TREE API? + * Return the node's children, which are also nodes. + * Do NOT override this method. + * Override #addChildrenTo(List). + * @see #getParent() + * @see #addChildrenTo(java.util.List) + */ + public final Iterator children() { + List children = new ArrayList(); + this.addChildrenTo(children); + return children.iterator(); + } + + /** + * Subclasses should override this method to add their children + * to the specified list. + * @see #children() + */ + protected void addChildrenTo(@SuppressWarnings("unused") List list) { + // this class has no children, subclasses will... + // when you override this method, don't forget to include: + // super.addChildrenTo(list); + } + + /** + * INTRA-TREE API? + * Return the containment hierarchy's root node. + * Most nodes must have a root. + * @see #getParent() + * NB: Assume the root has no parent. + */ + public Node root() { + Node p = this.parent; + return (p == null) ? this : p.root(); + } + + /** + * Return whether the node is a descendant of the specified node. + * By definition, a node is a descendant of itself. + */ + public boolean isDescendantOf(Node node) { + return (this == node) || this.parentIsDescendantOf(node); + } + + protected boolean parentIsDescendantOf(Node node) { + return (this.parent != null) && this.parent.isDescendantOf(node); + } + + /** + * Return a collection holding all the node's "references", and all + * the node's descendants' "references". "References" are + * objects that are "referenced" by another object, as opposed + * to "owned" by another object. + */ + public Iterator branchReferences() { + Collection branchReferences = new ArrayList(1000); // start big + this.addBranchReferencesTo(branchReferences); + return branchReferences.iterator(); + } + + /** + * INTRA-TREE API + * Add the node's "references", and all the node's descendants' + * "references", to the specified collection. "References" are + * objects that are "referenced" by another object, as opposed + * to "owned" by another object. + * This method is of particular concern to Handles, since most + * (hopefully all) "references" are held by Handles. + * @see Reference + * @see #children() + */ + public void addBranchReferencesTo(Collection branchReferences) { + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.addBranchReferencesTo(branchReferences); + } + } + + /** + * Return all the nodes in the object's branch of the tree, + * including the node itself. The nodes will probably returned + * in "depth-first" order. + * Only really used for testing and debugging. + */ + public Iterator allNodes() { + Collection nodes = new ArrayList(1000); // start big + this.addAllNodesTo(nodes); + return nodes.iterator(); + } + + /** + * INTRA-TREE API? + * Add all the nodes in the object's branch of the tree, + * including the node itself, to the specified collection. + * Only really used for testing and debugging. + */ + public void addAllNodesTo(Collection nodes) { + nodes.add(this); + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.addAllNodesTo(nodes); + } + } + + + // ********** model synchronization support ********** + + /** + * INTRA-TREE API + * This is a general notification that the specified node has been + * removed from the tree. The node receiving this notification + * should perform any necessary updates to remain in synch + * with the tree (e.g. clearing out or replacing any references + * to the removed node or any of the removed node's descendants). + * @see #isDescendantOf(Node) + */ + public void nodeRemoved(Node node) { + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.nodeRemoved(node); + } + // when you override this method, don't forget to include: + // super.nodeRemoved(node); + } + + /** + * convenience method + * return whether node1 is a descendant of node2; + * node1 can be null + */ + protected boolean nodeIsDescendantOf(Node node1, Node node2) { + return (node1 != null) && node1.isDescendantOf(node2); + } + + /** + * INTRA-TREE API + * This is a general notification that the specified node has been + * renamed. The node receiving this notification should mark its + * branch dirty if necessary (i.e. it references the renamed node + * or one of its descendants). This method is of particular concern + * to Handles. + * @see #isDescendantOf(Node) + */ + public void nodeRenamed(Node node) { + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.nodeRenamed(node); + } + // when you override this method, don't forget to include: + // super.nodeRenamed(node); + } + + + // ********** user comment ********** + + /** + * Return the object's user comment. + */ + public final String comment() { + return this.comment; + } + + /** + * Set the object's user comment. + */ + public final void setComment(String comment) { + Object old = this.comment; + this.comment = comment; + this.firePropertyChanged(COMMENT_PROPERTY, old, comment); + } + + + // ********** change support ********** + + /** + * An aspect of the node has changed: + * - if it is a persistent aspect, mark the object dirty + * - if it is a significant aspect, validate the object + */ + protected void aspectChanged(String aspectName) { + if (this.aspectIsPersistent(aspectName)) { + // System.out.println(Thread.currentThread() + " dirty change: " + this + ": " + aspectName); + this.markDirty(); + } + if (this.aspectChangeRequiresValidation(aspectName)) { + // System.out.println(Thread.currentThread() + " validation change: " + this + ": " + aspectName); + this.validate(); + } + } + + protected void validate() { + this.getValidator().validate(); + } + + /** + * INTRA-TREE API + * Return a validator that will be invoked whenever a + * "validated" aspect of the node tree changes. + * Typically only the root node directly holds a validator. + * NB: Root node model implementations will need to override this method. + */ + public Node.Validator getValidator() { + if (this.parent == null) { + throw new IllegalStateException("This node should not be firing change events during its construction."); //$NON-NLS-1$ + } + return this.parent.getValidator(); + } + + /** + * Set a validator that will be invoked whenever a + * "validated" aspect of the node tree changes. + * Typically only the root node directly holds a validator. + * NB: Root node model implementations will need to override this method. + */ + public void setValidator(Node.Validator validator) { + if (this.parent == null) { + throw new IllegalStateException("This root node should implement #setValidator(Node.Validator)."); //$NON-NLS-1$ + } + throw new UnsupportedOperationException("Only root nodes implement #setValidator(Node.Validator)."); //$NON-NLS-1$ + } + + + // ********** dirty flag support ********** + + /** + * Return whether any persistent aspects of the object + * have changed since the object was last read or saved. + * This does NOT include changes to the object's descendants. + */ + public final boolean isDirty() { + return this.dirty; + } + + /** + * Return whether any persistent aspects of the object, + * or any of its descendants, have changed since the object and + * its descendants were last read or saved. + */ + public final boolean isDirtyBranch() { + return this.dirtyBranch; + } + + /** + * Return whether the object is unmodified + * since it was last read or saved. + * This does NOT include changes to the object's descendants. + */ + public final boolean isClean() { + return ! this.dirty; + } + + /** + * Return whether the object and all of its descendants + * are unmodified since the object and + * its descendants were last read or saved. + */ + public final boolean isCleanBranch() { + return ! this.dirtyBranch; + } + + /** + * Set the dirty branch flag setting. This is set to true + * when either the object or one of its descendants becomes dirty. + */ + private void setIsDirtyBranch(boolean dirtyBranch) { + boolean old = this.dirtyBranch; + this.dirtyBranch = dirtyBranch; + this.firePropertyChanged(DIRTY_BRANCH_PROPERTY, old, dirtyBranch); + } + + /** + * Mark the object as dirty and as a dirty branch. + * An object is marked dirty when either a "persistent" attribute + * has changed or its save location has changed. + */ + private void markDirty() { + this.dirty = true; + this.markBranchDirty(); + } + + /** + * INTRA-TREE API + * Mark the node and its parent as dirty branches. + * This message is propagated up the containment + * tree when a particular node becomes dirty. + */ + public void markBranchDirty() { + // short-circuit any unnecessary propagation + if (this.dirtyBranch) { + // if this is already a dirty branch, the parent must be also + return; + } + + this.setIsDirtyBranch(true); + this.markParentBranchDirty(); + } + + protected void markParentBranchDirty() { + if (this.parent != null) { + this.parent.markBranchDirty(); + } + } + + /** + * Mark the object and all its descendants as dirty. + * This is used when the save location of some + * top-level object is changed and the entire + * containment tree must be marked dirty so it + * will be written out. + */ + public final void markEntireBranchDirty() { + this.markDirty(); + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.markEntireBranchDirty(); + } + } + + /** + * Mark the object and all its descendants as clean. + * Then notify the object's parent that it (the parent) + * might now be a clean branch also. + * Typically used when the object has just been + * read in or written out. + */ + public final void markEntireBranchClean() { + this.cascadeMarkEntireBranchClean(); + this.markParentBranchCleanIfPossible(); + } + + protected void markParentBranchCleanIfPossible() { + if (this.parent != null) { + this.parent.markBranchCleanIfPossible(); + } + } + + /** + * INTRA-TREE API + * Mark the node and all its descendants as clean. + * Typically used when the node has just been + * read in or written out. + * This method is for internal use only; it is not for + * client use. + * Not the best of method names.... :-( + */ + public final void cascadeMarkEntireBranchClean() { + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.cascadeMarkEntireBranchClean(); + } + this.dirty = false; + this.setIsDirtyBranch(false); + } + + /** + * INTRA-TREE API + * A child node's branch has been marked clean. If the node + * itself is clean and if all of its children are also clean, the + * node's branch can be marked clean. Then, if the node's + * branch is clean, the node will notify its parent that it might + * be clean also. This message is propagated up the containment + * tree when a particular node becomes clean. + */ + public final void markBranchCleanIfPossible() { + // short-circuit any unnecessary propagation + if (this.dirty) { + // if the object is "locally" dirty, it is still a dirty branch + return; + } + + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + if (child.isDirtyBranch()) { + return; + } + } + + this.setIsDirtyBranch(false); + this.markParentBranchCleanIfPossible(); + } + + private boolean aspectIsPersistent(String aspectName) { + return ! this.aspectIsTransient(aspectName); + } + + private boolean aspectIsTransient(String aspectName) { + return this.transientAspectNames().contains(aspectName); + } + + /** + * Return a set of the object's transient aspect names. + * These are the aspects that, when they change, will NOT cause the + * object to be marked dirty. + * If you need instance-based calculation of your transient aspects, + * override this method. If class-based calculation is sufficient, + * override #addTransientAspectNamesTo(Set). + */ + protected final Set transientAspectNames() { + synchronized (transientAspectNameSets) { + HashSet transientAspectNames = transientAspectNameSets.get(this.getClass()); + if (transientAspectNames == null) { + transientAspectNames = new HashSet(); + this.addTransientAspectNamesTo(transientAspectNames); + transientAspectNameSets.put(this.getClass(), transientAspectNames); + } + return transientAspectNames; + } + } + + /** + * Add the object's transient aspect names to the specified set. + * These are the aspects that, when they change, will NOT cause the + * object to be marked dirty. + * If class-based calculation of your transient aspects is sufficient, + * override this method. If you need instance-based calculation, + * override #transientAspectNames(). + */ + protected void addTransientAspectNamesTo(Set transientAspectNames) { + transientAspectNames.add(DIRTY_BRANCH_PROPERTY); + transientAspectNames.add(BRANCH_PROBLEMS_LIST); + transientAspectNames.add(HAS_BRANCH_PROBLEMS_PROPERTY); + // when you override this method, don't forget to include: + // super.addTransientAspectNamesTo(transientAspectNames); + } + + /** + * Return the dirty nodes in the object's branch of the tree, + * including the node itself (if appropriate). + * Only really used for testing and debugging. + */ + public final Iterator allDirtyNodes() { + return new FilteringIterator(this.allNodes()) { + @Override + protected boolean accept(Node node) { + return (node instanceof AbstractNode) && ((AbstractNode) node).isDirty(); + } + }; + } + + + // ********** problems ********** + + /** + * Return the node's problems. + * This does NOT include the problems of the node's descendants. + * @see #branchProblems() + */ + public final Iterator problems() { + return new CloneIterator(this.problems); // removes are not allowed + } + + /** + * Return the size of the node's problems. + * This does NOT include the problems of the node's descendants. + * @see #branchProblemsSize() + */ + public final int problemsSize() { + return this.problems.size(); + } + + /** + * Return whether the node has problems + * This does NOT include the problems of the node's descendants. + * @see #hasBranchProblems() + */ + public final boolean hasProblems() { + return ! this.problems.isEmpty(); + } + + /** + * Return all the node's problems along with all the + * node's descendants' problems. + */ + public final ListIterator branchProblems() { + return new CloneListIterator(this.branchProblems); // removes are not allowed + } + + /** + * Return the size of all the node's problems along with all the + * node's descendants' problems. + */ + public final int branchProblemsSize() { + return this.branchProblems.size(); + } + + /** + * Return whether the node or any of its descendants have problems. + */ + public final boolean hasBranchProblems() { + return ! this.branchProblems.isEmpty(); + } + + public final boolean containsBranchProblem(Problem problem) { + return this.branchProblems.contains(problem); + } + + protected final Problem buildProblem(String messageKey, int messageType, Object... messageArguments) { + return new DefaultProblem(this, messageKey, messageType, messageArguments); + } + + protected final Problem buildProblem(String messageKey, int messageType) { + return this.buildProblem(messageKey, messageType, EMPTY_PROBLEM_MESSAGE_ARGUMENTS); + } + + /** + * Validate the node and all of its descendants, + * and update their sets of "branch" problems. + * If the node's "branch" problems have changed, + * notify the node's parent. + */ + public void validateBranch() { + if (this.validateBranchInternal()) { + // if our "branch" problems have changed, then + // our parent must rebuild its "branch" problems also + this.rebuildParentBranchProblems(); + } + } + + protected void rebuildParentBranchProblems() { + if (this.parent != null) { + this.parent.rebuildBranchProblems(); + } + } + + /** + * INTRA-TREE API + * Validate the node and all of its descendants, + * and update their sets of "branch" problems. + * Return true if the collection of "branch" problems has changed. + * This method is for internal use only; it is not for + * client use. + */ + public boolean validateBranchInternal() { + // rebuild "branch" problems in children first + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + // ignore the return value because we are going to rebuild our "branch" + // problems no matter what, to see if they have changed + child.validateBranchInternal(); + } + + this.problems.clear(); + this.addProblemsTo(this.problems); + + return this.checkBranchProblems(); + } + + /** + * Check for any problems and add them to the specified list. + * This method should ONLY add problems for this particular node; + * it should NOT add problems for any of this node's descendants + * or ancestors. (Although there will be times when it is debatable + * as to which node a problem "belongs" to....) + * + * NB: This method should NOT modify ANY part of the node's state! + * It is a READ-ONLY behavior. ONLY the list of current problems + * passed in to the method should be modified. + */ + protected void addProblemsTo(@SuppressWarnings("unused") List currentProblems) { + // The default is to do nothing. + // When you override this method, don't forget to include: + // super.addProblemsTo(currentProblems); + } + + /** + * Rebuild the "branch" problems and return whether they have + * changed. + * NB: The entire collection of "branch" problems must be re-calculated + * with EVERY "significant" change - we cannot keep it in synch via + * change notifications because if a descendant with problems is + * removed or replaced we will not receive notification that its + * problems were removed from our "branch" problems. + */ + private boolean checkBranchProblems() { + Vector oldBranchProblems = new Vector(this.branchProblems); + int oldSize = this.branchProblems.size(); + + this.branchProblems.clear(); + this.branchProblems.addAll(this.problems); + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + child.addBranchProblemsTo(this.branchProblems); + } + + // if the size has changed to or from zero, our virtual flag has changed + int newSize = this.branchProblems.size(); + if ((oldSize == 0) && (newSize != 0)) { + this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, false, true); + } else if ((oldSize != 0) && (newSize == 0)) { + this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, true, false); + } + + if (oldBranchProblems.equals(this.branchProblems)) { + return false; // our "branch" problems did not change + } + // our "branch" problems changed + this.fireListChanged(BRANCH_PROBLEMS_LIST, this.branchProblems); + return true; + } + + /** + * INTRA-TREE API + * Add all the problems of the node and all + * the problems of its descendants to the + * specified collection. + */ + public final void addBranchProblemsTo(List list) { + list.addAll(this.branchProblems); + } + + /** + * INTRA-TREE API + * A child node's "branch" problems changed; + * therefore the node's "branch" problems have changed also and + * must be rebuilt. + */ + public final void rebuildBranchProblems() { + if ( ! this.checkBranchProblems()) { + throw new IllegalStateException("we should not get here unless our \"branch\" problems have changed"); //$NON-NLS-1$ + } + this.rebuildParentBranchProblems(); + } + + /** + * Clear the node's "branch" problems and the "branch" + * problems of all of its descendants. + * If the node's "branch" problems have changed, + * notify the node's parent. + */ + public final void clearAllBranchProblems() { + if (this.clearAllBranchProblemsInternal()) { + // if our "branch" problems have changed, then + // our parent must rebuild its "branch" problems also + this.rebuildParentBranchProblems(); + } + } + + /** + * INTRA-TREE API + * Clear the node's "branch" problems and the "branch" + * problems of all of its descendants. + * Return true if the collection of "branch" problems has changed. + * This method is for internal use only; it is not for + * client use. + */ + public final boolean clearAllBranchProblemsInternal() { + if (this.branchProblems.isEmpty()) { + return false; + } + for (Iterator stream = this.children(); stream.hasNext(); ) { + Node child = stream.next(); // pull out the child to ease debugging + // ignore the return value because we are going to clear our "branch" + // problems no matter what + child.clearAllBranchProblemsInternal(); + } + this.problems.clear(); + this.branchProblems.clear(); + this.firePropertyChanged(HAS_BRANCH_PROBLEMS_PROPERTY, true, false); + this.fireListChanged(BRANCH_PROBLEMS_LIST, this.branchProblems); + return true; + } + + /** + * Return whether a change to specified aspect requires a re-validation + * of the node's tree. + */ + private boolean aspectChangeRequiresValidation(String aspectName) { + return ! this.aspectChangeDoesNotRequireValidation(aspectName); + } + + private boolean aspectChangeDoesNotRequireValidation(String aspectName) { + return this.nonValidatedAspectNames().contains(aspectName); + } + + /** + * Return a set of the object's "non-validated" aspect names. + * These are the aspects that, when they change, will NOT cause the + * object (or its containing tree) to be validated, i.e. checked for problems. + * If you need instance-based calculation of your "non-validated" aspects, + * override this method. If class-based calculation is sufficient, + * override #addNonValidatedAspectNamesTo(Set). + */ + protected final Set nonValidatedAspectNames() { + synchronized (nonValidatedAspectNameSets) { + HashSet nonValidatedAspectNames = nonValidatedAspectNameSets.get(this.getClass()); + if (nonValidatedAspectNames == null) { + nonValidatedAspectNames = new HashSet(); + this.addNonValidatedAspectNamesTo(nonValidatedAspectNames); + nonValidatedAspectNameSets.put(this.getClass(), nonValidatedAspectNames); + } + return nonValidatedAspectNames; + } + } + + /** + * Add the object's "non-validated" aspect names to the specified set. + * These are the aspects that, when they change, will NOT cause the + * object (or its containing tree) to be validated, i.e. checked for problems. + * If class-based calculation of your "non-validated" aspects is sufficient, + * override this method. If you need instance-based calculation, + * override #nonValidatedAspectNames(). + */ + protected void addNonValidatedAspectNamesTo(Set nonValidatedAspectNames) { + nonValidatedAspectNames.add(COMMENT_PROPERTY); + nonValidatedAspectNames.add(DIRTY_BRANCH_PROPERTY); + nonValidatedAspectNames.add(BRANCH_PROBLEMS_LIST); + nonValidatedAspectNames.add(HAS_BRANCH_PROBLEMS_PROPERTY); + // when you override this method, don't forget to include: + // super.addNonValidatedAspectNamesTo(nonValidatedAspectNames); + } + + + // ********** display methods ********** + + /** + * Return a developer-friendly String. If you want something useful for + * displaying in a user interface, use #displayString(). + * If you want to give more information in your #toString(), + * override #toString(StringBuilder sb). + * Whatever you add to that string buffer will show up between the parentheses. + * @see AbstractModel#toString(StringBuilder sb) + * @see #displayString() + */ + @Override + public final String toString() { + return super.toString(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AsynchronousValidator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AsynchronousValidator.java new file mode 100644 index 0000000000..88a06f1b08 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AsynchronousValidator.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean; + +/** + * This implementation of the PluggableValidator.Delegate interface + * simply sets a shared "validate" flag to true. This should trigger a + * separate "validation" thread to begin validating the appropriate + * branch of nodes. + */ +public class AsynchronousValidator + implements PluggableValidator.Delegate +{ + private SynchronizedBoolean validateFlag; + + /** + * Construct a validator delegate with the specified shared + * "validate" flag. This flag should be shared with + * another thread that will perform the actual validation. + */ + public AsynchronousValidator(SynchronizedBoolean validateFlag) { + super(); + this.validateFlag = validateFlag; + } + + /** + * Set the shared "validate" flag to true, triggering + * an asynchronous validation of the appropriate + * branch of nodes. + */ + public void validate() { + this.validateFlag.setTrue(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.validateFlag); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/DefaultProblem.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/DefaultProblem.java new file mode 100644 index 0000000000..6ba2f3a7ad --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/DefaultProblem.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import java.util.Arrays; +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * This class is a straightforward implementation of the Problem interface. + */ +public class DefaultProblem + implements Problem +{ + private final Node source; + private final String messageKey; + private final int messageType; + private final Object[] messageArguments; + + + DefaultProblem(Node source, String messageKey, int messageType, Object[] messageArguments) { + super(); + this.source = source; + this.messageKey = messageKey; + this.messageType = messageType; + this.messageArguments = messageArguments; + } + + + // ********** Problem implementation ********** + + public Node source() { + return this.source; + } + + public String messageKey() { + return this.messageKey; + } + + public int messageType() { + return this.messageType; + } + + public Object[] messageArguments() { + return this.messageArguments; + } + + + // ********** Object overrides ********** + + /** + * We implement #equals(Object) because problems are repeatedly + * re-calculated and the resulting problems merged with the existing + * set of problems; and we want to keep the original problems and + * ignore any freshly-generated duplicates. + * Also, problems are not saved to disk.... + */ + @Override + public boolean equals(Object o) { + if ( ! (o instanceof Problem)) { + return false; + } + Problem other = (Problem) o; + return this.source == other.source() + && this.messageKey.equals(other.messageKey()) + && Arrays.equals(this.messageArguments, other.messageArguments()); + } + + @Override + public int hashCode() { + return this.source.hashCode() ^ this.messageKey.hashCode(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.messageKey); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Node.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Node.java new file mode 100644 index 0000000000..2eab200247 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Node.java @@ -0,0 +1,377 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * This interface defines the methods that must be implemented + * by any class whose instances are to be part of a containment hierarchy + * that supports a "dirty" state and validation "problems". + * + * Note: Methods marked "INTRA-TREE API" are typically only used by + * the nodes themselves, as opposed to clients of the nodes. These + * methods are called by a node on either its parent or its children. + */ +public interface Node extends Model { + + + // ********** containment hierarchy (parent/children) ********** + + /** + * INTRA-TREE API? + * Return the node's parent in the containment hierarchy. + * Most nodes must have a parent. The parent is immutable. + * @see #children() + */ + Node getParent(); + + /** + * INTRA-TREE API? + * Return the node's children, which are also nodes. + * @see #getParent() + */ + Iterator children(); + + /** + * INTRA-TREE API? + * Return the containment hierarchy's root node. + * Most nodes must have a root. + * @see #getParent() + */ + Node root(); + + /** + * Return whether the node is a descendant of the specified node. + * By definition, a node is a descendant of itself. + */ + boolean isDescendantOf(Node node); + + /** + * INTRA-TREE API + * Add the node's "references", and all the node's descendants' + * "references", to the specified collection. "References" are + * objects that are "referenced" by another object, as opposed + * to "owned" by another object. + * This method is of particular concern to Handles, since most + * (hopefully all) "references" are held by Handles. + * @see Reference + * @see #children() + */ + void addBranchReferencesTo(Collection branchReferences); + + /** + * INTRA-TREE API? + * Add all the nodes in the object's branch of the tree, + * including the node itself, to the specified collection. + * Only really used for testing and debugging. + */ + void addAllNodesTo(Collection nodes); + + + // ********** model synchronization support ********** + + /** + * INTRA-TREE API + * This is a general notification that the specified node has been + * removed from the tree. The node receiving this notification + * should perform any necessary updates to remain in synch + * with the tree (e.g. clearing out or replacing any references + * to the removed node or any of the removed node's descendants). + * @see #isDescendantOf(Node) + */ + void nodeRemoved(Node node); + + /** + * INTRA-TREE API + * This is a general notification that the specified node has been + * renamed. The node receiving this notification should mark its + * branch dirty if necessary (i.e. it references the renamed node + * or one of its descendants). This method is of particular concern + * to Handles. + * @see #isDescendantOf(Node) + */ + void nodeRenamed(Node node); + + + // ********** dirty flag support ********** + + /** + * Return whether any persistent aspects of the node, + * or any of its descendants, have changed since the node and + * its descendants were last read or saved. + */ + boolean isDirtyBranch(); + String DIRTY_BRANCH_PROPERTY = "dirtyBranch"; //$NON-NLS-1$ + + /** + * INTRA-TREE API + * Mark the node and its parent as dirty branches. + * This message is propagated up the containment + * tree when a particular node becomes dirty. + */ + void markBranchDirty(); + + /** + * Mark the node and all its descendants as dirty. + * This is used when the save location of some + * top-level node is changed and the entire + * containment tree must be marked dirty so it + * will be written out. + */ + void markEntireBranchDirty(); + + /** + * INTRA-TREE API + * A child node's branch has been marked clean. If the node + * itself is clean and if all of its children are also clean, the + * node's branch can be marked clean. Then, if the node's + * branch is clean, the node will notify its parent that it might + * be clean also. This message is propagated up the containment + * tree when a particular node becomes clean. + */ + void markBranchCleanIfPossible(); + + /** + * INTRA-TREE API + * Mark the node and all its descendants as clean. + * Typically used when the node has just been + * read in or written out. + * This method is for internal use only; it is not for + * client use. + * Not the best of method names.... :-( + */ + void cascadeMarkEntireBranchClean(); + + + // ********** problems ********** + + /** + * INTRA-TREE API + * Return a validator that will be invoked whenever a + * "validated" aspect of the node tree changes. + * Typically only the root node directly holds a validator. + */ + Validator getValidator(); + + /** + * Set a validator that will be invoked whenever a + * "validated" aspect of the node tree changes. + * Typically only the root node directly holds a validator. + */ + void setValidator(Validator validator); + + /** + * Validate the node and its descendants. + * This is an explicit request invoked by a client; and it will + * typically be followed by a call to one of the following methods: + * #branchProblems() + * #hasBranchProblems() + * Whether the node maintains its problems on the fly + * or waits until this method is called is determined by the + * implementation. + * @see Problem + */ + void validateBranch(); + + /** + * INTRA-TREE API + * Validate the node and all of its descendants, + * and update their sets of "branch" problems. + * Return true if the collection of "branch" problems has changed. + * This method is for internal use only; it is not for + * client use. + */ + boolean validateBranchInternal(); + + /** + * Return all the node's problems along with all the + * node's descendants' problems. + */ + ListIterator branchProblems(); + String BRANCH_PROBLEMS_LIST = "branchProblems"; //$NON-NLS-1$ + + /** + * Return the size of all the node's problems along with all the + * node's descendants' problems. + */ + int branchProblemsSize(); + + /** + * Return whether the node or any of its descendants have problems. + */ + boolean hasBranchProblems(); + String HAS_BRANCH_PROBLEMS_PROPERTY = "hasBranchProblems"; //$NON-NLS-1$ + + /** + * Return whether the node contains the specified branch problem. + */ + boolean containsBranchProblem(Problem problem); + + /** + * INTRA-TREE API + * Something changed, rebuild the node's collection of branch problems. + */ + void rebuildBranchProblems(); + + /** + * INTRA-TREE API + * Add the node's problems, and all the node's descendants' + * problems, to the specified list. + * A call to this method should be immediately preceded by a call to + * #validateBranch() or all of the problems might not be + * added to the list. + * @see Problem + */ + void addBranchProblemsTo(List branchProblems); + + /** + * Clear the node's "branch" problems and the "branch" + * problems of all of its descendants. + */ + void clearAllBranchProblems(); + + /** + * INTRA-TREE API + * Clear the node's "branch" problems and the "branch" + * problems of all of its descendants. + * Return true if the collection of "branch" problems has changed. + * This method is for internal use only; it is not for + * client use. + */ + boolean clearAllBranchProblemsInternal(); + + + // ********** comment ********** + + /** + * Return the user comment concerning the node. + */ + String comment(); + String COMMENT_PROPERTY = "comment"; //$NON-NLS-1$ + + /** + * Set the user comment concerning the node. + */ + void setComment(String comment); + + + // ********** displaying/sorting ********** + + /** + * Return a string representation of the model, suitable for sorting. + */ + String displayString(); + + + // ********** sub-interfaces ********** + + /** + * Simple interface defining a "reference" between two nodes. + * @see Node#addBranchReferencesTo(java.util.Collection) + */ + interface Reference { + + /** + * Return the "source" node of the reference, i.e. the node that + * references the "target" node. + */ + Node source(); + + /** + * Return the "target" node of the reference, i.e. the node that + * is referenced by the "source" node. + */ + Node target(); + + } + + + /** + * A validator will validate a node as appropriate. + * Typically the validation will + * - occur whenever a node has changed + * - encompass the entire tree containing the node + * - execute asynchronously + */ + interface Validator { + + /** + * A "significant" aspect has changed; + * validate the node as appropriate + */ + void validate(); + + /** + * Stop all validation of the node until #resume() is called. + * This can be used to improve the performance of any long-running + * action that triggers numerous changes to the node. Be sure to + * match a call to this method with a corresponding call to + * #resume(). + */ + void pause(); + + /** + * Resume validation of the node. This method can only be + * called after a matching call to #pause(). + */ + void resume(); + + } + + + // ********** helper implementations ********** + + /** + * Straightforward implementation of the Reference interface + * defined above. + */ + public class SimpleReference implements Reference { + private Node source; + private Node target; + public SimpleReference(Node source, Node target) { + super(); + if (source == null || target == null) { + throw new NullPointerException(); + } + this.source = source; + this.target = target; + } + public Node source() { + return this.source; + } + public Node target() { + return this.target; + } + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.source + " => " + this.target); //$NON-NLS-1$ + } + } + + + /** + * This validator does nothing to validate the node. + */ + Validator NULL_VALIDATOR = + new PluggableValidator(PluggableValidator.Delegate.Null.instance()) { + @Override + public String toString() { + return "Node.NULL_VALIDATOR"; //$NON-NLS-1$ + } + }; + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/PluggableValidator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/PluggableValidator.java new file mode 100644 index 0000000000..33a283c0bf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/PluggableValidator.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean; + +/** + * This implementation of the Validator interface implements the + * pause/resume portion of the protocol, but delegates the actual + * validation to a "pluggable" delegate. + */ +public class PluggableValidator + implements Node.Validator +{ + private boolean pause; + private boolean validateOnResume; + private final Delegate delegate; + + + /** + * Convenience factory method. + */ + public static Node.Validator buildAsynchronousValidator(SynchronizedBoolean validateFlag) { + return new PluggableValidator(new AsynchronousValidator(validateFlag)); + } + + /** + * Convenience factory method. + */ + public static Node.Validator buildSynchronousValidator(Node node) { + return new PluggableValidator(new SynchronousValidator(node)); + } + + /** + * Construct a validator with the specified delegate. + */ + public PluggableValidator(Delegate delegate) { + super(); + this.pause = false; + this.validateOnResume = false; + this.delegate = delegate; + } + + public synchronized void validate() { + if (this.pause) { + this.validateOnResume = true; + } else { + this.delegate.validate(); + } + } + + public synchronized void pause() { + if (this.pause) { + throw new IllegalStateException("already paused"); //$NON-NLS-1$ + } + this.pause = true; + } + + public synchronized void resume() { + if ( ! this.pause) { + throw new IllegalStateException("not paused"); //$NON-NLS-1$ + } + this.pause = false; + // validate any changes that occurred while the validation was paused + if (this.validateOnResume) { + this.validateOnResume = false; + this.delegate.validate(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.delegate); + } + + + // ********** member interface ********** + + /** + * Interface implemented by any delegates of a pluggable validator. + */ + public interface Delegate { + + /** + * The validator is not "paused" - perform the appropriate validation. + */ + void validate(); + + + /** + * This delegate does nothing. + */ + final class Null implements Delegate { + public static final Delegate INSTANCE = new Null(); + public static Delegate instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void validate() { + // do nothing + } + @Override + public String toString() { + return "PluggableValidator.Delegate.Null"; //$NON-NLS-1$ + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Problem.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Problem.java new file mode 100644 index 0000000000..13b4778a71 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Problem.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +/** + * Define an interface describing the problems associated with a node. + */ +public interface Problem { + + /** + * Return the node most closely associated with the problem. + */ + Node source(); + + /** + * Return a key that can be used to uniquely identify the problem's message. + */ + String messageKey(); + + /** + * Return the arguments associate with the problem's message. + */ + Object[] messageArguments(); + + /** + * Return the type of the identified problem's message + */ + int messageType(); + + /** + * Return whether the problem is equal to the specified object. + * It is equal if the specified object is a implementation of the + * Problem interface and its source, message key, and message + * arguments are all equal to this problem's. + */ + boolean equals(Object o); + + /** + * Return the problem's hash code, which should calculated as an + * XOR of the source's hash code and the message key's hash code. + */ + int hashCode(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/SynchronousValidator.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/SynchronousValidator.java new file mode 100644 index 0000000000..b176685613 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/SynchronousValidator.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.node; + +import org.eclipse.jpt.common.utility.internal.StringTools; + +/** + * This implementation of the PluggableValidator.Delegate interface + * will validate the node immediately. + * + * This is useful for debugging in a single thread or generating + * problem reports. + */ +public class SynchronousValidator + implements PluggableValidator.Delegate +{ + private final Node node; + + /** + * Construct a validator that will immediately validate the + * specified node. + */ + public SynchronousValidator(Node node) { + super(); + this.node = node; + } + + public void validate() { + this.node.validateBranch(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.node); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CachingComboBoxModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CachingComboBoxModel.java new file mode 100644 index 0000000000..cd6412e91e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CachingComboBoxModel.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.ComboBoxModel; + +/** + * This interface allows a client to better control the performance of + * a combo box model by allowing the client to specify when it is + * acceptable for the model to "cache" and "uncache" its list of elements. + * The model may ignore these hints if appropriate. + */ +public interface CachingComboBoxModel extends ComboBoxModel { + + /** + * Cache the comboBoxModel List. If you call this, you + * must make sure to call uncacheList() as well. Otherwise + * stale data will be in the ComboBox until cacheList() is + * called again or uncacheList() is called. + */ + void cacheList(); + + /** + * Clear the cached list. Next time the list is needed it will + * be built when it is not cached. + */ + void uncacheList(); + + /** + * Check to see if the list is already cached. This can be used for + * MouseEvents, since they are not terribly predictable. + */ + boolean isCached(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CheckBoxTableCellRenderer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CheckBoxTableCellRenderer.java new file mode 100644 index 0000000000..87a2b2e5ef --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CheckBoxTableCellRenderer.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.border.Border; +import org.eclipse.jpt.common.utility.internal.swing.TableCellEditorAdapter.ImmediateEditListener; + +/** + * Make the cell look like a check box. + */ +public class CheckBoxTableCellRenderer implements TableCellEditorAdapter.Renderer { + + /** the component used to paint the cell */ + private final JCheckBox checkBox; + + /** the listener to be notified on an immediate edit */ + protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener; + + /** "normal" border - assume the default table "focus" border is 1 pixel thick */ + private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); + + + // ********** constructors/initialization ********** + + /** + * Construct a cell renderer with no label or icon. + */ + public CheckBoxTableCellRenderer() { + super(); + this.checkBox = this.buildCheckBox(); + // by default, check boxes do not paint their borders + this.checkBox.setBorderPainted(true); + // this setting is recommended for check boxes inside of trees and tables + this.checkBox.setBorderPaintedFlat(true); + } + + /** + * Construct a cell renderer with the specified text and icon, + * either of which may be null. + */ + public CheckBoxTableCellRenderer(String text, Icon icon) { + this(); + this.setText(text); + this.setIcon(icon); + } + + /** + * Construct a cell renderer with the specified text. + */ + public CheckBoxTableCellRenderer(String text) { + this(text, null); + } + + /** + * Construct a cell renderer with the specified icon. + */ + public CheckBoxTableCellRenderer(Icon icon) { + this(null, icon); + } + + protected JCheckBox buildCheckBox() { + JCheckBox cb = new JCheckBox(); + cb.addActionListener(this.buildActionListener()); + return cb; + } + + private ActionListener buildActionListener() { + return new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (CheckBoxTableCellRenderer.this.immediateEditListener != null) { + CheckBoxTableCellRenderer.this.immediateEditListener.immediateEdit(); + } + } + }; + } + + + // ********** TableCellRenderer implementation ********** + + public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { + this.checkBox.setHorizontalAlignment(SwingConstants.CENTER); + this.checkBox.setComponentOrientation(table.getComponentOrientation()); + this.checkBox.setFont(table.getFont()); + this.checkBox.setEnabled(table.isEnabled()); + + this.checkBox.setForeground(this.foregroundColor(table, value, selected, hasFocus, row, column)); + this.checkBox.setBackground(this.backgroundColor(table, value, selected, hasFocus, row, column)); + // once the colors are set, calculate opaque setting + this.checkBox.setOpaque(this.cellIsOpaqueIn(table, value, selected, hasFocus, row, column)); + this.checkBox.setBorder(this.border(table, value, selected, hasFocus, row, column)); + + this.setValue(value); + return this.checkBox; + } + + /** + * Return the cell's foreground color. + */ + protected Color foregroundColor(JTable table, @SuppressWarnings("unused") Object value, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return UIManager.getColor("Table.focusCellForeground"); //$NON-NLS-1$ + } + return table.getSelectionForeground(); + } + return table.getForeground(); + } + + /** + * Return the cell's background color. + */ + protected Color backgroundColor(JTable table, @SuppressWarnings("unused") Object value, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return UIManager.getColor("Table.focusCellBackground"); //$NON-NLS-1$ + } + return table.getSelectionBackground(); + } + return table.getBackground(); + } + + /** + * Return the cell's border. + */ + protected Border border(@SuppressWarnings("unused") JTable table, @SuppressWarnings("unused") Object value, @SuppressWarnings("unused") boolean selected, boolean hasFocus, @SuppressWarnings("unused") int row, @SuppressWarnings("unused") int column) { + return hasFocus ? UIManager.getBorder("Table.focusCellHighlightBorder") : NO_FOCUS_BORDER; //$NON-NLS-1$ + } + + /** + * Return whether the cell should be opaque in the table. + * If the cell's background is the same as the table's background + * and table is opaque, we don't need to paint the background - + * the table will do it. + */ + protected boolean cellIsOpaqueIn(JTable table, @SuppressWarnings("unused") Object value, @SuppressWarnings("unused") boolean selected, @SuppressWarnings("unused") boolean hasFocus, @SuppressWarnings("unused") int row, @SuppressWarnings("unused") int column) { + Color cellBackground = this.checkBox.getBackground(); + Color tableBackground = table.getBackground(); + return ! (table.isOpaque() && cellBackground.equals(tableBackground)); + } + + /** + * Set the check box's value. + */ + protected void setValue(Object value) { + // CR#3999318 - This null check needs to be removed once JDK bug is fixed + if (value == null) { + value = Boolean.FALSE; + } + this.checkBox.setSelected(((Boolean) value).booleanValue()); + } + + + // ********** TableCellEditorAdapter.Renderer implementation ********** + + public Object getValue() { + return Boolean.valueOf(this.checkBox.isSelected()); + } + + public void setImmediateEditListener(ImmediateEditListener listener) { + this.immediateEditListener = listener; + } + + // ********** public API ********** + + /** + * Set the check box's text; which by default is blank. + */ + public void setText(String text) { + this.checkBox.setText(text); + } + + /** + * Set the check box's icon; which by default is not present. + */ + public void setIcon(Icon icon) { + this.checkBox.setIcon(icon); + } + + /** + * Return the renderer's preferred height. This allows you + * to set the table's row height to something the check box + * will look good in.... + */ + public int preferredHeight() { + // add in space for the border top and bottom + return (int) this.checkBox.getPreferredSize().getHeight() + 2; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ComboBoxTableCellRenderer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ComboBoxTableCellRenderer.java new file mode 100644 index 0000000000..055a43b09b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ComboBoxTableCellRenderer.java @@ -0,0 +1,328 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JTable; +import javax.swing.ListCellRenderer; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import org.eclipse.jpt.common.utility.internal.ReflectionTools; + +/** + * Make the cell look like a combo-box. + */ +public class ComboBoxTableCellRenderer implements TableCellEditorAdapter.Renderer { + + /* caching the combo box because we are caching the comboBoxModel. + * Everytime we rebuilt the comboBox we would set the model on it and not + * remove the model from the old combo box. This meant that new listeners + * kept being added to the comboBoxModel for every comboBox build. + * Not sure if there is a way to clear out the old combo box, or why + * we were buildig a new combo box every time so I went with caching it. + */ + private JComboBox comboBox; + + /** the items used to populate the combo box */ + private CachingComboBoxModel model; + private ListCellRenderer renderer; + Object value; + private static int height = -1; + boolean fakeFocusFlag; + + /** the listener to be notified on an immediate edit */ + protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener; + + /** hold the original colors of the combo-box */ + private static Color defaultForeground; + private static Color defaultBackground; + + /** "normal" border - assume the default table "focus" border is 1 pixel thick */ + private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); + + + // ********** constructors/initialization ********** + + /** + * Default constructor. + */ + private ComboBoxTableCellRenderer() { + super(); + initialize(); + } + + /** + * Construct a cell renderer that uses the specified combo-box model. + */ + public ComboBoxTableCellRenderer(ComboBoxModel model) { + this(new NonCachingComboBoxModel(model)); + } + + /** + * Construct a cell renderer that uses the specified caching combo-box model. + */ + public ComboBoxTableCellRenderer(CachingComboBoxModel model) { + this(); + this.model = model; + } + + /** + * Construct a cell renderer that uses the specified + * combo-box model and renderer. + */ + public ComboBoxTableCellRenderer(ComboBoxModel model, ListCellRenderer renderer) { + this(new NonCachingComboBoxModel(model), renderer); + } + + /** + * Construct a cell renderer that uses the specified + * caching combo-box model and renderer. + */ + public ComboBoxTableCellRenderer(CachingComboBoxModel model, ListCellRenderer renderer) { + this(model); + this.renderer = renderer; + } + + protected void initialize() { + // save the original colors of the combo-box, so we + // can use them to paint non-selected cells + if (height == -1) { + JComboBox cb = new JComboBox(); + cb.addItem("m"); //$NON-NLS-1$ + + // add in space for the border top and bottom + height = cb.getPreferredSize().height + 2; + + defaultForeground = cb.getForeground(); + defaultBackground = cb.getBackground(); + } + } + + static JLabel prototypeLabel = new JLabel("Prototype", new EmptyIcon(16), SwingConstants.LEADING); //$NON-NLS-1$ + + protected JComboBox buildComboBox() { + + final JComboBox result = new JComboBox() { + private boolean fakeFocus; + @Override + public boolean hasFocus() { + return fakeFocus || super.hasFocus(); + } + @Override + public void paint(Graphics g) { + fakeFocus = ComboBoxTableCellRenderer.this.fakeFocusFlag; + super.paint(g); + fakeFocus = false; + } + //wrap the renderer to deal with the prototypeDisplayValue + @Override + public void setRenderer(final ListCellRenderer aRenderer) { + super.setRenderer(new ListCellRenderer(){ + public Component getListCellRendererComponent(JList list, Object v, int index, boolean isSelected, boolean cellHasFocus) { + if (v == prototypeLabel) { + return prototypeLabel; + } + return aRenderer.getListCellRendererComponent(list, v, index, isSelected, cellHasFocus); + } + }); + } + @Override + public int getSelectedIndex() { + boolean listNotCached = !listIsCached(); + if (listNotCached) { + cacheList(); + } + + int index = super.getSelectedIndex(); + + if (listNotCached) { + uncacheList(); + } + return index; + } + + }; + // stole this code from javax.swing.DefaultCellEditor + result.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //$NON-NLS-1$ + result.addActionListener(this.buildActionListener()); + result.addPopupMenuListener(this.buildPopupMenuListener()); + + //These are used to workaround problems with Swing trying to + //determine the size of a comboBox with a large model + result.setPrototypeDisplayValue(prototypeLabel); + getListBox(result).setPrototypeCellValue(prototypeLabel); + + return result; + } + + + private JList getListBox(JComboBox result) { + return (JList) ReflectionTools.getFieldValue(result.getUI(), "listBox"); //$NON-NLS-1$ + } + + + private ActionListener buildActionListener() { + return new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox cb = (JComboBox) e.getSource(); + Object selectedItem = cb.getSelectedItem(); + + // Only update the selected item and invoke immediateEdit() if the + // selected item actually changed, during the initialization of the + // editing, the model changes and causes this method to be invoked, + // it causes CR#3963675 to occur because immediateEdit() stop the + // editing, which is done at the wrong time + if (ComboBoxTableCellRenderer.this.value != selectedItem) { + ComboBoxTableCellRenderer.this.value = cb.getSelectedItem(); + ComboBoxTableCellRenderer.this.immediateEdit(); + } + } + }; + } + + void immediateEdit() { + if (this.immediateEditListener != null) { + this.immediateEditListener.immediateEdit(); + } + } + + private PopupMenuListener buildPopupMenuListener() { + return new PopupMenuListener() { + + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + if (listIsCached()) { + uncacheList(); + } + cacheList(); + } + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + if (listIsCached()) { + uncacheList(); + } + + } + + public void popupMenuCanceled(PopupMenuEvent e) { + if (listIsCached()) { + uncacheList(); + } + } + }; + } + + + void cacheList() { + this.model.cacheList(); + } + + void uncacheList() { + this.model.uncacheList(); + } + + boolean listIsCached() { + return this.model.isCached(); + } + // ********** TableCellRenderer implementation ********** + + public Component getTableCellRendererComponent(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) { + this.fakeFocusFlag = selected || hasFocus; + if (this.comboBox == null) { + this.comboBox = this.buildComboBox(); + + this.comboBox.setComponentOrientation(table.getComponentOrientation()); + this.comboBox.setModel(this.model); + if (this.renderer != null) { + this.comboBox.setRenderer(this.renderer); + } + this.comboBox.setFont(table.getFont()); + this.comboBox.setEnabled(table.isEnabled()); + this.comboBox.setBorder(this.border(table, val, selected, hasFocus, row, column)); + } + + // We need to go through the model since JComboBox might prevent us from + // selecting the value. This can happen when the value is not contained + // in the model, see CR#3950044 for an example + this.model.setSelectedItem(val); + + return this.comboBox; + } + + /** + * Return the cell's foreground color. + */ + protected Color foregroundColor(JTable table, @SuppressWarnings("unused") Object val, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return defaultForeground; + } + return table.getSelectionForeground(); + } + return defaultForeground; + } + + /** + * Return the cell's background color. + */ + protected Color backgroundColor(JTable table, @SuppressWarnings("unused") Object val, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return defaultBackground; + } + return table.getSelectionBackground(); + } + return defaultBackground; + } + + /** + * Return the cell's border. + */ + protected Border border(@SuppressWarnings("unused") JTable table, @SuppressWarnings("unused") Object val, @SuppressWarnings("unused") boolean selected, boolean hasFocus, @SuppressWarnings("unused") int row, @SuppressWarnings("unused") int column) { + return hasFocus ? + UIManager.getBorder("Table.focusCellHighlightBorder") //$NON-NLS-1$ + : + NO_FOCUS_BORDER; + } + + + // ********** TableCellEditorAdapter.Renderer implementation ********** + + public Object getValue() { + return this.value; + } + + public void setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener) { + this.immediateEditListener = listener; + } + + + // ********** public API ********** + + /** + * Return the renderer's preferred height. This allows you + * to set the row height to something the combo-box will look good in.... + */ + public int preferredHeight() { + return height; + } + +} \ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/Displayable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/Displayable.java new file mode 100644 index 0000000000..5a3adb7cc4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/Displayable.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.Icon; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Used by general-purpose UI models and renderers to cast + * application model objects to something displayable. + */ +public interface Displayable + extends Model +{ + + /** + * Return a string that can be used to identify the model + * in a textual UI setting (typically the object's name). + * When the display string changes, the model should fire + * the appropriate change notification: + * this.firePropertyChanged(DISPLAY_STRING_PROPERTY, oldDisplayString, this.displayString()); + */ + String displayString(); + String DISPLAY_STRING_PROPERTY = "displayString"; //$NON-NLS-1$ + + /** + * Return an icon that can be used to identify the model + * in a UI component that supports icons (the icon can be null). + * When the icon changes, the model should fire + * the appropriate change notification: + * this.firePropertyChanged(ICON_PROPERTY, oldIcon, this.icon()); + */ + Icon icon(); + String ICON_PROPERTY = "icon"; //$NON-NLS-1$ + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/EmptyIcon.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/EmptyIcon.java new file mode 100644 index 0000000000..b04fbcf4b0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/EmptyIcon.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Component; +import java.awt.Graphics; +import javax.swing.Icon; + +/** + * Implement the Icon interface with an icon that has a size but + * does not paint anything on the graphics context. + */ +public class EmptyIcon + implements Icon +{ + private final int width; + private final int height; + + public static final EmptyIcon NULL_INSTANCE = new EmptyIcon(0); + + + public EmptyIcon(int width, int height) { + super(); + this.width = width; + this.height = height; + } + + public EmptyIcon(int size) { + this(size, size); + } + + + // ********** Icon implementation ********** + + public void paintIcon(Component c, Graphics g, int x, int y) { + // don't paint anything for an empty icon + } + + public int getIconWidth() { + return this.width; + } + + public int getIconHeight() { + return this.height; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListBrowser.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListBrowser.java new file mode 100644 index 0000000000..68ee9aa0ec --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListBrowser.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.ListModel; + +/** + * This implementation of LongListComponent.Browser uses a + * JOptionPane to prompt the user for the selection. The JOPtionPane + * is passed a FilteringListPanel to assist the user in making + * a selection. + */ +public class FilteringListBrowser + implements ListChooser.ListBrowser +{ + private FilteringListPanel panel; + + /** + * Default constructor. + */ + public FilteringListBrowser() { + super(); + this.panel = this.buildPanel(); + } + + protected FilteringListPanel buildPanel() { + return new LocalFilteringListPanel(); + } + + /** + * Prompt the user using a JOptionPane with a filtering + * list panel. + */ + public void browse(ListChooser chooser) { + this.initializeCellRenderer(chooser); + + int option = + JOptionPane.showOptionDialog( + chooser, + this.message(chooser), + this.title(chooser), + this.optionType(chooser), + this.messageType(chooser), + this.icon(chooser), + this.selectionValues(chooser), + this.initialSelectionValue(chooser) + ); + + if (option == JOptionPane.OK_OPTION) { + chooser.getModel().setSelectedItem(this.panel.selection()); + } + + // clear the text field so the list box is re-filtered + this.panel.textField().setText(""); //$NON-NLS-1$ + } + + protected void initializeCellRenderer(JComboBox comboBox) { + // default behavior should be to use the cell renderer from the combobox. + this.panel.listBox().setCellRenderer(comboBox.getRenderer()); + } + + /** + * the message can be anything - here we build a component + */ + protected Object message(JComboBox comboBox) { + this.panel.setCompleteList(this.convertToArray(comboBox.getModel())); + this.panel.setSelection(comboBox.getModel().getSelectedItem()); + return this.panel; + } + + protected String title(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected int optionType(@SuppressWarnings("unused") JComboBox comboBox) { + return JOptionPane.OK_CANCEL_OPTION; + } + + protected int messageType(@SuppressWarnings("unused") JComboBox comboBox) { + return JOptionPane.QUESTION_MESSAGE; + } + + protected Icon icon(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected Object[] selectionValues(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected Object initialSelectionValue(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + /** + * Convert the list of objects in the specified list model + * into an array. + */ + protected Object[] convertToArray(ListModel model) { + int size = model.getSize(); + Object[] result = new Object[size]; + for (int i = 0; i < size; i++) { + result[i] = model.getElementAt(i); + } + return result; + } + + + // ********** custom panel ********** + + protected static class LocalFilteringListPanel extends FilteringListPanel { + protected static final Object[] EMPTY_ARRAY = new Object[0]; + + protected LocalFilteringListPanel() { + super(EMPTY_ARRAY, null); + } + + /** + * Disable the performance tweak because JOptionPane + * will try open wide enough to disable the horizontal scroll bar; + * and it looks a bit clumsy. + */ + @Override + protected String prototypeCellValue() { + return null; + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListPanel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListPanel.java new file mode 100644 index 0000000000..e58608c9e4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListPanel.java @@ -0,0 +1,455 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.BorderLayout; +import java.awt.Font; +import javax.swing.AbstractListModel; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListCellRenderer; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.eclipse.jpt.common.utility.internal.SimpleStringMatcher; +import org.eclipse.jpt.common.utility.internal.StringConverter; +import org.eclipse.jpt.common.utility.internal.StringMatcher; + +/** + * This panel presents an entry field and a list box of choices that + * allows the user to filter the entries in the list box by entering + * a pattern in the entry field. + * + * By default, two wildcards are allowed in the pattern: + * '*' will match any set of zero or more characters + * '?' will match any single character + * + * The panel consists of 4 components that can be customized: + * - 1 text field + * - 1 list box + * - 2 labels, one for each of the above + * + * Other aspects of the panel's behavior can be changed: + * - the string converter determines how the objects in the + * list are converted to strings and compared to the pattern + * entered in the text field; by default the converter simply + * uses the result of the object's #toString() method + * (if you replace the string converter, you will probably + * want to replace the list box's cell renderer also) + * - the string matcher can also be changed if you would + * like different pattern matching behavior than that + * described above + * - you can specify the maximum size of the list - this may + * force the user to enter a pattern restrictive enough + * to result in a list smaller than the maximum size; the + * default is -1, which disables the restriction + * + * This panel is not a typical panel, in the sense that it does not share + * its model with clients via value models. Instead, this panel's model + * is set and queried directly because it is designed to be used in a + * dialog that directs the user's behavior (as opposed to a "normal" + * window). + */ +public class FilteringListPanel extends JPanel { + + /** + * The complete list of available choices + * (as opposed to the partial list held by the list box). + */ + private Object[] completeList; + + /** + * An adapter used to convert the objects in the list + * to strings so they can be run through the matcher + * and displayed in the text field. + */ + StringConverter stringConverter; + + /** The text field. */ + private JTextField textField; + private JLabel textFieldLabel; + private DocumentListener textFieldListener; + + /** The list box. */ + private JList listBox; + private JLabel listBoxLabel; + + /** The maximum number of entries displayed in the list box. */ + private int maxListSize; + + /** + * The matcher used to filter the list against + * the pattern entered in the text field. By default, + * this allows the two wildcard characters described in + * the class comment. + */ + private StringMatcher stringMatcher; + + /** + * Performance tweak: We use this buffer instead of + * a temporary variable during filtering so we don't have + * to keep re-allocating it. + */ + private Object[] buffer; + + private static final Border TEXT_FIELD_LABEL_BORDER = BorderFactory.createEmptyBorder(0, 0, 5, 0); + private static final Border LIST_BOX_LABEL_BORDER = BorderFactory.createEmptyBorder(5, 0, 5, 0); + + + // ********** constructors ********** + + /** + * Construct a FilteringListPanel with the specified list of choices + * and initial selection. Use the default string converter to convert the + * choices and selection to strings (which simply calls #toString() on + * the objects). + */ + public FilteringListPanel(Object[] completeList, Object initialSelection) { + this(completeList, initialSelection, StringConverter.Default.instance()); + } + + /** + * Construct a FilteringListPanel with the specified list of choices + * and initial selection. Use the specified string converter to convert the + * choices and selection to strings. + */ + public FilteringListPanel(Object[] completeList, Object initialSelection, StringConverter stringConverter) { + super(new BorderLayout()); + this.completeList = completeList; + this.stringConverter = stringConverter; + this.initialize(initialSelection); + } + + + // ********** initialization ********** + + private void initialize(Object initialSelection) { + this.maxListSize = this.defaultMaxListSize(); + this.buffer = this.buildBuffer(); + + this.textFieldListener = this.buildTextFieldListener(); + + this.stringMatcher = this.buildStringMatcher(); + + this.initializeLayout(initialSelection); + } + + private Object[] buildBuffer() { + return new Object[this.max()]; + } + + /** + * Return the current max number of entries allowed in the list box. + */ + private int max() { + if (this.maxListSize == -1) { + return this.completeList.length; + } + return Math.min(this.maxListSize, this.completeList.length); + } + + /** + * Build a listener that will listen to changes in the text field + * and filter the list appropriately. + */ + private DocumentListener buildTextFieldListener() { + return new DocumentListener() { + public void insertUpdate(DocumentEvent e) { + FilteringListPanel.this.filterList(); + } + public void changedUpdate(DocumentEvent e) { + FilteringListPanel.this.filterList(); + } + public void removeUpdate(DocumentEvent e) { + FilteringListPanel.this.filterList(); + } + @Override + public String toString() { + return "text field listener"; //$NON-NLS-1$ + } + }; + } + + private int defaultMaxListSize() { + return -1; + } + + private StringMatcher buildStringMatcher() { + return new SimpleStringMatcher(); + } + + private void initializeLayout(Object initialSelection) { + // text field + JPanel textFieldPanel = new JPanel(new BorderLayout()); + this.textFieldLabel = new JLabel(); + this.textFieldLabel.setBorder(TEXT_FIELD_LABEL_BORDER); + textFieldPanel.add(this.textFieldLabel, BorderLayout.NORTH); + + this.textField = new JTextField(); + this.textField.getDocument().addDocumentListener(this.textFieldListener); + this.textFieldLabel.setLabelFor(this.textField); + textFieldPanel.add(this.textField, BorderLayout.CENTER); + + this.add(textFieldPanel, BorderLayout.NORTH); + + // list box + JPanel listBoxPanel = new JPanel(new BorderLayout()); + this.listBoxLabel = new JLabel(); + this.listBoxLabel.setBorder(LIST_BOX_LABEL_BORDER); + listBoxPanel.add(this.listBoxLabel, BorderLayout.NORTH); + + this.listBox = new JList(); + this.listBox.setDoubleBuffered(true); + this.listBox.setModel(this.buildPartialArrayListModel(this.completeList, this.max())); + this.listBox.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + // performance tweak(?) + this.listBox.setPrototypeCellValue(this.prototypeCellValue()); + this.listBox.setPrototypeCellValue(null); + this.listBox.setCellRenderer(this.buildDefaultCellRenderer()); + this.listBoxLabel.setLabelFor(this.listBox); + // bug 2777802 - scroll bars shouldn't be on the tab sequence + JScrollPane listBoxScrollPane = new JScrollPane(this.listBox); + listBoxScrollPane.getHorizontalScrollBar().setFocusable(false); + listBoxScrollPane.getVerticalScrollBar().setFocusable(false); + listBoxPanel.add(listBoxScrollPane, BorderLayout.CENTER); + + // initialize the widgets + this.listBox.setSelectedValue(initialSelection, true); + this.textField.select(0, this.textField.getText().length()); + + this.add(listBoxPanel, BorderLayout.CENTER); + } + + + // ********** public API ********** + + public Object selection() { + return this.listBox.getSelectedValue(); + } + + public void setSelection(Object selection) { + this.listBox.setSelectedValue(selection, true); + } + + public Object[] completeList() { + return this.completeList; + } + + /** + * rebuild the filtering buffer and re-apply the filter + * to the new list + */ + public void setCompleteList(Object[] completeList) { + this.completeList = completeList; + if (this.buffer.length < this.max()) { + // the buffer will never shrink - might want to re-consider... ~bjv + this.buffer = this.buildBuffer(); + } + this.filterList(); + } + + public int maxListSize() { + return this.maxListSize; + } + + public void setMaxListSize(int maxListSize) { + this.maxListSize = maxListSize; + if (this.buffer.length < this.max()) { + // the buffer will never shrink - might want to re-consider... ~bjv + this.buffer = this.buildBuffer(); + } + this.filterList(); + } + + public StringConverter stringConverter() { + return this.stringConverter; + } + + /** + * apply the new filter to the list + */ + public void setStringConverter(StringConverter stringConverter) { + this.stringConverter = stringConverter; + this.filterList(); + } + + /** + * allow client code to access the text field + * (so we can set the focus) + */ + public JTextField textField() { + return this.textField; + } + + /** + * allow client code to access the text field label + */ + public JLabel textFieldLabel() { + return this.textFieldLabel; + } + + /** + * convenience method + */ + public void setTextFieldLabelText(String text) { + this.textFieldLabel.setText(text); + } + + /** + * allow client code to access the list box + * (so we can add mouse listeners for double-clicking) + */ + public JList listBox() { + return this.listBox; + } + + /** + * convenience method + */ + public void setListBoxCellRenderer(ListCellRenderer renderer) { + this.listBox.setCellRenderer(renderer); + } + + /** + * allow client code to access the list box label + */ + public JLabel listBoxLabel() { + return this.listBoxLabel; + } + + /** + * convenience method + */ + public void setListBoxLabelText(String text) { + this.listBoxLabel.setText(text); + } + + /** + * convenience method + */ + public void setComponentsFont(Font font) { + this.textFieldLabel.setFont(font); + this.textField.setFont(font); + this.listBoxLabel.setFont(font); + this.listBox.setFont(font); + } + + public StringMatcher stringMatcher() { + return this.stringMatcher; + } + + /** + * re-apply the filter to the list + */ + public void setStringMatcher(StringMatcher stringMatcher) { + this.stringMatcher = stringMatcher; + this.filterList(); + } + + + // ********** internal methods ********** + + /** + * Allow subclasses to disable performance tweak + * by returning null here. + */ + protected String prototypeCellValue() { + return "==========> A_STRING_THAT_IS_DEFINITELY_LONGER_THAN_EVERY_STRING_IN_THE_LIST <=========="; //$NON-NLS-1$ + } + + /** + * By default, use the string converter to build the text + * used by the list box's cell renderer. + */ + protected ListCellRenderer buildDefaultCellRenderer() { + return new SimpleListCellRenderer() { + @Override + @SuppressWarnings("unchecked") + protected String buildText(Object value) { + return FilteringListPanel.this.stringConverter.convertToString((T) value); + } + }; + } + + /** + * Something has changed that requires us to filter the list. + * + * This method is synchronized because a fast typist can + * generate events quicker than we can filter the list. (? ~bjv) + */ + synchronized void filterList() { + // temporarily stop listening to the list box selection, since we will + // be changing the selection during the filtering and don't want + // that to affect the text field + this.filterList(this.textField.getText()); + } + + /** + * Filter the contents of the list box to match the + * specified pattern. + */ + private void filterList(String pattern) { + if (pattern.length() == 0) { + this.listBox.setModel(this.buildPartialArrayListModel(this.completeList, this.max())); + } else { + this.stringMatcher.setPatternString(pattern); + int j = 0; + int len = this.completeList.length; + int max = this.max(); + for (int i = 0; i < len; i++) { + if (this.stringMatcher.matches(this.stringConverter.convertToString(this.entry(i)))) { + this.buffer[j++] = this.completeList[i]; + } + if (j == max) { + break; + } + } + this.listBox.setModel(this.buildPartialArrayListModel(this.buffer, j)); + } + + // after filtering the list, determine the appropriate selection + if (this.listBox.getModel().getSize() == 0) { + this.listBox.getSelectionModel().clearSelection(); + } else { + this.listBox.getSelectionModel().setAnchorSelectionIndex(0); + this.listBox.getSelectionModel().setLeadSelectionIndex(0); + this.listBox.ensureIndexIsVisible(0); + } + } + + /** + * minimize scope of suppressed warnings + */ + @SuppressWarnings("unchecked") + private T entry(int index) { + return (T) this.completeList[index]; + } + + /** + * Build a list model that wraps only a portion of the specified array. + * The model will include the array entries from 0 to (size - 1). + */ + private ListModel buildPartialArrayListModel(final Object[] array, final int size) { + return new AbstractListModel() { + public int getSize() { + return size; + } + public Object getElementAt(int index) { + return array[index]; + } + }; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ListChooser.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ListChooser.java new file mode 100644 index 0000000000..84bad23fc9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ListChooser.java @@ -0,0 +1,430 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.AWTEvent; +import java.awt.AWTException; +import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.SwingConstants; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.basic.BasicComboBoxUI; +import org.eclipse.jpt.common.utility.internal.ReflectionTools; + +/** + * This component provides a way to handle selecting an item from a + * list that may grow too large to be handled conveniently by a combo-box. + * If the list's size is less than the designated "long" list size, + * the choice list will be displayed in a normal combo-box popup; + * otherwise, a dialog will be used to prompt the user to choose a selection. + * + * To change the browse mechanism, subclasses may + * - override the method #buildBrowser() + * - override the method #browse(), in which case the method + * #buildBrowser() may be ignored. + */ +public class ListChooser + extends JComboBox +{ + + /** the size of a "long" list - anything smaller is a "short" list */ + int longListSize = DEFAULT_LONG_LIST_SIZE; + + /** the default size of a "long" list, which is 20 (to match JOptionPane's behavior) */ + public static final int DEFAULT_LONG_LIST_SIZE = 20; + + /** property change associated with long list size */ + public static final String LONG_LIST_SIZE_PROPERTY = "longListSize"; //$NON-NLS-1$ + + static JLabel prototypeLabel = new JLabel("Prototype", new EmptyIcon(17), SwingConstants.LEADING); //$NON-NLS-1$ + + /** + * whether the chooser is choosable. if a chooser is not choosable, + * it only serves as a display widget. a user may not change its + * selected value. + */ + boolean choosable = true; + + /** property change associated with choosable */ + public static final String CHOOSABLE_PROPERTY = "choosable"; //$NON-NLS-1$ + + /** the browser used to make a selection from the long list - typically via a dialog */ + private ListBrowser browser; + + private NodeSelector nodeSelector; + + /** INTERNAL - The popup is being shown. Used to prevent infinite loop. */ + boolean popupAlreadyInProgress; + + + // **************** Constructors ****************************************** + + /** + * Construct a list chooser for the specified model. + */ + public ListChooser(ComboBoxModel model) { + this(model, new NodeSelector.DefaultNodeSelector()); + } + + public ListChooser(CachingComboBoxModel model) { + this(model, new NodeSelector.DefaultNodeSelector()); + } + + public ListChooser(ComboBoxModel model, NodeSelector nodeSelector) { + this(new NonCachingComboBoxModel(model), nodeSelector); + } + + public ListChooser(CachingComboBoxModel model, NodeSelector nodeSelector) { + super(model); + this.initialize(); + this.nodeSelector = nodeSelector; + } + // **************** Initialization **************************************** + + protected void initialize() { + this.addPopupMenuListener(this.buildPopupMenuListener()); + this.setRenderer(new DefaultListCellRenderer()); + this.addKeyListener(buildF3KeyListener()); + + //These are used to workaround problems with Swing trying to + //determine the size of a comboBox with a large model + setPrototypeDisplayValue(prototypeLabel); + listBox().setPrototypeCellValue(prototypeLabel); + } + + + private JList listBox() { + return (JList) ReflectionTools.getFieldValue(this.ui, "listBox"); //$NON-NLS-1$ + } + + /** + * When the popup is about to be shown, the event is consumed, and + * PopupHandler determines whether to reshow the popup or to show + * the long list browser. + */ + private PopupMenuListener buildPopupMenuListener() { + return new PopupMenuListener() { + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + ListChooser.this.aboutToShowPopup(); + } + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + // do nothing + } + public void popupMenuCanceled(PopupMenuEvent e) { + // do nothing + } + @Override + public String toString() { + return "pop-up menu listener"; //$NON-NLS-1$ + } + }; + } + + /** + * If this code is being reached due to the PopupHandler already being in progress, + * then do nothing. Otherwise, set the flag to true and launch the PopupHandler. + */ + void aboutToShowPopup() { + if (this.popupAlreadyInProgress) { + return; + } + + this.popupAlreadyInProgress = true; + EventQueue.invokeLater(new PopupHandler()); + } + + + private KeyListener buildF3KeyListener() { + return new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_F3) { + goToSelectedItem(); + } + } + @Override + public String toString() { + return "F3 key listener"; //$NON-NLS-1$ + } + }; + } + + public void goToSelectedItem() { + if (getSelectedItem() != null) { + ListChooser.this.nodeSelector.selectNodeFor(getSelectedItem()); + } + } + + // **************** Browsing ********************************************** + + /** + * Lazily initialize because subclasses may have further initialization to do + * before browser can be built. + */ + protected void browse() { + if (this.browser == null) { + this.browser = this.buildBrowser(); + } + + this.browser.browse(this); + } + + /** + * Return the "browser" used to make a selection from the long list, + * typically via a dialog. + */ + protected ListChooser.ListBrowser buildBrowser() { + return new SimpleListBrowser(); + } + + + // **************** Choosable functionality ******************************* + + /** override behavior - consume selection if chooser is not choosable */ + @Override + public void setSelectedIndex(int anIndex) { + if (this.choosable) { + super.setSelectedIndex(anIndex); + } + } + + private void updateArrowButton() { + try { + BasicComboBoxUI comboBoxUi = (BasicComboBoxUI) ListChooser.this.getUI(); + JButton arrowButton = (JButton) ReflectionTools.getFieldValue(comboBoxUi, "arrowButton"); //$NON-NLS-1$ + arrowButton.setEnabled(this.isEnabled() && this.choosable); + } + catch (Exception e) { + // this is a huge hack to try and make the combo box look right, + // so if it doesn't work, just swallow the exception + } + } + + + // **************** List Caching ******************************* + + void cacheList() { + ((CachingComboBoxModel) getModel()).cacheList(); + } + + void uncacheList() { + ((CachingComboBoxModel) getModel()).uncacheList(); + } + + boolean listIsCached() { + return ((CachingComboBoxModel) getModel()).isCached(); + } + + // **************** Public ************************************************ + + public int longListSize() { + return this.longListSize; + } + + public void setLongListSize(int newLongListSize) { + int oldLongListSize = this.longListSize; + this.longListSize = newLongListSize; + this.firePropertyChange(LONG_LIST_SIZE_PROPERTY, oldLongListSize, newLongListSize); + } + + public boolean isChoosable() { + return this.choosable; + } + + public void setChoosable(boolean newValue) { + boolean oldValue = this.choosable; + this.choosable = newValue; + this.firePropertyChange(CHOOSABLE_PROPERTY, oldValue, newValue); + this.updateArrowButton(); + } + + // **************** Handle selecting null as a value ********************** + + private boolean selectedIndexIsNoneSelectedItem(int index) { + return index == -1 && + getModel().getSize() > 0 && + getModel().getElementAt(0) == null; + } + + @Override + public int getSelectedIndex() { + boolean listNotCached = !listIsCached(); + if (listNotCached) { + cacheList(); + } + + int index = super.getSelectedIndex(); + + // Use index 0 to show the item since the actual value is + // null and JComboBox does not handle null values + if (selectedIndexIsNoneSelectedItem(index)) { + index = 0; + } + + if (listNotCached) { + uncacheList(); + } + return index; + } + + //wrap the renderer to deal with the prototypeDisplayValue + @Override + public void setRenderer(final ListCellRenderer aRenderer) { + super.setRenderer(new ListCellRenderer(){ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value == prototypeLabel) { + return prototypeLabel; + } + return aRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + }); + } + + + // **************** Member classes **************************************** + + /** + * Define the API required by this ListChooser when it must + * prompt the user to select an item from the "long" list. + */ + public interface ListBrowser + { + /** + * Prompt the user to make a selection from the specified + * combo-box's model. + */ + void browse(ListChooser parentChooser); + } + + + /** + * Runnable class that consumes popup window and determines whether + * to reshow popup or to launch browser, based on the size of the list. + */ + private class PopupHandler + implements Runnable + { + /** The mouse event */ + private MouseEvent lastMouseEvent; + + /** The component from which the last mouse event was thrown */ + private JComponent eventComponent; + + /** The location of the component at the time the last mouse event was thrown */ + private Point componentLocation; + + /** The location of the mouse at the time the last mouse event was thrown */ + private Point mouseLocation; + + + PopupHandler() { + this.initialize(); + } + + private void initialize() { + AWTEvent event = EventQueue.getCurrentEvent(); + + if (event instanceof MouseEvent) { + this.lastMouseEvent = (MouseEvent) event; + this.eventComponent = (JComponent) this.lastMouseEvent.getSource(); + this.componentLocation = this.eventComponent.getLocationOnScreen(); + this.mouseLocation = this.lastMouseEvent.getPoint(); + } + else { + this.eventComponent = null; + this.componentLocation = null; + this.mouseLocation = null; + } + } + + public void run() { + ListChooser.this.hidePopup(); + + cacheList(); + if (ListChooser.this.choosable == true) { + // If the combo box model is of sufficient length, the browser will be shown. + // Asking the combo box model for its size should be enough to ensure that + // its size is recalculated. + if (ListChooser.this.getModel().getSize() > ListChooser.this.longListSize) { + this.checkComboBoxButton(); + ListChooser.this.browse(); + } + else { + ListChooser.this.showPopup(); + this.checkMousePosition(); + } + } + if (listIsCached()) { + uncacheList(); + } + + ListChooser.this.popupAlreadyInProgress = false; + } + + /** If this is not done, the button never becomes un-pressed */ + private void checkComboBoxButton() { + try { + BasicComboBoxUI comboBoxUi = (BasicComboBoxUI) ListChooser.this.getUI(); + JButton arrowButton = (JButton) ReflectionTools.getFieldValue(comboBoxUi, "arrowButton"); //$NON-NLS-1$ + arrowButton.getModel().setPressed(false); + } + catch (Exception ex) { + // this is a huge hack to try and make the combo box look right, + // so if it doesn't work, just swallow the exception + this.handleException(ex); + } + } + + private void handleException(@SuppressWarnings("unused") Exception ex) { + // do nothing for now + } + + /** + * Moves the mouse back to its original position before any jiggery pokery that we've done. + */ + private void checkMousePosition() { + if (this.eventComponent == null) { + return; + } + + final Point newComponentLocation = this.eventComponent.getLocationOnScreen(); + boolean componentMoved = + newComponentLocation.x - this.componentLocation.x != 0 + || newComponentLocation.y - this.componentLocation.y != 0; + + if (componentMoved) { + try { + new Robot().mouseMove( + newComponentLocation.x + this.mouseLocation.x, + newComponentLocation.y + this.mouseLocation.y + ); + } + catch (AWTException ex) { + // move failed - do nothing + } + } + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NodeSelector.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NodeSelector.java new file mode 100644 index 0000000000..64079be527 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NodeSelector.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +/** + * This will be called when the user presses F3 or chooses + * 'Go To' in the context menu + */ +public interface NodeSelector +{ + /** + * Select the appropriate Node in the tree or the editor panel. + */ + void selectNodeFor(Object item); + + /** + * This NodeSelector will do nothing when selectNodeFor(Object) is called + */ + class DefaultNodeSelector implements NodeSelector { + + public void selectNodeFor(Object item) { + //default is to do nothing + } + } +} \ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NonCachingComboBoxModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NonCachingComboBoxModel.java new file mode 100644 index 0000000000..aef9a7c6ed --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NonCachingComboBoxModel.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.ComboBoxModel; +import javax.swing.event.ListDataListener; + +/** + * This implementation of the CachingComboBoxModel interface can be used + * whenever there is no need for caching (i.e. the contents of the selection + * list can be generated with little latency). All the normal ComboBoxModel + * behavior is delegated to a client-supplied ComboBoxModel. + */ +public class NonCachingComboBoxModel implements CachingComboBoxModel { + private ComboBoxModel wrappedComboBoxModel; + + public NonCachingComboBoxModel(ComboBoxModel wrappedComboBoxModel) { + this.wrappedComboBoxModel = wrappedComboBoxModel; + } + + + // ********** CachingComboBoxModel implementation ********** + + public void cacheList() { + //do nothing + } + + public void uncacheList() { + //do nothing + } + + public boolean isCached() { + return false; + } + + + // ********** ComboBoxModel implementation ********** + + public void setSelectedItem(Object anItem) { + this.wrappedComboBoxModel.setSelectedItem(anItem); + } + + public Object getSelectedItem() { + return this.wrappedComboBoxModel.getSelectedItem(); + } + + + // ********** ListModel implementation ********** + + public int getSize() { + return this.wrappedComboBoxModel.getSize(); + } + + public Object getElementAt(int index) { + return this.wrappedComboBoxModel.getElementAt(index); + } + + public void addListDataListener(ListDataListener l) { + this.wrappedComboBoxModel.addListDataListener(l); + } + + public void removeListDataListener(ListDataListener l) { + this.wrappedComboBoxModel.removeListDataListener(l); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleDisplayable.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleDisplayable.java new file mode 100644 index 0000000000..d10b6b44db --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleDisplayable.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.Icon; +import org.eclipse.jpt.common.utility.internal.model.AbstractModel; + +/** + * This implementation of Displayable converts any Object + * to a Displayable. Subclass it to override #displayString() and + * #icon() if necessary. Change notification will be fired if the + * object is changed. + * + * This can be used for Strings - the display string + * will simply be the String itself. + */ +public class SimpleDisplayable + extends AbstractModel + implements Displayable +{ + /** The object to be converted to a Displayable. */ + protected Object object; + + + /** + * Construct a displayable for the specified object. + */ + public SimpleDisplayable(Object object) { + super(); + this.object = object; + } + + public SimpleDisplayable(boolean b) { + this(Boolean.valueOf(b)); + } + + public SimpleDisplayable(char c) { + this(Character.valueOf(c)); + } + + public SimpleDisplayable(byte b) { + this(Byte.valueOf(b)); + } + + public SimpleDisplayable(short s) { + this(Short.valueOf(s)); + } + + public SimpleDisplayable(int i) { + this(Integer.valueOf(i)); + } + + public SimpleDisplayable(long l) { + this(Long.valueOf(l)); + } + + public SimpleDisplayable(float f) { + this(Float.valueOf(f)); + } + + public SimpleDisplayable(double d) { + this(Double.valueOf(d)); + } + + + // ********** Displayable implementation ********** + + public String displayString() { + return this.object.toString(); + } + + public Icon icon() { + return null; + } + + + // ********** accessors ********** + + public Object getObject() { + return this.object; + } + + public void setObject(Object object) { + String oldDisplayString = this.displayString(); + Icon oldIcon = this.icon(); + this.object = object; + this.firePropertyChanged(DISPLAY_STRING_PROPERTY, oldDisplayString, this.displayString()); + this.firePropertyChanged(ICON_PROPERTY, oldIcon, this.icon()); + } + + public boolean getBoolean() { + return ((Boolean) this.object).booleanValue(); + } + + public void setBoolean(boolean b) { + this.setObject(Boolean.valueOf(b)); + } + + public char getChar() { + return ((Character) this.object).charValue(); + } + + public void setChar(char c) { + this.setObject(Character.valueOf(c)); + } + + public byte getByte() { + return ((Byte) this.object).byteValue(); + } + + public void setByte(byte b) { + this.setObject(Byte.valueOf(b)); + } + + public short getShort() { + return ((Short) this.object).shortValue(); + } + + public void setShort(short s) { + this.setObject(Short.valueOf(s)); + } + + public int getInt() { + return ((Integer) this.object).intValue(); + } + + public void setInt(int i) { + this.setObject(Integer.valueOf(i)); + } + + public long getLong() { + return ((Long) this.object).longValue(); + } + + public void setLong(long l) { + this.setObject(Long.valueOf(l)); + } + + public float getFloat() { + return ((Float) this.object).floatValue(); + } + + public void setFloat(float f) { + this.setObject(Float.valueOf(f)); + } + + public double getDouble() { + return ((Double) this.object).doubleValue(); + } + + public void setDouble(double d) { + this.setObject(Double.valueOf(d)); + } + + + // ********** override methods ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.object); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListBrowser.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListBrowser.java new file mode 100644 index 0000000000..a600532a12 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListBrowser.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JOptionPane; +import javax.swing.ListModel; + +/** + * This implementation of ListChooser.Browser uses a + * JOptionPane to prompt the user for the selection. Subclasses + * can change the dialog's title, message, and/or icon. + */ +public class SimpleListBrowser + implements ListChooser.ListBrowser +{ + /** Default constructor */ + protected SimpleListBrowser() { + super(); + } + + /** + * Prompt the user using a JOptionPane. + */ + public void browse(ListChooser chooser) { + Object selection = + JOptionPane.showInputDialog( + chooser, + this.message(chooser), + this.title(chooser), + this.messageType(chooser), + this.icon(chooser), + this.selectionValues(chooser), + this.initialSelectionValue(chooser) + ); + + if (selection != null) { + chooser.getModel().setSelectedItem(selection); + } + } + + protected Object message(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected String title(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected int messageType(@SuppressWarnings("unused") JComboBox comboBox) { + return JOptionPane.QUESTION_MESSAGE; + } + + protected Icon icon(@SuppressWarnings("unused") JComboBox comboBox) { + return null; + } + + protected Object[] selectionValues(JComboBox comboBox) { + return this.convertToArray(comboBox.getModel()); + } + + protected Object initialSelectionValue(JComboBox comboBox) { + return comboBox.getModel().getSelectedItem(); + } + + /** + * Convert the list of objects in the specified list model + * into an array. + */ + protected Object[] convertToArray(ListModel model) { + int size = model.getSize(); + Object[] result = new Object[size]; + for (int i = 0; i < size; i++) { + result[i] = model.getElementAt(i); + } + return result; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListCellRenderer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListCellRenderer.java new file mode 100644 index 0000000000..6bbd290d0f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListCellRenderer.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Component; +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JList; + +/** + * This renderer should behave the same as the DefaultListCellRenderer; + * but it slightly refactors the calculation of the icon and text of the list + * cell so that subclasses can easily override the methods that build + * the icon and text. + * + * In most cases, you need only override: + * #buildIcon(Object value) + * #buildText(Object value) + */ +public class SimpleListCellRenderer + extends DefaultListCellRenderer +{ + + /** + * Construct a simple renderer. + */ + public SimpleListCellRenderer() { + super(); + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + // substitute null for the cell value so nothing is drawn initially... + super.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus); + this.setOpaque(true); + + // ...then set the icon and text manually + this.setIcon(this.buildIcon(list, value, index, isSelected, cellHasFocus)); + this.setText(this.buildText(list, value, index, isSelected, cellHasFocus)); + + this.setToolTipText(this.buildToolTipText(list, value, index, isSelected, cellHasFocus)); + + // the context will be initialized only if a reader is running + if (this.accessibleContext != null) { + this.accessibleContext.setAccessibleName(this.buildAccessibleName(list, value, index, isSelected, cellHasFocus)); + } + + return this; + } + + /** + * Return the icon representation of the specified cell + * value and other settings. (Even more settings are + * accessible via inherited getters: hasFocus, isEnabled, etc.) + */ + protected Icon buildIcon(@SuppressWarnings("unused") JList list, Object value, @SuppressWarnings("unused") int index, @SuppressWarnings("unused") boolean isSelected, @SuppressWarnings("unused") boolean cellHasFocus) { + return this.buildIcon(value); + } + + /** + * Return the icon representation of the specified cell + * value. The default is to display no icon at all unless the + * value itself is an icon. + */ + protected Icon buildIcon(Object value) { + // replicate the default behavior + return (value instanceof Icon) ? (Icon) value : null; + } + + /** + * Return the textual representation of the specified cell + * value and other settings. (Even more settings are + * accessible via inherited getters: hasFocus, isEnabled, etc.) + */ + protected String buildText(@SuppressWarnings("unused") JList list, Object value, @SuppressWarnings("unused") int index, @SuppressWarnings("unused") boolean isSelected, @SuppressWarnings("unused") boolean cellHasFocus) { + return this.buildText(value); + } + + /** + * Return the textual representation of the specified cell + * value. The default is to display the object's default string + * representation (as returned by #toString()); unless the + * value itself is an icon, in which case no text is displayed. + */ + protected String buildText(Object value) { + return (value instanceof Icon) ? "" : ((value == null) ? "" : value.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Return the text displayed when the cursor lingers over the specified cell. + * (Even more settings are accessible via inherited getters: hasFocus, isEnabled, etc.) + */ + protected String buildToolTipText(@SuppressWarnings("unused") JList list, Object value, @SuppressWarnings("unused") int index, @SuppressWarnings("unused") boolean isSelected, @SuppressWarnings("unused") boolean cellHasFocus) { + return this.buildToolTipText(value); + } + + /** + * Return the text displayed when the cursor lingers over the specified cell. + */ + protected String buildToolTipText(@SuppressWarnings("unused") Object value) { + return null; + } + + /** + * Return the accessible name to be given to the component used to render + * the given value and other settings. (Even more settings are accessible via + * inherited getters: hasFocus, isEnabled, etc.) + */ + protected String buildAccessibleName(@SuppressWarnings("unused") JList list, Object value, @SuppressWarnings("unused") int index, @SuppressWarnings("unused") boolean isSelected, @SuppressWarnings("unused") boolean cellHasFocus) { + return this.buildAccessibleName(value); + } + + /** + * Return the accessible name to be given to the component used to render + * the given value. + */ + protected String buildAccessibleName(@SuppressWarnings("unused") Object value) { + return null; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SpinnerTableCellRenderer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SpinnerTableCellRenderer.java new file mode 100644 index 0000000000..f97ada2066 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SpinnerTableCellRenderer.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Color; +import java.awt.Component; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JSpinner; +import javax.swing.JTable; +import javax.swing.SpinnerModel; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * Make the cell look like a spinner. + */ +public class SpinnerTableCellRenderer implements TableCellEditorAdapter.Renderer { + + /** the component used to paint the cell */ + protected JSpinner spinner; + + /** the listener to be notified on an immediate edit */ + protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener; + + + // ********** constructors/initialization ********** + + /** + * Construct a cell renderer that uses the default + * spinner model, which is a "number" model. + */ + public SpinnerTableCellRenderer() { + super(); + this.initialize(); + } + + /** + * Construct a cell renderer that uses the specified + * spinner model, which will determine how the values are displayed. + */ + public SpinnerTableCellRenderer(SpinnerModel model) { + this(); + this.setModel(model); + } + + protected void initialize() { + this.spinner = this.buildSpinner(); + } + + protected JSpinner buildSpinner() { + JSpinner s = new JSpinner(); + s.addChangeListener(this.buildChangeListener()); + return s; + } + + private ChangeListener buildChangeListener() { + return new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (SpinnerTableCellRenderer.this.immediateEditListener != null) { + SpinnerTableCellRenderer.this.immediateEditListener.immediateEdit(); + } + } + }; + } + + + // ********** TableCellRenderer implementation ********** + + public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { + this.spinner.setComponentOrientation(table.getComponentOrientation()); + this.spinner.setFont(table.getFont()); + this.spinner.setEnabled(table.isEnabled()); + + JComponent editor = this.editor(); + editor.setForeground(this.foregroundColor(table, value, selected, hasFocus, row, column)); + editor.setBackground(this.backgroundColor(table, value, selected, hasFocus, row, column)); + this.spinner.setBorder(this.border(table, value, selected, hasFocus, row, column)); + + this.setValue(value); + return this.spinner; + } + + /** + * Return the cell's foreground color. + */ + protected Color foregroundColor(JTable table, @SuppressWarnings("unused") Object value, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return UIManager.getColor("Table.focusCellForeground"); //$NON-NLS-1$ + } + return table.getSelectionForeground(); + } + return table.getForeground(); + } + + /** + * Return the cell's background color. + */ + protected Color backgroundColor(JTable table, @SuppressWarnings("unused") Object value, boolean selected, boolean hasFocus, int row, int column) { + if (selected) { + if (hasFocus && table.isCellEditable(row, column)) { + return UIManager.getColor("Table.focusCellBackground"); //$NON-NLS-1$ + } + return table.getSelectionBackground(); + } + return table.getBackground(); + } + + /** + * Return the cell's border. + */ + protected Border border(JTable table, @SuppressWarnings("unused") Object value, boolean selected, boolean hasFocus, @SuppressWarnings("unused") int row, @SuppressWarnings("unused") int column) { + if (hasFocus) { + return UIManager.getBorder("Table.focusCellHighlightBorder"); //$NON-NLS-1$ + } + if (selected) { + return BorderFactory.createLineBorder(table.getSelectionBackground(), 1); + } + return BorderFactory.createLineBorder(table.getBackground(), 1); + } + + /** + * Return the editor component whose colors should be set + * by the renderer. + */ + protected JComponent editor() { + JComponent editor = this.spinner.getEditor(); + if (editor instanceof JSpinner.DefaultEditor) { + // typically, the editor will be the default or one of its subclasses... + editor = ((JSpinner.DefaultEditor) editor).getTextField(); + } + return editor; + } + + /** + * Set the spinner's value + */ + protected void setValue(Object value) { + // CR#3999318 - This null check needs to be removed once JDK bug is fixed + if (value == null) { + value = Integer.valueOf(0); + } + this.spinner.setValue(value); + } + + + // ********** TableCellEditorAdapter.Renderer implementation ********** + + public Object getValue() { + return this.spinner.getValue(); + } + + public void setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener) { + this.immediateEditListener = listener; + } + + + // ********** public API ********** + + /** + * Set the spinner's model. + */ + public void setModel(SpinnerModel model) { + this.spinner.setModel(model); + } + + /** + * Return the renderer's preferred height. This allows you + * to set the row height to something the spinner will look good in.... + */ + public int preferredHeight() { + // add in space for the border top and bottom + return (int) this.spinner.getPreferredSize().getHeight() + 2; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/TableCellEditorAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/TableCellEditorAdapter.java new file mode 100644 index 0000000000..bfe82bbf6b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/TableCellEditorAdapter.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.swing; + +import java.awt.Component; +import javax.swing.AbstractCellEditor; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; + +/** + * A table cell editor that wraps a table cell renderer. + */ +public class TableCellEditorAdapter extends AbstractCellEditor implements TableCellEditor { + + /** delegate to a renderer */ + private Renderer renderer; + + + // ********** constructors/initialization ********** + + private TableCellEditorAdapter() { + super(); + } + + /** + * Construct a cell editor that behaves like the specified renderer. + */ + public TableCellEditorAdapter(Renderer renderer) { + this(); + this.initialize(renderer); + } + + protected void initialize(Renderer r) { + this.renderer = r; + r.setImmediateEditListener(this.buildImmediateEditListener()); + } + + private ImmediateEditListener buildImmediateEditListener() { + return new ImmediateEditListener() { + public void immediateEdit() { + TableCellEditorAdapter.this.stopCellEditing(); + } + }; + } + + + // ********** CellEditor implementation ********** + + public Object getCellEditorValue() { + return this.renderer.getValue(); + } + + + // ********** TableCellEditor implementation ********** + + public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row, int column) { + return this.renderer.getTableCellRendererComponent(table, value, selected, true, row, column); + } + + + // ********** Member classes ********************************************** + + /** + * This interface defines the methods that must be implemented by a renderer + * that can be wrapped by a TableCellEditorAdapter. + */ + public interface Renderer extends TableCellRenderer { + + /** + * Return the current value of the renderer. + */ + Object getValue(); + + /** + * Set the immediate edit listener + */ + void setImmediateEditListener(ImmediateEditListener listener); + } + + + public interface ImmediateEditListener { + + /** + * Called when the renderer does an "immediate edit" + */ + void immediateEdit(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/AsynchronousSynchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/AsynchronousSynchronizer.java new file mode 100644 index 0000000000..da4a234623 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/AsynchronousSynchronizer.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.synchronizers; + +import java.util.concurrent.ThreadFactory; + +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.internal.ConsumerThreadCoordinator; +import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean; +import org.eclipse.jpt.common.utility.synchronizers.Synchronizer; + +/** + * This synchronizer will perform synchronizations in a separate thread, + * allowing calls to {@link Synchronizer#synchronize()} to return immediately. + *

+ * NB: The client-supplied command should handle any exceptions + * appropriately (e.g. log the exception and return gracefully so the thread + * can continue the synchronization process). + */ +public class AsynchronousSynchronizer + implements Synchronizer +{ + /** + * This flag is shared with the synchronization/consumer thread. Setting it to true + * will trigger the synchronization to begin or, if the synchronization is + * currently executing, to execute again, once the current execution is + * complete. + */ + final SynchronizedBoolean synchronizeFlag = new SynchronizedBoolean(false); + + /** + * Most of the thread-related behavior is delegated to this coordinator. + */ + private final ConsumerThreadCoordinator consumerThreadCoordinator; + + + // ********** construction ********** + + /** + * Construct an asynchronous synchronizer that uses the specified command to + * perform the synchronization. + * Use simple JDK thread(s) for the synchronization thread(s). + * Allow the synchronization thread(s) to be assigned + * JDK-generated names. + */ + public AsynchronousSynchronizer(Command command) { + this(command, null, null); + } + + /** + * Construct an asynchronous synchronizer that uses the specified command to + * perform the synchronization. + * Use the specified thread factory to construct the synchronization thread(s). + * Allow the synchronization thread(s) to be assigned + * JDK-generated names. + */ + public AsynchronousSynchronizer(Command command, ThreadFactory threadFactory) { + this(command, threadFactory, null); + } + + /** + * Construct an asynchronous synchronizer that uses the specified command to + * perform the synchronization. Assign the synchronization thread(s) the specified + * name. + * Use simple JDK thread(s) for the synchronization thread(s). + */ + public AsynchronousSynchronizer(Command command, String threadName) { + this(command, null, threadName); + } + + /** + * Construct an asynchronous synchronizer that uses the specified command to + * perform the synchronization. + * Use the specified thread factory to construct the synchronization thread(s). + * Assign the synchronization thread(s) the specified + * name. + */ + public AsynchronousSynchronizer(Command command, ThreadFactory threadFactory, String threadName) { + super(); + if (command == null) { + throw new NullPointerException(); + } + this.consumerThreadCoordinator = new ConsumerThreadCoordinator(this.buildConsumer(command), threadFactory, threadName); + } + + ConsumerThreadCoordinator.Consumer buildConsumer(Command command) { + return new Consumer(command); + } + + + // ********** Synchronizer implementation ********** + + /** + * Build and start the synchronization thread, but postpone the first + * synchronization until requested, i.e. via a call to + * {@link #synchronize()}. + *

+ * Note: We don't clear the "synchronize" flag here; so if the flag has + * been set before getting here, the first synchronization will + * start promptly (albeit, asynchronously). + * The "synchronize" flag will be set if:

    + *
  • {@link #synchronize()} was called after the synchronizer was + * constructed but before {@link #start()} was called; or + *
  • {@link #synchronize()} was called after {@link #stop()} was called + * but before {@link #start()} was called (to restart the synchronizer); or + *
  • {@link #stop()} was called when there was an outstanding request + * for a synchronization (i.e. the "synchronization" flag was set at + * the time {@link #stop()} was called) + *
+ */ + public void start() { + this.consumerThreadCoordinator.start(); + } + + /** + * Set the "synchronize" flag so the synchronization thread will either
    + *
  • if the thread is quiesced, start a synchronization immediately, or + *
  • if the thread is currently executing a synchronization, execute another + * synchronization once the current synchronization is complete + *
+ */ + public void synchronize() { + this.synchronizeFlag.setTrue(); + } + + /** + * Interrupt the synchronization thread so that it stops executing at the + * end of the current synchronization. Suspend the current thread until + * the synchronization thread is finished executing. If any uncaught + * exceptions were thrown while the synchronization thread was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public void stop() { + this.consumerThreadCoordinator.stop(); + } + + + // ********** consumer ********** + + /** + * This implementation of {@link ConsumerThreadCoordinator.Consumer} + * will execute the client-supplied "synchronize" command. + * It will wait until the shared "synchronize" flag is set to execute the + * command. Once the comand is executed, the thread will quiesce until + * the flag is set again. If the flag was set during the execution of the + * command (either recursively by the command itself or by another thread), + * the command will be re-executed immediately. Stop the thread by calling + * {@link Thread#interrupt()}. + */ + class Consumer + implements ConsumerThreadCoordinator.Consumer + { + /** + * The client-supplied command that executes on the + * synchronization/consumer thread. + */ + private final Command command; + + Consumer(Command command) { + super(); + this.command = command; + } + + /** + * Wait until the "synchronize" flag is set, + * then clear it and allow the "synchronize" command to execute. + */ + public void waitForProducer() throws InterruptedException { + AsynchronousSynchronizer.this.synchronizeFlag.waitToSetFalse(); + } + + /** + * Execute the client-supplied command. + */ + public void execute() { + this.command.execute(); + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java new file mode 100644 index 0000000000..06af819213 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.synchronizers; + +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.internal.ConsumerThreadCoordinator; +import org.eclipse.jpt.common.utility.internal.ListenerList; +import org.eclipse.jpt.common.utility.synchronizers.CallbackSynchronizer; + +/** + * Extend the asynchronous synchronizer to notify listeners + * when a synchronization "cycle" is complete; i.e. the synchronization has, + * for the moment, handled every outstanding "synchronize" request and quiesced. + * This notification is not guaranteed to occur with every + * synchronization "cycle"; since other, unrelated, synchronizations can be + * triggered concurrently. + *

+ * NB: Listeners should handle any exceptions + * appropriately (e.g. log the exception and return gracefully so the thread + * can continue the synchronization process). + */ +public class CallbackAsynchronousSynchronizer + extends AsynchronousSynchronizer + implements CallbackSynchronizer +{ + private final ListenerList listenerList = new ListenerList(Listener.class); + + + // ********** construction ********** + + /** + * Construct a callback asynchronous synchronizer that uses the specified + * command to perform the synchronization. Allow the synchronization thread(s) + * to be assigned JDK-generated names. + */ + public CallbackAsynchronousSynchronizer(Command command) { + super(command); + } + + /** + * Construct a callback asynchronous synchronizer that uses the specified + * command to perform the synchronization. Assign the synchronization thread(s) + * the specified name. + */ + public CallbackAsynchronousSynchronizer(Command command, String threadName) { + super(command, threadName); + } + + /** + * Build a consumer that will let us know when the synchronization has + * quiesced. + */ + @Override + ConsumerThreadCoordinator.Consumer buildConsumer(Command command) { + return new CallbackConsumer(command); + } + + + // ********** CallbackSynchronizer implementation ********** + + public void addListener(Listener listener) { + this.listenerList.add(listener); + } + + public void removeListener(Listener listener) { + this.listenerList.remove(listener); + } + + /** + * Notify our listeners. + */ + void synchronizationQuiesced() { + for (Listener listener : this.listenerList.getListeners()) { + listener.synchronizationQuiesced(this); + } + } + + + // ********** synchronization thread runnable ********** + + /** + * Extend {@link AsynchronousSynchronizer.Consumer} + * to notify the synchronizer when the synchronization has quiesced + * (i.e. the command has finished executing and there are no further + * requests for synchronization). + * Because synchronization is asynchronous, no other thread will be able to + * initiate another synchronization until the synchronizer's listeners have been + * notified. Note also, the synchronizer's listeners can, themselves, + * trigger another synchronization (by directly or indirectly calling + * {@link org.eclipse.jpt.common.utility.synchronizers.Synchronizer#synchronize()}); + * but this synchronization will not occur until after all the + * listeners have been notified. + */ + class CallbackConsumer + extends Consumer + { + CallbackConsumer(Command command) { + super(command); + } + + @Override + public void execute() { + super.execute(); + // hmmm - we will notify listeners even when we our thread is "interrupted"; + // that seems ok... ~bjv + if (CallbackAsynchronousSynchronizer.this.synchronizeFlag.isFalse()) { + CallbackAsynchronousSynchronizer.this.synchronizationQuiesced(); + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackSynchronousSynchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackSynchronousSynchronizer.java new file mode 100644 index 0000000000..d811067b8b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackSynchronousSynchronizer.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.synchronizers; + +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.internal.ListenerList; +import org.eclipse.jpt.common.utility.synchronizers.CallbackSynchronizer; + +/** + * Extend the synchronous synchronizer to notify listeners + * when a synchronization "cycle" is complete; i.e. the synchronization has, + * for the moment, handled every "synchronize" request and quiesced. + * This notification is not guaranteed to occur with every + * synchronization "cycle"; + * since other, unrelated, synchronizations can be triggered concurrently. + *

+ * NB: If another synchronization is initiated while we are + * notifying the synchronizer's listeners (i.e. the 'again' flag is set), it will not + * start until all the listeners are notified. + * Note also, the synchronizer's listeners can, themselves, + * trigger another synchronization (by directly or indirectly calling + * {@link org.eclipse.jpt.common.utility.synchronizers.Synchronizer#synchronize()}); + * but this synchronization will not occur until after all the + * listeners have been notified. + */ +public class CallbackSynchronousSynchronizer + extends SynchronousSynchronizer + implements CallbackSynchronizer +{ + private final ListenerList listenerList = new ListenerList(Listener.class); + + + // ********** construction ********** + + /** + * Construct a callback synchronous synchronizer that uses the specified + * command to perform the synchronization. + */ + public CallbackSynchronousSynchronizer(Command command) { + super(command); + } + + + // ********** CallbackSynchronizer implementation ********** + + public void addListener(Listener listener) { + this.listenerList.add(listener); + } + + public void removeListener(Listener listener) { + this.listenerList.remove(listener); + } + + /** + * Notify our listeners. + */ + private void synchronizationQuiesced() { + for (Listener listener : this.listenerList.getListeners()) { + listener.synchronizationQuiesced(this); + } + } + + + // ********** override ********** + + @Override + void execute_() { + super.execute_(); + if (this.state.getValue() != State.REPEAT) { + // hmmm - we will notify listeners even when we are "stopped"; + // that seems ok... ~bjv + this.synchronizationQuiesced(); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/SynchronousSynchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/SynchronousSynchronizer.java new file mode 100644 index 0000000000..7a232f6c0b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/SynchronousSynchronizer.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.synchronizers; + +import java.util.Vector; +import org.eclipse.jpt.common.utility.Command; +import org.eclipse.jpt.common.utility.internal.CompositeException; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.SynchronizedObject; +import org.eclipse.jpt.common.utility.synchronizers.Synchronizer; + +/** + * This synchronizer will synchronize immediately and not return until the + * synchronization and any nested (recursive) synchronizations are complete. + * In some situations this implementation should be used sparingly, and for as + * short a time as possible, as it increases the probability of deadlocks. A + * deadlock can occur when {@link Synchronizer#synchronize()} is called from multiple + * threads and multiple resources are locked by the synchronization in varying + * orders. + *

+ * As defined in the {@link Synchronizer} interface, {@link Synchronizer#start()} + * and {@link Synchronizer#stop()} + * should be called in the same thread, but it is not required. + * {@link Synchronizer#synchronize()} should + * always be called in the same thread (i.e. only recursively, beyond the + * initial call); although, this too is not required. + * This thread need not be the same thread that executes + * {@link Synchronizer#start()} and {@link Synchronizer#stop()}. + */ +public class SynchronousSynchronizer + implements Synchronizer +{ + /** + * The client-supplied command that performs the synchronization. It may + * trigger further calls to {@link #synchronize()} (i.e. the + * synchronization may recurse). + */ + private final Command command; + + /** + * The synchronizer's current state. + */ + final SynchronizedObject state; + + /** + * The synchronizer's initial state is {@link #STOPPED}. + */ + enum State { + STOPPED, + READY, + EXECUTING, + REPEAT, + STOPPING + } + + /** + * A list of the uncaught exceptions thrown by the command. + */ + final Vector exceptions = new Vector(); + + + // ********** construction ********** + + /** + * Construct a synchronous synchronizer that uses the specified command to + * perform the synchronization. + */ + public SynchronousSynchronizer(Command command) { + super(); + if (command == null) { + throw new NullPointerException(); + } + this.command = command; + // use the synchronizer as the mutex so it is freed up by the wait in #stop() + this.state = new SynchronizedObject(State.STOPPED, this); + } + + + // ********** Synchronizer implementation ********** + + /** + * Set the synchronizer's {@link #state} to {@link State#READY READY} + * and execute the first synchronization. Throw an exception if the + * synchronizer is not {@link State#STOPPED STOPPED}. + */ + public synchronized void start() { + switch (this.state.getValue()) { + case STOPPED: + this.state.setValue(State.READY); + this.synchronize(); + break; + case READY: + case EXECUTING: + case REPEAT: + case STOPPING: + default: + throw this.buildIllegalStateException(); + } + } + + /** + * It's possible to come back here if the synchronization command recurses + * and triggers another synchronization. + */ + public void synchronize() { + if (this.beginSynchronization()) { + this.synchronize_(); + } + } + + /** + * A client has requested a synchronization. + * Return whether we can begin a new synchronization. + * If a synchronization is already under way, return false; + * but set the {@link #state} to {@link State#REPEAT REPEAT} + * so another synchronization will occur once the current + * synchronization is complete. + */ + private synchronized boolean beginSynchronization() { + switch (this.state.getValue()) { + case STOPPED: + // synchronization is not allowed + return false; + case READY: + // begin a new synchronization + this.state.setValue(State.EXECUTING); + return true; + case EXECUTING: + // set flag so a new synchronization will occur once the current one is finished + this.state.setValue(State.REPEAT); + return false; + case REPEAT: + // the "repeat" flag is already set + return false; + case STOPPING: + // no further synchronizations are allowed + return false; + default: + throw this.buildIllegalStateException(); + } + } + + /** + * This method should be called only once per set of "recursing" + * synchronizations. Any recursive call to {@link #synchronize()} will + * simply set the {@link #state} to {@link State#REPEAT REPEAT}, + * causing the command to execute again. + */ + private void synchronize_() { + do { + this.execute(); + } while (this.repeatSynchronization()); + } + + /** + * Execute the client-supplied command. Do not allow any unhandled + * exceptions to kill the thread. Store them up for later pain. + */ + private void execute() { + try { + this.execute_(); + } catch (Throwable ex) { + this.exceptions.add(ex); + } + } + + /** + * By default, just execute the command. + */ + void execute_() { + this.command.execute(); + } + + /** + * The current synchronization has finished. + * Return whether we should begin another synchronization. + */ + private synchronized boolean repeatSynchronization() { + switch (this.state.getValue()) { + case STOPPED: + case READY: + throw this.buildIllegalStateException(); + case EXECUTING: + // synchronization has finished and there are no outstanding requests for another; return to "ready" + this.state.setValue(State.READY); + return false; + case REPEAT: + // the "repeat" flag was set; clear it and start another synchronization + this.state.setValue(State.EXECUTING); + return true; + case STOPPING: + // a client has initiated a "stop"; mark the "stop" complete and perform no more synchronizations + this.state.setValue(State.STOPPED); + return false; + default: + throw this.buildIllegalStateException(); + } + } + + /** + * Set the flags so that no further synchronizations occur. If any uncaught + * exceptions were thrown while the synchronization was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public synchronized void stop() { + switch (this.state.getValue()) { + case STOPPED: + throw this.buildIllegalStateException(); + case READY: + // simply return to "stopped" state + this.state.setValue(State.STOPPED); + break; + case EXECUTING: + case REPEAT: + // set the "stopping" flag and wait until the synchronization has finished + this.state.setValue(State.STOPPING); + this.waitUntilStopped(); + break; + case STOPPING: + throw this.buildIllegalStateException(); + default: + throw this.buildIllegalStateException(); + } + + if (this.exceptions.size() > 0) { + Throwable[] temp = this.exceptions.toArray(new Throwable[this.exceptions.size()]); + this.exceptions.clear(); + throw new CompositeException(temp); + } + } + + /** + * This wait will free up the synchronizer's synchronized methods + * (since the synchronizer is the state's mutex). + */ + private void waitUntilStopped() { + try { + this.state.waitUntilValueIs(State.STOPPED); + } catch (InterruptedException ex) { + // the thread that called #stop() was interrupted while waiting + // for the synchronization to finish - ignore; + // 'state' is still set to 'STOPPING', so the #synchronize_() loop + // will still stop - we just won't wait around for it... + } + } + + private IllegalStateException buildIllegalStateException() { + return new IllegalStateException("state: " + this.state); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.state); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/Model.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/Model.java new file mode 100644 index 0000000000..050cb25eb1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/Model.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model; + +import org.eclipse.jpt.common.utility.model.listener.ChangeListener; +import org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.common.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.common.utility.model.listener.StateChangeListener; +import org.eclipse.jpt.common.utility.model.listener.TreeChangeListener; + +/** + * Interface to be implemented by models that notify listeners of + * changes to bound properties, collections, lists, and/or trees. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @see ChangeListener + * @see StateChangeListener + * @see PropertyChangeListener + * @see CollectionChangeListener + * @see ListChangeListener + * @see TreeChangeListener + * @see org.eclipse.jpt.common.utility.internal.model.AbstractModel + */ +// TODO use objects (IDs?) instead of strings to identify aspects? +public interface Model { + + // ********** change ********** + + /** + * Add a listener that listens to all change events. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addChangeListener(ChangeListener listener); + + /** + * Remove the specified change listener. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removeChangeListener(ChangeListener listener); + + + // ********** state change ********** + + /** + * Add a listener that listens to all state change events. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addStateChangeListener(StateChangeListener listener); + + /** + * Remove the specified state change listener. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removeStateChangeListener(StateChangeListener listener); + + + // ********** property change ********** + + /** + * Add a listener that listens to all property change events with + * the specified property name. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addPropertyChangeListener(String propertyName, PropertyChangeListener listener); + + /** + * Remove a listener that listens to all property change events, + * with the specified property name. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removePropertyChangeListener(String propertyName, PropertyChangeListener listener); + + + // ********** collection change ********** + + /** + * Add a listener that listens to all collection change events with + * the specified collection name. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addCollectionChangeListener(String collectionName, CollectionChangeListener listener); + + /** + * Remove a listener that listens to all collection change events, + * with the specified collection name. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removeCollectionChangeListener(String collectionName, CollectionChangeListener listener); + + + // ********** list change ********** + + /** + * Add a listener that listens to all list change events with + * the specified list name. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addListChangeListener(String listName, ListChangeListener listener); + + /** + * Remove a listener that listens to all list change events, + * with the specified list name. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removeListChangeListener(String listName, ListChangeListener listener); + + + // ********** tree change ********** + + /** + * Add a listener that listens to all tree change events with + * the specified tree name. + * Throw an exception if the same listener is added more than once. + * The listener cannot be null. + */ + void addTreeChangeListener(String treeName, TreeChangeListener listener); + + /** + * Remove a listener that listens to all tree change events, + * with the specified tree name. + * Throw an exception if the listener is null or if the listener was never added. + */ + void removeTreeChangeListener(String treeName, TreeChangeListener listener); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ChangeEvent.java new file mode 100644 index 0000000000..9d702bac6c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ChangeEvent.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.EventObject; +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Abstract class for all the change events that can be fired by models. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class ChangeEvent extends EventObject { + + private static final long serialVersionUID = 1L; + + + /** + * Construct a new change event. + * + * @param source The object on which the event initially occurred. + */ + protected ChangeEvent(Model source) { + super(source); + } + + /** + * Covariant override. + */ + @Override + public Model getSource() { + return (Model) super.getSource(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + StringTools.buildSimpleToStringOn(this, sb); + sb.append('('); + int len = sb.length(); + this.toString(sb); + if (sb.length() == len) { + sb.deleteCharAt(len - 1); + } else { + sb.append(')'); + } + return sb.toString(); + } + + protected void toString(@SuppressWarnings("unused") StringBuilder sb) { + // subclasses should override this to do something a bit more helpful + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionAddEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionAddEvent.java new file mode 100644 index 0000000000..c2a2e7bc9c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionAddEvent.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "collection add" event gets delivered whenever a model adds items to a + * "bound" or "constrained" collection. A CollectionAddEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +/* + * Design options: + * - create a collection to wrap a single added or removed item + * (this is the option we implemented below and in collaborating code) + * since there is no way to optimize downstream code for + * single items, we take another performance hit by building + * a collection each time (@see Collections#singleton(Object)) + * and forcing downstream code to use an iterator every time + * + * - fire a separate event for each item added or removed + * eliminates any potential for optimizations to downstream code + * + * - add protocol to support both single items and collections + * adds conditional logic to downstream code + */ +public final class CollectionAddEvent extends CollectionEvent { + + /** The items added to the collection. */ + private final Object[] items; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new collection add event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + * @param item The item added to the collection. + */ + public CollectionAddEvent(Model source, String collectionName, Object item) { + this(source, collectionName, new Object[] {item}); + } + + /** + * Construct a new collection add event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + * @param items The items added to the collection. + */ + public CollectionAddEvent(Model source, String collectionName, Collection items) { + this(source, collectionName, items.toArray()); // NPE if 'items' is null + } + + private CollectionAddEvent(Model source, String collectionName, Object[] items) { + super(source, collectionName); + this.items = items; + } + + + // ********** standard state ********** + + /** + * Return the items added to the collection. + */ + public Iterable getItems() { + return new ArrayIterable(this.items); + } + + /** + * Return the number of items added to the collection. + */ + public int getItemsSize() { + return this.items.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.items); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public CollectionAddEvent clone(Model newSource) { + return this.clone(newSource, this.collectionName); + } + + /** + * Return a copy of the event with the specified source and collection name + * replacing the current source and collection name. + */ + public CollectionAddEvent clone(Model newSource, String newCollectionName) { + return new CollectionAddEvent(newSource, newCollectionName, this.items); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionChangeEvent.java new file mode 100644 index 0000000000..5c2c75903c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionChangeEvent.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "collection change" event gets delivered whenever a model changes a "bound" + * or "constrained" collection in a manner that is not easily characterized by + * the other collection events. + * A CollectionChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}. + * A CollectionChangeEvent is accompanied by the collection name and + * the current state of the collection. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class CollectionChangeEvent extends CollectionEvent { + + /** + * The the collection in its current state. + * Clients will need to calculate the necessary changes to synchronize + * with the collection. + */ + private final Object[] collection; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new collection change event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + */ + public CollectionChangeEvent(Model source, String collectionName, Collection collection) { + this(source, collectionName, collection.toArray()); // NPE if 'collection' is null + } + + private CollectionChangeEvent(Model source, String collectionName, Object[] collection) { + super(source, collectionName); + this.collection = collection; + } + + + // ********** standard state ********** + + /** + * Return the current state of the collection. + */ + public Iterable getCollection() { + return new ArrayIterable(this.collection); + } + + /** + * Return the number of items in the current state of the collection. + */ + public int getCollectionSize() { + return this.collection.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.collection); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public CollectionChangeEvent clone(Model newSource) { + return this.clone(newSource, this.collectionName); + } + + /** + * Return a copy of the event with the specified source and collection name + * replacing the current source and collection name. + */ + public CollectionChangeEvent clone(Model newSource, String newCollectionName) { + return new CollectionChangeEvent(newSource, newCollectionName, this.collection); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionClearEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionClearEvent.java new file mode 100644 index 0000000000..0fb2adce58 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionClearEvent.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "collection clear" event gets delivered whenever a model clears + * a "bound" or "constrained" collection. A CollectionClearEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class CollectionClearEvent extends CollectionEvent { + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new collection clear event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + */ + public CollectionClearEvent(Model source, String collectionName) { + super(source, collectionName); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public CollectionClearEvent clone(Model newSource) { + return this.clone(newSource, this.collectionName); + } + + /** + * Return a copy of the event with the specified source and collection name + * replacing the current source and collection name. + */ + public CollectionClearEvent clone(Model newSource, String newCollectionName) { + return new CollectionClearEvent(newSource, newCollectionName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionEvent.java new file mode 100644 index 0000000000..d3e0bda965 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionEvent.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; +// TODO add "item/original/nested event" for item changed? +/** + * A "collection" event gets delivered whenever a model changes a "bound" + * or "constrained" collection. A CollectionEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}. + * The intent is that any listener + * can keep itself synchronized with the model's collection via the collection + * events it receives and need not maintain a reference to the original + * collection. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class CollectionEvent extends ChangeEvent { + + /** Name of the collection that changed. */ + final String collectionName; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a new collection event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + */ + public CollectionEvent(Model source, String collectionName) { + super(source); + if (collectionName == null) { + throw new NullPointerException(); + } + this.collectionName = collectionName; + } + + /** + * Return the programmatic name of the collection that was changed. + */ + public String getCollectionName() { + return this.collectionName; + } + + @Override + protected void toString(StringBuilder sb) { + sb.append(this.collectionName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionRemoveEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionRemoveEvent.java new file mode 100644 index 0000000000..a12218d9b3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionRemoveEvent.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "collection remove" event gets delivered whenever a model removes items + * from a "bound" or "constrained" collection. A CollectionRemoveEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +/* + * See design discussion in CollectionAddEvent + */ +public final class CollectionRemoveEvent extends CollectionEvent { + + /** The items removed from the collection. */ + private final Object[] items; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new collection remove event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + * @param item The item removed from the collection. + */ + public CollectionRemoveEvent(Model source, String collectionName, Object item) { + this(source, collectionName, new Object[] {item}); + } + + /** + * Construct a new collection remove event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the collection that was changed. + * @param items The items removed from the collection. + */ + public CollectionRemoveEvent(Model source, String collectionName, Collection items) { + this(source, collectionName, items.toArray()); // NPE if 'items' is null + } + + private CollectionRemoveEvent(Model source, String collectionName, Object[] items) { + super(source, collectionName); + this.items = items; + } + + + // ********** standard state ********** + + /** + * Return the items removed from the collection. + */ + public Iterable getItems() { + return new ArrayIterable(this.items); + } + + /** + * Return the number of items removed from the collection. + */ + public int getItemsSize() { + return this.items.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.items); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public CollectionRemoveEvent clone(Model newSource) { + return this.clone(newSource, this.collectionName); + } + + /** + * Return a copy of the event with the specified source and collection name + * replacing the current source and collection name. + */ + public CollectionRemoveEvent clone(Model newSource, String newCollectionName) { + return new CollectionRemoveEvent(newSource, newCollectionName, this.items); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListAddEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListAddEvent.java new file mode 100644 index 0000000000..900740add7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListAddEvent.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list add" event gets delivered whenever a model adds items to a + * "bound" or "constrained" list. A ListAddEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +/* + * See design discussion in CollectionAddEvent + */ +public final class ListAddEvent extends ListEvent { + + /** The index at which the items were added. */ + private final int index; + + /** The items added to the list. */ + private final Object[] items; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new list add event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + * @param index The index at which the items were added. + * @param item The item added to the list. + */ + public ListAddEvent(Model source, String listName, int index, Object item) { + this(source, listName, index, new Object[] {item}); + } + + /** + * Construct a new list add event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + * @param index The index at which the items were added. + * @param items The items added to the list. + */ + public ListAddEvent(Model source, String listName, int index, List items) { + this(source, listName, index, items.toArray()); // NPE if 'items' is null + } + + private ListAddEvent(Model source, String listName, int index, Object[] items) { + super(source, listName); + this.index = index; + this.items = items; + } + + + // ********** standard state ********** + + /** + * Return the index at which the items were added to the list. + */ + public int getIndex() { + return this.index; + } + + /** + * Return the items added to the list. + */ + public Iterable getItems() { + return new ArrayIterable(this.items); + } + + /** + * Return the number of items added to the list. + */ + public int getItemsSize() { + return this.items.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.items); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListAddEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListAddEvent clone(Model newSource, String newListName) { + return this.clone(newSource, newListName, 0); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name and displacing + * the index by the specified amount. + */ + public ListAddEvent clone(Model newSource, String newListName, int offset) { + return new ListAddEvent(newSource, newListName, this.index + offset, this.items); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListChangeEvent.java new file mode 100644 index 0000000000..ebcf2919c6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListChangeEvent.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list change" event gets delivered whenever a model changes a "bound" + * or "constrained" list in a manner that is not easily characterized by + * the other list events. + * A ListChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + * A ListChangeEvent is accompanied by the list name and + * the current state of the list. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class ListChangeEvent extends ListEvent { + + /** + * The the list in its current state. + * Clients will need to calculate the necessary changes to synchronize + * with the list. + */ + private final Object[] list; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new list change event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + */ + public ListChangeEvent(Model source, String listName, List list) { + this(source, listName, list.toArray()); // NPE if 'list' is null + } + + private ListChangeEvent(Model source, String listName, Object[] list) { + super(source, listName); + this.list = list; + } + + + // ********** standard state ********** + + /** + * Return the current state of the list. + */ + public Iterable getList() { + return new ArrayIterable(this.list); + } + + /** + * Return the number of items in the current state of the list. + */ + public int getListSize() { + return this.list.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.list); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListChangeEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListChangeEvent clone(Model newSource, String newListName) { + return new ListChangeEvent(newSource, newListName, this.list); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListClearEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListClearEvent.java new file mode 100644 index 0000000000..8faf558131 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListClearEvent.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list clear" event gets delivered whenever a model clears + * a "bound" or "constrained" list. A ListClearEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class ListClearEvent extends ListEvent { + + private static final long serialVersionUID = 1L; + + + // ********** constructor ********** + + /** + * Construct a new list clear event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + */ + public ListClearEvent(Model source, String listName) { + super(source, listName); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListClearEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListClearEvent clone(Model newSource, String newListName) { + return new ListClearEvent(newSource, newListName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListEvent.java new file mode 100644 index 0000000000..acd345845c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListEvent.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +// TODO add "item/original/nested event" for item changed? +/** + * A "list" event gets delivered whenever a model changes a "bound" + * or "constrained" list. A ListEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + * The intent is that any listener + * can keep itself synchronized with the model's list via the list + * events it receives and need not maintain a reference to the original + * list. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class ListEvent extends ChangeEvent { + + /** Name of the list that changed. */ + final String listName; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a new list event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + */ + public ListEvent(Model source, String listName) { + super(source); + if (listName == null) { + throw new NullPointerException(); + } + this.listName = listName; + } + + /** + * Return the programmatic name of the list that was changed. + */ + public String getListName() { + return this.listName; + } + + @Override + protected void toString(StringBuilder sb) { + sb.append(this.listName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListMoveEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListMoveEvent.java new file mode 100644 index 0000000000..4627edb5b3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListMoveEvent.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list move" event gets delivered whenever a model moves the elements in + * a "bound" or "constrained" list. A ListMoveEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class ListMoveEvent extends ListEvent { + + /** The index to which the items were moved. */ + private final int targetIndex; + + /** The index from which the items were moved. */ + private final int sourceIndex; + + /** The number of items moved. */ + private final int length; + + private static final long serialVersionUID = 1L; + + + // ********** constructor ********** + + /** + * Construct a new list move event. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + * @param targetIndex The index to which the items were moved. + * @param sourceIndex The index from which the items were moved. + * @param length The number of items moved. + */ + public ListMoveEvent(Model source, String listName, int targetIndex, int sourceIndex, int length) { + super(source, listName); + this.targetIndex = targetIndex; + this.sourceIndex = sourceIndex; + this.length = length; + } + + + // ********** standard state ********** + + /** + * Return the index to which the items were moved. + */ + public int getTargetIndex() { + return this.targetIndex; + } + + /** + * Return the index from which the items were moved. + */ + public int getSourceIndex() { + return this.sourceIndex; + } + + /** + * Return the number of items moved. + */ + public int getLength() { + return this.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + sb.append(this.sourceIndex); + sb.append(" => "); //$NON-NLS-1$ + sb.append(this.targetIndex); + sb.append(" length="); //$NON-NLS-1$ + sb.append(this.length); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListMoveEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListMoveEvent clone(Model newSource, String newListName) { + return this.clone(newSource, newListName, 0); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name and displacing + * the index by the specified amount. + */ + public ListMoveEvent clone(Model newSource, String newListName, int offset) { + return new ListMoveEvent(newSource, newListName, this.targetIndex + offset, this.sourceIndex + offset, this.length); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListRemoveEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListRemoveEvent.java new file mode 100644 index 0000000000..41b82d8ca6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListRemoveEvent.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list remove" event gets delivered whenever a model removes items + * from a "bound" or "constrained" list. A ListRemoveEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +/* + * See design discussion in CollectionAddEvent + */ +public final class ListRemoveEvent extends ListEvent { + + /** The index at which the items were removed. */ + private final int index; + + /** The items removed from the list. */ + private final Object[] items; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new list remove event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the list that was changed. + * @param index The index at which the items were removed. + * @param item The item removed from the list. + */ + public ListRemoveEvent(Model source, String listName, int index, Object item) { + this(source, listName, index, new Object[] {item}); + } + + /** + * Construct a new list remove event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the list that was changed. + * @param index The index at which the items were removed. + * @param items The items removed from the list. + */ + public ListRemoveEvent(Model source, String listName, int index, Collection items) { + this(source, listName, index, items.toArray()); // NPE if 'items' is null + } + + private ListRemoveEvent(Model source, String listName, int index, Object[] items) { + super(source, listName); + this.index = index; + this.items = items; + } + + + // ********** standard state ********** + + /** + * Return the index at which the items were removed from the list. + */ + public int getIndex() { + return this.index; + } + + /** + * Return the items removed from the list. + */ + public Iterable getItems() { + return new ArrayIterable(this.items); + } + + /** + * Return the number of items removed from the list. + */ + public int getItemsSize() { + return this.items.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.items); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListRemoveEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListRemoveEvent clone(Model newSource, String newListName) { + return this.clone(newSource, newListName, 0); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name and displacing + * the index by the specified amount. + */ + public ListRemoveEvent clone(Model newSource, String newListName, int offset) { + return new ListRemoveEvent(newSource, newListName, this.index + offset, this.items); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListReplaceEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListReplaceEvent.java new file mode 100644 index 0000000000..dd6f3760e0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListReplaceEvent.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "list replace" event gets delivered whenever a model replaces items in a + * "bound" or "constrained" list. A ListReplaceEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class ListReplaceEvent extends ListEvent { + + /** The index at which the items were replaced. */ + private final int index; + + /** The new items that replaced the old items in the list. */ + private final Object[] newItems; + + /** The old items that were replaced by the new items in the list. */ + private final Object[] oldItems; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new list replace event for a list of replaced items. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + * @param index The index at which the item in the list was replaced. + * @param newItem The new item in the list. + * @param oldItem The old item in the list that were replaced. + */ + public ListReplaceEvent(Model source, String listName, int index, Object newItem, Object oldItem) { + this(source, listName, index, new Object[] {newItem}, new Object[] {oldItem}); + } + + /** + * Construct a new list replace event for a list of replaced items. + * + * @param source The object on which the event initially occurred. + * @param listName The programmatic name of the list that was changed. + * @param index The index at which the items in the list were replaced. + * @param newItems The new items in the list. + * @param oldItems The old items in the list that were replaced. + */ + public ListReplaceEvent(Model source, String listName, int index, List newItems, List oldItems) { + this(source, listName, index, newItems.toArray(), oldItems.toArray()); // NPE if either 'newItems' or 'oldItems' is null + } + + private ListReplaceEvent(Model source, String listName, int index, Object[] newItems, Object[] oldItems) { + super(source, listName); + if (newItems.length != oldItems.length) { + throw new IllegalArgumentException("sizes must match - new items size: " + newItems.length //$NON-NLS-1$ + + " old items size: " + oldItems.length); //$NON-NLS-1$ + } + this.index = index; + this.newItems = newItems; + this.oldItems = oldItems; + } + + + // ********** standard state ********** + + /** + * Return the index at which the items were replaced in the list. + */ + public int getIndex() { + return this.index; + } + + /** + * Return the new items that replaced the old items in the list. + */ + public Iterable getNewItems() { + return new ArrayIterable(this.newItems); + } + + /** + * Return the old items that were replaced by the new items in the list. + */ + public Iterable getOldItems() { + return new ArrayIterable(this.oldItems); + } + + /** + * Return the number of items that were replaced. + */ + public int getItemsSize() { + return this.newItems.length; + } + + @Override + protected void toString(StringBuilder sb) { + super.toString(sb); + sb.append(": "); //$NON-NLS-1$ + StringTools.append(sb, this.oldItems); + sb.append(" => "); //$NON-NLS-1$ + StringTools.append(sb, this.newItems); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public ListReplaceEvent clone(Model newSource) { + return this.clone(newSource, this.listName); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name. + */ + public ListReplaceEvent clone(Model newSource, String newListName) { + return this.clone(newSource, newListName, 0); + } + + /** + * Return a copy of the event with the specified source and list name + * replacing the current source and list name and displacing + * the index by the specified amount. + */ + public ListReplaceEvent clone(Model newSource, String newListName, int offset) { + return new ListReplaceEvent(newSource, newListName, this.index + offset, this.newItems, this.oldItems); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/PropertyChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/PropertyChangeEvent.java new file mode 100644 index 0000000000..55c7475e85 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/PropertyChangeEvent.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "property change" event gets delivered whenever a model changes a "bound" + * or "constrained" property. A PropertyChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener}. + * A PropertyChangeEvent is accompanied by the old and new values + * of the property. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class PropertyChangeEvent extends ChangeEvent { + + /** Name of the property that changed. */ + private final String propertyName; + + /** The property's old value, before the change. */ + private final Object oldValue; + + /** The property's new value, after the change. */ + private final Object newValue; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new property change event. + * + * @param source The object on which the event initially occurred. + * @param propertyName The programmatic name of the property that was changed. + * @param oldValue The old value of the property. + * @param newValue The new value of the property. + */ + public PropertyChangeEvent(Model source, String propertyName, Object oldValue, Object newValue) { + super(source); + if (propertyName == null) { + throw new NullPointerException(); + } + this.propertyName = propertyName; + this.oldValue = oldValue; + this.newValue = newValue; + } + + + // ********** standard state ********** + + /** + * Return the programmatic name of the property that was changed. + */ + public String getPropertyName() { + return this.propertyName; + } + + /** + * Return the old value of the property. + */ + public Object getOldValue() { + return this.oldValue; + } + + /** + * Return the new value of the property. + */ + public Object getNewValue() { + return this.newValue; + } + + @Override + protected void toString(StringBuilder sb) { + sb.append(this.propertyName); + sb.append(": "); //$NON-NLS-1$ + sb.append(this.oldValue); + sb.append(" => "); //$NON-NLS-1$ + sb.append(this.newValue); + } + + + // ********** cloning ********** + + public PropertyChangeEvent clone(Model newSource) { + return new PropertyChangeEvent(newSource, this.propertyName, this.oldValue, this.newValue); + } + + /** + * Return a copy of the event with the specified source and property name + * replacing the current source and property name. + */ + public PropertyChangeEvent clone(Model newSource, String newPropertyName) { + return new PropertyChangeEvent(newSource, newPropertyName, this.oldValue, this.newValue); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/StateChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/StateChangeEvent.java new file mode 100644 index 0000000000..fdac399c4b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/StateChangeEvent.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A generic "state change" event gets delivered whenever a model changes to + * such extent that it cannot be delineated all aspects of it that have changed. + * Any listener can synchronize with the model as necessary since the model is + * available as the event's 'source'. + * A StateChangeEvent is sent as an argument to the + * {@link org.eclipse.jpt.common.utility.model.listener.StateChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class StateChangeEvent extends ChangeEvent { + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new state change event. + * + * @param source The object on which the event initially occurred. + */ + public StateChangeEvent(Model source) { + super(source); + } + + + // ********** cloning ********** + + public StateChangeEvent clone(Model newSource) { + return new StateChangeEvent(newSource); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeAddEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeAddEvent.java new file mode 100644 index 0000000000..0550271a5d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeAddEvent.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "tree add" event gets delivered whenever a model adds a node to a "bound" + * or "constrained" tree. A TreeChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class TreeAddEvent extends TreeEvent { + + /** + * Path to the node added to the tree. + */ + protected final Object[] path; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new tree add event. + * + * @param source The object on which the event initially occurred. + * @param treeName The programmatic name of the tree that was changed. + * @param path The path to the part of the tree that was added. + */ + public TreeAddEvent(Model source, String treeName, List path) { + this(source, treeName, path.toArray()); // NPE if 'path' is null + } + + private TreeAddEvent(Model source, String treeName, Object[] path) { + super(source, treeName); + this.path = path; + } + + + // ********** standard state ********** + + /** + * Return the path to the part of the tree that was added. + */ + public Iterable getPath() { + return new ArrayIterable(this.path); + } + + + // ********** cloning ********** + + public TreeAddEvent clone(Model newSource) { + return this.clone(newSource, this.treeName); + } + + /** + * Return a copy of the event with the specified source and tree name + * replacing the current source and tree name. + */ + public TreeAddEvent clone(Model newSource, String newTreeName) { + return new TreeAddEvent(newSource, newTreeName, this.path); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeChangeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeChangeEvent.java new file mode 100644 index 0000000000..7ad7e03db5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeChangeEvent.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.Collection; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "tree change" event gets delivered whenever a model changes a "bound" + * or "constrained" tree in a manner that is not easily characterized by + * the other tree events. + * A TreeChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class TreeChangeEvent extends TreeEvent { + + /** + * The current nodes in the changed tree. + */ + protected final Object[] nodes; + + private static final long serialVersionUID = 1L; + + + // ********** constructor ********** + + /** + * Construct a new tree change event. + * + * @param source The object on which the event initially occurred. + * @param treeName The programmatic name of the tree that was changed. + * @param nodes The current nodes in the changed tree. + */ + public TreeChangeEvent(Model source, String treeName, Collection nodes) { + this(source, treeName, nodes.toArray()); // NPE if 'nodes' is null + } + + private TreeChangeEvent(Model source, String treeName, Object[] nodes) { + super(source, treeName); + this.nodes = nodes; + } + + + // ********** standard state ********** + + /** + * Return the current nodes in the changed tree. + */ + public Iterable getNodes() { + return new ArrayIterable(this.nodes); + } + + /** + * Return the current nodes in the changed tree. + */ + public int getNodesSize() { + return this.nodes.length; + } + + + // ********** cloning ********** + + public TreeChangeEvent clone(Model newSource) { + return this.clone(newSource, this.treeName); + } + + /** + * Return a copy of the event with the specified source and tree name + * replacing the current source and tree name. + */ + public TreeChangeEvent clone(Model newSource, String newTreeName) { + return new TreeChangeEvent(newSource, newTreeName, this.nodes); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeClearEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeClearEvent.java new file mode 100644 index 0000000000..6d96568a7f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeClearEvent.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "tree clear" event gets delivered whenever a model clears + * a "bound" or "constrained" tree. A TreeClearEvent is sent + * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class TreeClearEvent extends TreeEvent { + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new tree clear event. + * + * @param source The object on which the event initially occurred. + * @param collectionName The programmatic name of the tree that was changed. + */ + public TreeClearEvent(Model source, String treeName) { + super(source, treeName); + } + + + // ********** cloning ********** + + /** + * Return a copy of the event with the specified source + * replacing the current source. + */ + public TreeClearEvent clone(Model newSource) { + return this.clone(newSource, this.treeName); + } + + /** + * Return a copy of the event with the specified source and collection name + * replacing the current source and collection name. + */ + public TreeClearEvent clone(Model newSource, String newCollectionName) { + return new TreeClearEvent(newSource, newCollectionName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeEvent.java new file mode 100644 index 0000000000..e57777aef1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeEvent.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "tree" event gets delivered whenever a model changes a "bound" + * or "constrained" tree. A TreeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}. + * The intent is that any listener + * can keep itself synchronized with the model's tree via the tree events + * it receives and need not maintain a reference to the original tree. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class TreeEvent extends ChangeEvent { + + /** Name of the tree that changed. */ + final String treeName; + + private static final long serialVersionUID = 1L; + + + /** + * Construct a new tree event. + * + * @param source The object on which the event initially occurred. + * @param treeName The programmatic name of the tree that was changed. + */ + public TreeEvent(Model source, String treeName) { + super(source); + if (treeName == null) { + throw new NullPointerException(); + } + this.treeName = treeName; + } + + /** + * Return the programmatic name of the tree that was changed. + */ + public String getTreeName() { + return this.treeName; + } + + @Override + protected void toString(StringBuilder sb) { + sb.append(this.treeName); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeRemoveEvent.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeRemoveEvent.java new file mode 100644 index 0000000000..afa3157678 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeRemoveEvent.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.event; + +import java.util.List; + +import org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * A "tree remove" event gets delivered whenever a model removes a node rom a + * "bound" or "constrained" tree. A TreeChangeEvent is sent as an + * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public final class TreeRemoveEvent extends TreeEvent { + + /** + * Path to the node removed from the tree. + */ + protected final Object[] path; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a new tree remove event. + * + * @param source The object on which the event initially occurred. + * @param treeName The programmatic name of the tree that was changed. + * @param path The path to the part of the tree that was removed. + */ + public TreeRemoveEvent(Model source, String treeName, List path) { + this(source, treeName, path.toArray()); // NPE if 'path' is null + } + + private TreeRemoveEvent(Model source, String treeName, Object[] path) { + super(source, treeName); + this.path = path; + } + + + // ********** standard state ********** + + /** + * Return the path to the part of the tree that was removed. + */ + public Iterable getPath() { + return new ArrayIterable(this.path); + } + + + // ********** cloning ********** + + public TreeRemoveEvent clone(Model newSource) { + return this.clone(newSource, this.treeName); + } + + /** + * Return a copy of the event with the specified source and tree name + * replacing the current source and tree name. + */ + public TreeRemoveEvent clone(Model newSource, String newTreeName) { + return new TreeRemoveEvent(newSource, newTreeName, this.path); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeAdapter.java new file mode 100644 index 0000000000..51dcbd0391 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeAdapter.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * Convenience implementation of {@link ChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class ChangeAdapter implements ChangeListener { + + // ***** state + public void stateChanged(StateChangeEvent event) { + // do nothing + } + + // ***** property + public void propertyChanged(PropertyChangeEvent event) { + // do nothing + } + + // ***** collection + public void itemsAdded(CollectionAddEvent event) { + // do nothing + } + + public void itemsRemoved(CollectionRemoveEvent event) { + // do nothing + } + + public void collectionCleared(CollectionClearEvent event) { + // do nothing + } + + public void collectionChanged(CollectionChangeEvent event) { + // do nothing + } + + // ***** list + public void itemsAdded(ListAddEvent event) { + // do nothing + } + + public void itemsRemoved(ListRemoveEvent event) { + // do nothing + } + + public void itemsReplaced(ListReplaceEvent event) { + // do nothing + } + + public void itemsMoved(ListMoveEvent event) { + // do nothing + } + + public void listCleared(ListClearEvent event) { + // do nothing + } + + public void listChanged(ListChangeEvent event) { + // do nothing + } + + // ***** tree + public void nodeAdded(TreeAddEvent event) { + // do nothing + } + + public void nodeRemoved(TreeRemoveEvent event) { + // do nothing + } + + public void treeCleared(TreeClearEvent event) { + // do nothing + } + + public void treeChanged(TreeChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeListener.java new file mode 100644 index 0000000000..c5c6203fac --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeListener.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +/** + * General purpose change listener. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ChangeListener + extends StateChangeListener, PropertyChangeListener, CollectionChangeListener, ListChangeListener, TreeChangeListener +{ + // combine the other listener interfaces +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeAdapter.java new file mode 100644 index 0000000000..99ca881f23 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeAdapter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; + +/** + * Convenience implementation of {@link CollectionChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class CollectionChangeAdapter implements CollectionChangeListener { + + /** + * Default constructor. + */ + public CollectionChangeAdapter() { + super(); + } + + public void itemsAdded(CollectionAddEvent event) { + // do nothing + } + + public void itemsRemoved(CollectionRemoveEvent event) { + // do nothing + } + + public void collectionCleared(CollectionClearEvent event) { + // do nothing + } + + public void collectionChanged(CollectionChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeListener.java new file mode 100644 index 0000000000..a8ff66487c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeListener.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; + +/** + * A "collection change" event gets fired whenever a model changes a "bound" + * collection. You can register a CollectionChangeListener with a source + * model so as to be notified of any bound collection updates. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface CollectionChangeListener extends EventListener { + + /** + * This method gets called when items are added to a bound collection. + * + * @param event An event describing the event source, + * the collection that changed, and the items that were added. + */ + void itemsAdded(CollectionAddEvent event); + + /** + * This method gets called when items are removed from a bound collection. + * + * @param event An event describing the event source, + * the collection that changed, and the items that were removed. + */ + void itemsRemoved(CollectionRemoveEvent event); + + /** + * This method gets called when a bound collection is cleared. + * + * @param event An event describing the event source + * and the collection that changed. + */ + void collectionCleared(CollectionClearEvent event); + + /** + * This method gets called when a bound collection is changed in a manner + * that is not easily characterized by the other methods in this interface. + * + * @param event An event describing the event source + * and the collection that changed. + */ + void collectionChanged(CollectionChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CommandChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CommandChangeListener.java new file mode 100644 index 0000000000..5b3fd4a76f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CommandChangeListener.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.Command; + +/** + * Convenience implementation of {@link ChangeListener}. + * All change notifications are funneled through a single command. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class CommandChangeListener + extends SimpleChangeListener +{ + protected final Command command; + + /** + * Construct a change listener that executes the specified command whenever + * it receives any change notification from the model to which it is added + * as a listener. + */ + public CommandChangeListener(Command command) { + super(); + this.command = command; + } + + @Override + protected void modelChanged() { + this.command.execute(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeAdapter.java new file mode 100644 index 0000000000..a2b85b47f7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeAdapter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; + +/** + * Convenience implementation of {@link ListChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class ListChangeAdapter implements ListChangeListener { + + /** + * Default constructor. + */ + public ListChangeAdapter() { + super(); + } + + public void itemsAdded(ListAddEvent event) { + // do nothing + } + + public void itemsRemoved(ListRemoveEvent event) { + // do nothing + } + + public void itemsReplaced(ListReplaceEvent event) { + // do nothing + } + + public void itemsMoved(ListMoveEvent event) { + // do nothing + } + + public void listCleared(ListClearEvent event) { + // do nothing + } + + public void listChanged(ListChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeListener.java new file mode 100644 index 0000000000..0680c551bb --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeListener.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; + +/** + * A "list change" event gets fired whenever a model changes a "bound" + * list. You can register a ListChangeListener with a source + * model so as to be notified of any bound list updates. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ListChangeListener extends EventListener { + + /** + * This method gets called when items are added to a bound list. + * + * @param event An event describing the event source, + * the list that changed, the items that were added, and the index + * at which the items were added. + */ + void itemsAdded(ListAddEvent event); + + /** + * This method gets called when items are removed from a bound list. + * + * @param event An event describing the event source, + * the list that changed, the items that were removed, and the index + * at which the items were removed. + */ + void itemsRemoved(ListRemoveEvent event); + + /** + * This method gets called when items in a bound list are replaced. + * + * @param event An event describing the event source, + * the list that changed, the items that were added, the items that were + * replaced, and the index at which the items were replaced. + */ + void itemsReplaced(ListReplaceEvent event); + + /** + * This method gets called when items in a bound list are moved. + * + * @param event An event describing the event source, + * the list that changed, and the indices of where items were moved + * from and to. + */ + void itemsMoved(ListMoveEvent event); + + /** + * This method gets called when a bound list is cleared. + * + * @param event A ListClearEvent object describing the event source + * and the list that changed. + */ + void listCleared(ListClearEvent event); + + /** + * This method gets called when a bound list is changed in a manner + * that is not easily characterized by the other methods in this interface. + * + * @param event A ListChangeEvent object describing the event source + * and the list that changed. + */ + void listChanged(ListChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/MultiMethodReflectiveChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/MultiMethodReflectiveChangeListener.java new file mode 100644 index 0000000000..45762bf06c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/MultiMethodReflectiveChangeListener.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.lang.reflect.Method; + +import org.eclipse.jpt.common.utility.internal.ReflectionTools; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * This class is used by {@link ReflectiveChangeListener} when the requested listener + * needs to implement multiple methods (i.e. {@link CollectionChangeListener}, + * {@link ListChangeListener}, or {@link TreeChangeListener}). + */ +class MultiMethodReflectiveChangeListener + extends ReflectiveChangeListener + implements CollectionChangeListener, ListChangeListener, TreeChangeListener +{ + /** the methods we will invoke on the target object */ + private final Method addMethod; + private final Method removeMethod; + private final Method replaceMethod; // this can be null + private final Method moveMethod; // this can be null + private final Method clearMethod; + private final Method changeMethod; + + + /** + * The "replace" and "move" methods are optional. + */ + MultiMethodReflectiveChangeListener(Object target, Method addMethod, Method removeMethod, Method replaceMethod, Method moveMethod, Method clearMethod, Method changeMethod) { + super(target); + this.addMethod = addMethod; + this.removeMethod = removeMethod; + this.replaceMethod = replaceMethod; + this.moveMethod = moveMethod; + this.clearMethod = clearMethod; + this.changeMethod = changeMethod; + } + + /** + * No "replace" or "move" methods. + */ + MultiMethodReflectiveChangeListener(Object target, Method addMethod, Method removeMethod, Method clearMethod, Method changeMethod) { + this(target, addMethod, removeMethod, null, null, clearMethod, changeMethod); + } + + + // ********** CollectionChangeListener implementation ********** + + private void invoke(Method method, CollectionEvent event) { + if (method.getParameterTypes().length == 0) { + ReflectionTools.executeMethod(method, this.target, EMPTY_OBJECT_ARRAY); + } else { + ReflectionTools.executeMethod(method, this.target, new CollectionEvent[] {event}); + } + } + + public void itemsAdded(CollectionAddEvent event) { + this.invoke(this.addMethod, event); + } + + public void itemsRemoved(CollectionRemoveEvent event) { + this.invoke(this.removeMethod, event); + } + + public void collectionCleared(CollectionClearEvent event) { + this.invoke(this.clearMethod, event); + } + + public void collectionChanged(CollectionChangeEvent event) { + this.invoke(this.changeMethod, event); + } + + + // ********** ListChangeListener implementation ********** + + private void invoke(Method method, ListEvent event) { + if (method.getParameterTypes().length == 0) { + ReflectionTools.executeMethod(method, this.target, EMPTY_OBJECT_ARRAY); + } else { + ReflectionTools.executeMethod(method, this.target, new ListEvent[] {event}); + } + } + + public void itemsAdded(ListAddEvent event) { + this.invoke(this.addMethod, event); + } + + public void itemsRemoved(ListRemoveEvent event) { + this.invoke(this.removeMethod, event); + } + + public void itemsReplaced(ListReplaceEvent event) { + this.invoke(this.replaceMethod, event); + } + + public void itemsMoved(ListMoveEvent event) { + this.invoke(this.moveMethod, event); + } + + public void listCleared(ListClearEvent event) { + this.invoke(this.clearMethod, event); + } + + public void listChanged(ListChangeEvent event) { + this.invoke(this.changeMethod, event); + } + + + // ********** TreeChangeListener implementation ********** + + private void invoke(Method method, TreeEvent event) { + if (method.getParameterTypes().length == 0) { + ReflectionTools.executeMethod(method, this.target, EMPTY_OBJECT_ARRAY); + } else { + ReflectionTools.executeMethod(method, this.target, new TreeEvent[] {event}); + } + } + + public void nodeAdded(TreeAddEvent event) { + this.invoke(this.addMethod, event); + } + + public void nodeRemoved(TreeRemoveEvent event) { + this.invoke(this.removeMethod, event); + } + + public void treeCleared(TreeClearEvent event) { + this.invoke(this.clearMethod, event); + } + + public void treeChanged(TreeChangeEvent event) { + this.invoke(this.changeMethod, event); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeAdapter.java new file mode 100644 index 0000000000..c492353133 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeAdapter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; + +/** + * Convenience implementation of {@link PropertyChangeListener}. + * This is probably of limited use, since there only a single method to implement; + * maybe as a null implementation. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class PropertyChangeAdapter implements PropertyChangeListener { + + public void propertyChanged(PropertyChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeListener.java new file mode 100644 index 0000000000..4bb488f14a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeListener.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; + +/** + * A "property change" event gets fired whenever a model changes a "bound" + * property. You can register a PropertyChangeListener with a source + * model so as to be notified of any bound property updates. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface PropertyChangeListener extends EventListener { + + /** + * This method gets called when a model has changed a bound property. + * + * @param event An event describing the event source + * and the property's old and new values. + */ + void propertyChanged(PropertyChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ReflectiveChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ReflectiveChangeListener.java new file mode 100644 index 0000000000..8a24cd5073 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ReflectiveChangeListener.java @@ -0,0 +1,377 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.lang.reflect.Method; + +import org.eclipse.jpt.common.utility.internal.ReflectionTools; +import org.eclipse.jpt.common.utility.model.event.ChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * This factory builds listeners that reflectively forward change events. + * If you are worried about having too many little classes that have to be + * loaded and maintained by the class loader, you can use one of these. + * Of course, this comes with the additional overhead of reflection.... + * Also note that the validity of the method name is not checked at compile + * time, but at runtime; although we do check the method as soon as the + * listener is instantiated. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class ReflectiveChangeListener { + + /** the target object on which we will invoke the method */ + protected final Object target; + + + protected static final Class STATE_CHANGE_EVENT_CLASS = StateChangeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] STATE_CHANGE_EVENT_CLASS_ARRAY = new Class[] {STATE_CHANGE_EVENT_CLASS}; + + + protected static final Class PROPERTY_CHANGE_EVENT_CLASS = PropertyChangeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] PROPERTY_CHANGE_EVENT_CLASS_ARRAY = new Class[] {PROPERTY_CHANGE_EVENT_CLASS}; + + + protected static final Class COLLECTION_EVENT_CLASS = CollectionEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] COLLECTION_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_EVENT_CLASS}; + + protected static final Class COLLECTION_ADD_EVENT_CLASS = CollectionAddEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] COLLECTION_ADD_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_ADD_EVENT_CLASS}; + + protected static final Class COLLECTION_REMOVE_EVENT_CLASS = CollectionRemoveEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] COLLECTION_REMOVE_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_REMOVE_EVENT_CLASS}; + + protected static final Class COLLECTION_CLEAR_EVENT_CLASS = CollectionClearEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] COLLECTION_CLEAR_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_CLEAR_EVENT_CLASS}; + + protected static final Class COLLECTION_CHANGE_EVENT_CLASS = CollectionChangeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] COLLECTION_CHANGE_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_CHANGE_EVENT_CLASS}; + + + protected static final Class LIST_EVENT_CLASS = ListEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_EVENT_CLASS_ARRAY = new Class[] {LIST_EVENT_CLASS}; + + protected static final Class LIST_ADD_EVENT_CLASS = ListAddEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_ADD_EVENT_CLASS_ARRAY = new Class[] {LIST_ADD_EVENT_CLASS}; + + protected static final Class LIST_REMOVE_EVENT_CLASS = ListRemoveEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_REMOVE_EVENT_CLASS_ARRAY = new Class[] {LIST_REMOVE_EVENT_CLASS}; + + protected static final Class LIST_REPLACE_EVENT_CLASS = ListReplaceEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_REPLACE_EVENT_CLASS_ARRAY = new Class[] {LIST_REPLACE_EVENT_CLASS}; + + protected static final Class LIST_MOVE_EVENT_CLASS = ListMoveEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_MOVE_EVENT_CLASS_ARRAY = new Class[] {LIST_MOVE_EVENT_CLASS}; + + protected static final Class LIST_CLEAR_EVENT_CLASS = ListClearEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_CLEAR_EVENT_CLASS_ARRAY = new Class[] {LIST_CLEAR_EVENT_CLASS}; + + protected static final Class LIST_CHANGE_EVENT_CLASS = ListChangeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] LIST_CHANGE_EVENT_CLASS_ARRAY = new Class[] {LIST_CHANGE_EVENT_CLASS}; + + + protected static final Class TREE_EVENT_CLASS = TreeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] TREE_EVENT_CLASS_ARRAY = new Class[] {TREE_EVENT_CLASS}; + + protected static final Class TREE_ADD_EVENT_CLASS = TreeAddEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] TREE_ADD_EVENT_CLASS_ARRAY = new Class[] {TREE_ADD_EVENT_CLASS}; + + protected static final Class TREE_REMOVE_EVENT_CLASS = TreeRemoveEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] TREE_REMOVE_EVENT_CLASS_ARRAY = new Class[] {TREE_REMOVE_EVENT_CLASS}; + + protected static final Class TREE_CLEAR_EVENT_CLASS = TreeClearEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] TREE_CLEAR_EVENT_CLASS_ARRAY = new Class[] {TREE_CLEAR_EVENT_CLASS}; + + protected static final Class TREE_CHANGE_EVENT_CLASS = TreeChangeEvent.class; + @SuppressWarnings("unchecked") + protected static final Class[] TREE_CHANGE_EVENT_CLASS_ARRAY = new Class[] {TREE_CHANGE_EVENT_CLASS}; + + protected static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + + // ********** helper methods ********** + + /** + * Find and return a method implemented by the target that can be invoked + * reflectively when a change event occurs. + */ + private static Method findChangeListenerMethod(Object target, String methodName, Class[] eventClassArray) { + try { + return ReflectionTools.getMethod(target, methodName, eventClassArray); + } catch (RuntimeException ex1) { + return ReflectionTools.getMethod(target, methodName); + } + } + + /** + * Check whether the specified method is suitable for being invoked when a + * change event has occurred. Throw an exception if it is not suitable. + */ + private static void checkChangeListenerMethod(Method method, Class eventClass) { + Class[] parmTypes = method.getParameterTypes(); + int parmTypesLength = parmTypes.length; + if (parmTypesLength == 0) { + return; + } + if ((parmTypesLength == 1) && parmTypes[0].isAssignableFrom(eventClass)) { + return; + } + throw new IllegalArgumentException(method.toString()); + } + + + // ********** factory methods: StateChangeListener ********** + + /** + * Construct a state change listener that will invoke the specified method + * on the specified target. + */ + public static StateChangeListener buildStateChangeListener(Object target, Method method) { + checkChangeListenerMethod(method, STATE_CHANGE_EVENT_CLASS); + return new SingleMethodReflectiveChangeListener(target, method); + } + + /** + * Construct a state change listener that will invoke the specified method + * on the specified target. If a single-argument method with the specified + * name and appropriate argument is found, it will be invoked; otherwise, + * a zero-argument method with the specified name will be invoked. + */ + public static StateChangeListener buildStateChangeListener(Object target, String methodName) { + return buildStateChangeListener(target, findChangeListenerMethod(target, methodName, STATE_CHANGE_EVENT_CLASS_ARRAY)); + } + + + // ********** factory methods: PropertyChangeListener ********** + + /** + * Construct a property change listener that will invoke the specified method + * on the specified target. + */ + public static PropertyChangeListener buildPropertyChangeListener(Object target, Method method) { + checkChangeListenerMethod(method, PROPERTY_CHANGE_EVENT_CLASS); + return new SingleMethodReflectiveChangeListener(target, method); + } + + /** + * Construct a property change listener that will invoke the specified method + * on the specified target. If a single-argument method with the specified + * name and appropriate argument is found, it will be invoked; otherwise, + * a zero-argument method with the specified name will be invoked. + */ + public static PropertyChangeListener buildPropertyChangeListener(Object target, String methodName) { + return buildPropertyChangeListener(target, findChangeListenerMethod(target, methodName, PROPERTY_CHANGE_EVENT_CLASS_ARRAY)); + } + + + // ********** factory methods: CollectionChangeListener ********** + + /** + * Construct a collection change listener that will invoke the specified methods + * on the specified target. + */ + public static CollectionChangeListener buildCollectionChangeListener(Object target, Method addMethod, Method removeMethod, Method clearMethod, Method changeMethod) { + checkChangeListenerMethod(addMethod, COLLECTION_ADD_EVENT_CLASS); + checkChangeListenerMethod(removeMethod, COLLECTION_REMOVE_EVENT_CLASS); + checkChangeListenerMethod(clearMethod, COLLECTION_CLEAR_EVENT_CLASS); + checkChangeListenerMethod(changeMethod, COLLECTION_CHANGE_EVENT_CLASS); + return new MultiMethodReflectiveChangeListener(target, addMethod, removeMethod, clearMethod, changeMethod); + } + + /** + * Construct a collection change listener that will invoke the specified method + * on the specified target for any change event. + */ + public static CollectionChangeListener buildCollectionChangeListener(Object target, Method method) { + return buildCollectionChangeListener(target, method, method, method, method); + } + + /** + * Construct a collection change listener that will invoke the specified methods + * on the specified target for change events. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static CollectionChangeListener buildCollectionChangeListener(Object target, String addMethodName, String removeMethodName, String clearMethodName, String changeMethodName) { + return buildCollectionChangeListener( + target, + findChangeListenerMethod(target, addMethodName, COLLECTION_ADD_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, removeMethodName, COLLECTION_REMOVE_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, clearMethodName, COLLECTION_CLEAR_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, changeMethodName, COLLECTION_CHANGE_EVENT_CLASS_ARRAY) + ); + } + + /** + * Construct a collection change listener that will invoke the specified method + * on the specified target for any change event. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static CollectionChangeListener buildCollectionChangeListener(Object target, String methodName) { + return buildCollectionChangeListener(target, findChangeListenerMethod(target, methodName, COLLECTION_EVENT_CLASS_ARRAY)); + } + + + // ********** factory methods: ListChangeListener ********** + + /** + * Construct a list change listener that will invoke the specified methods + * on the specified target. + */ + public static ListChangeListener buildListChangeListener(Object target, Method addMethod, Method removeMethod, Method replaceMethod, Method moveMethod, Method clearMethod, Method changeMethod) { + checkChangeListenerMethod(addMethod, LIST_ADD_EVENT_CLASS); + checkChangeListenerMethod(removeMethod, LIST_REMOVE_EVENT_CLASS); + checkChangeListenerMethod(replaceMethod, LIST_REPLACE_EVENT_CLASS); + checkChangeListenerMethod(moveMethod, LIST_MOVE_EVENT_CLASS); + checkChangeListenerMethod(clearMethod, LIST_CLEAR_EVENT_CLASS); + checkChangeListenerMethod(changeMethod, LIST_CHANGE_EVENT_CLASS); + return new MultiMethodReflectiveChangeListener(target, addMethod, removeMethod, replaceMethod, moveMethod, clearMethod, changeMethod); + } + + /** + * Construct a list change listener that will invoke the specified method + * on the specified target for any change event. + */ + public static ListChangeListener buildListChangeListener(Object target, Method method) { + return buildListChangeListener(target, method, method, method, method, method, method); + } + + /** + * Construct a list change listener that will invoke the specified methods + * on the specified target for change events. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static ListChangeListener buildListChangeListener(Object target, String addMethodName, String removeMethodName, String replaceMethodName, String moveMethodName, String clearMethodName, String changeMethodName) { + return buildListChangeListener( + target, + findChangeListenerMethod(target, addMethodName, LIST_ADD_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, removeMethodName, LIST_REMOVE_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, replaceMethodName, LIST_REPLACE_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, moveMethodName, LIST_MOVE_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, clearMethodName, LIST_CLEAR_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, changeMethodName, LIST_CHANGE_EVENT_CLASS_ARRAY) + ); + } + + /** + * Construct a list change listener that will invoke the specified method + * on the specified target for any change event. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static ListChangeListener buildListChangeListener(Object target, String methodName) { + return buildListChangeListener(target, findChangeListenerMethod(target, methodName, LIST_EVENT_CLASS_ARRAY)); + } + + + // ********** factory methods: TreeChangeListener ********** + + /** + * Construct a tree change listener that will invoke the specified methods + * on the specified target. + */ + public static TreeChangeListener buildTreeChangeListener(Object target, Method addMethod, Method removeMethod, Method clearMethod, Method changeMethod) { + checkChangeListenerMethod(addMethod, TREE_ADD_EVENT_CLASS); + checkChangeListenerMethod(removeMethod, TREE_REMOVE_EVENT_CLASS); + checkChangeListenerMethod(clearMethod, TREE_CLEAR_EVENT_CLASS); + checkChangeListenerMethod(changeMethod, TREE_CHANGE_EVENT_CLASS); + return new MultiMethodReflectiveChangeListener(target, addMethod, removeMethod, clearMethod, changeMethod); + } + + /** + * Construct a tree change listener that will invoke the specified method + * on the specified target for any change event. + */ + public static TreeChangeListener buildTreeChangeListener(Object target, Method method) { + return buildTreeChangeListener(target, method, method, method, method); + } + + /** + * Construct a tree change listener that will invoke the specified methods + * on the specified target for change events. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static TreeChangeListener buildTreeChangeListener(Object target, String addMethodName, String removeMethodName, String clearMethodName, String changeMethodName) { + return buildTreeChangeListener( + target, + findChangeListenerMethod(target, addMethodName, TREE_ADD_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, removeMethodName, TREE_REMOVE_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, clearMethodName, TREE_CLEAR_EVENT_CLASS_ARRAY), + findChangeListenerMethod(target, changeMethodName, TREE_CHANGE_EVENT_CLASS_ARRAY) + ); + } + + /** + * Construct a tree change listener that will invoke the specified method + * on the specified target for any change event. If a single-argument method + * with the specified name and appropriate argument is found, it will be invoked; + * otherwise, a zero-argument method with the specified name will be invoked. + */ + public static TreeChangeListener buildTreeChangeListener(Object target, String methodName) { + return buildTreeChangeListener(target, findChangeListenerMethod(target, methodName, TREE_EVENT_CLASS_ARRAY)); + } + + + // ********** constructor ********** + + /** + * Construct a listener that will invoke the specified method + * on the specified target. + */ + protected ReflectiveChangeListener(Object target) { + super(); + this.target = target; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SimpleChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SimpleChangeListener.java new file mode 100644 index 0000000000..cc82b775bf --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SimpleChangeListener.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.ChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.common.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListAddEvent; +import org.eclipse.jpt.common.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.common.utility.model.event.ListClearEvent; +import org.eclipse.jpt.common.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.common.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * Convenience abstract implementation of {@link ChangeListener}. + * All change notifications are funneled through a single method. + * This class can be used by + * subclassing it and overriding either {@link #modelChanged(ChangeEvent)} + * (if access to the event is required) or {@link #modelChanged()} (if access + * to the event is not required). + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class SimpleChangeListener + implements ChangeListener +{ + public SimpleChangeListener() { + super(); + } + + public void stateChanged(StateChangeEvent event) { + this.modelChanged(event); + } + + public void propertyChanged(PropertyChangeEvent event) { + this.modelChanged(event); + } + + public void collectionChanged(CollectionChangeEvent event) { + this.modelChanged(event); + } + + public void collectionCleared(CollectionClearEvent event) { + this.modelChanged(event); + } + + public void itemsAdded(CollectionAddEvent event) { + this.modelChanged(event); + } + + public void itemsRemoved(CollectionRemoveEvent event) { + this.modelChanged(event); + } + + public void itemsAdded(ListAddEvent event) { + this.modelChanged(event); + } + + public void itemsMoved(ListMoveEvent event) { + this.modelChanged(event); + } + + public void itemsRemoved(ListRemoveEvent event) { + this.modelChanged(event); + } + + public void itemsReplaced(ListReplaceEvent event) { + this.modelChanged(event); + } + + public void listChanged(ListChangeEvent event) { + this.modelChanged(event); + } + + public void listCleared(ListClearEvent event) { + this.modelChanged(event); + } + + public void nodeAdded(TreeAddEvent event) { + this.modelChanged(event); + } + + public void nodeRemoved(TreeRemoveEvent event) { + this.modelChanged(event); + } + + public void treeChanged(TreeChangeEvent event) { + this.modelChanged(event); + } + + public void treeCleared(TreeClearEvent event) { + this.modelChanged(event); + } + + /** + * The model has notified the listener of the change described by the + * specified change event. By default the listener executes {@link #modelChanged()}. + */ + protected void modelChanged(@SuppressWarnings("unused") ChangeEvent event) { + this.modelChanged(); + } + + /** + * The model has notified the listener of a change. + * By default the listener throws an exception. + */ + protected void modelChanged() { + throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SingleMethodReflectiveChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SingleMethodReflectiveChangeListener.java new file mode 100644 index 0000000000..146e75ffd3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SingleMethodReflectiveChangeListener.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.lang.reflect.Method; +import org.eclipse.jpt.common.utility.internal.ReflectionTools; +import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; + +/** + * This class is used by {@link ReflectiveChangeListener} when the requested listener + * need only implement a single method (i.e. {@link StateChangeListener} or + * {@link PropertyChangeListener}). + */ +class SingleMethodReflectiveChangeListener + extends ReflectiveChangeListener + implements StateChangeListener, PropertyChangeListener +{ + + /** the method we will invoke on the target object */ + private final Method method; + /** cache the number of arguments */ + private final boolean methodIsZeroArgument; + + SingleMethodReflectiveChangeListener(Object target, Method method) { + super(target); + this.method = method; + this.methodIsZeroArgument = method.getParameterTypes().length == 0; + } + + + // ********** StateChangeListener implementation ********** + + public void stateChanged(StateChangeEvent event) { + if (this.methodIsZeroArgument) { + ReflectionTools.executeMethod(this.method, this.target, EMPTY_OBJECT_ARRAY); + } else { + ReflectionTools.executeMethod(this.method, this.target, new StateChangeEvent[] {event}); + } + } + + + // ********** PropertyChangeListener implementation ********** + + public void propertyChanged(PropertyChangeEvent event) { + if (this.methodIsZeroArgument) { + ReflectionTools.executeMethod(this.method, this.target, EMPTY_OBJECT_ARRAY); + } else { + ReflectionTools.executeMethod(this.method, this.target, new PropertyChangeEvent[] {event}); + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeAdapter.java new file mode 100644 index 0000000000..3cc5f185f2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeAdapter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; + +/** + * Convenience implementation of {@link StateChangeListener}. + * This is probably of limited use, since there only a single method to implement; + * maybe as a null implementation. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class StateChangeAdapter implements StateChangeListener { + + public void stateChanged(StateChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeListener.java new file mode 100644 index 0000000000..f9c599edee --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeListener.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.event.StateChangeEvent; + +/** + * A generic "state change" event gets delivered whenever a model changes to + * such extent that it cannot be delineated all aspects of it that have changed. + * You can register a StateChangeListener with a source model so as to be notified + * of any such changes. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface StateChangeListener extends EventListener { + + /** + * This method gets called when a model has changed in some general fashion. + * + * @param event An event describing the event source. + */ + void stateChanged(StateChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeAdapter.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeAdapter.java new file mode 100644 index 0000000000..8d7ccee3d4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeAdapter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * Convenience implementation of {@link TreeChangeListener}. + *

+ * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public class TreeChangeAdapter implements TreeChangeListener { + + /** + * Default constructor. + */ + public TreeChangeAdapter() { + super(); + } + + public void nodeAdded(TreeAddEvent event) { + // do nothing + } + + public void nodeRemoved(TreeRemoveEvent event) { + // do nothing + } + + public void treeCleared(TreeClearEvent event) { + // do nothing + } + + public void treeChanged(TreeChangeEvent event) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeListener.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeListener.java new file mode 100644 index 0000000000..de8c898fb5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeListener.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.listener; + +import java.util.EventListener; + +import org.eclipse.jpt.common.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.common.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.common.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.common.utility.model.event.TreeRemoveEvent; + +/** + * A "tree change" event gets fired whenever a model changes a "bound" + * tree. You can register a TreeChangeListener with a source + * model so as to be notified of any bound tree updates. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface TreeChangeListener extends EventListener { + + /** + * This method gets called when a node is added to a bound tree. + * + * @param event An event describing the event source, + * the tree that changed, and the path to the node that was added. + */ + void nodeAdded(TreeAddEvent event); + + /** + * This method gets called when a node is removed from a bound tree. + * + * @param event An event describing the event source, + * the tree that changed, and the path to the node that was removed. + */ + void nodeRemoved(TreeRemoveEvent event); + + /** + * This method gets called when a bound tree is cleared. + * + * @param event An event describing the event source, + * the tree that changed, and an empty path. + */ + void treeCleared(TreeClearEvent event); + + /** + * This method gets called when a portion of a bound tree is changed in + * a manner that is not easily characterized by the other methods in this + * interface. + * + * @param event An event describing the event source, + * the tree that changed, and the current state of the + * tree that changed. + */ + void treeChanged(TreeChangeEvent event); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/CollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/CollectionValueModel.java new file mode 100644 index 0000000000..d7adf509aa --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/CollectionValueModel.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +import java.util.Iterator; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Interface used to abstract collection accessing and + * change notification and make it more pluggable. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the model + */ +public interface CollectionValueModel + extends Model, Iterable +{ + + /** + * Return the collection's values. + */ + Iterator iterator(); + String VALUES = "values"; //$NON-NLS-1$ + + /** + * Return the size of the collection. + */ + int size(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/ListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/ListValueModel.java new file mode 100644 index 0000000000..16d88283b8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/ListValueModel.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +import java.util.Iterator; +import java.util.ListIterator; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Interface used to abstract list accessing and + * change notification and make it more pluggable. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the list model + */ +public interface ListValueModel + extends Model, Iterable +{ + /** + * Return the list's values. + */ + Iterator iterator(); + String LIST_VALUES = "list values"; //$NON-NLS-1$ + + /** + * Return the list's values. + */ + ListIterator listIterator(); + + /** + * Return the size of the list. + */ + int size(); + + /** + * Return the item at the specified index of the list. + */ + E get(int index); + + /** + * Return the list's values. + */ + Object[] toArray(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/PropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/PropertyValueModel.java new file mode 100644 index 0000000000..180be43f1d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/PropertyValueModel.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Interface used to abstract property accessing and + * change notification and make it more pluggable. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of value held by the model + */ +public interface PropertyValueModel + extends Model +{ + + /** + * Return the property's value. + */ + T getValue(); + String VALUE = "value"; //$NON-NLS-1$ + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeNodeValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeNodeValueModel.java new file mode 100644 index 0000000000..f9281b00e9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeNodeValueModel.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +/** + * Extend {@link WritablePropertyValueModel} to better support + * {@link org.eclipse.jpt.common.utility.internal.model.value.swing.TreeModelAdapter}. + *

+ * Implementors of this interface should fire a "state change" event + * whenever the node's internal state changes in a way that the + * tree listeners should be notified. + *

+ * Implementors of this interface should also fire a "value property change" + * event whenever the node's value changes. Typically, only nodes that + * hold "primitive" data will fire this event. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the model + * + * @see org.eclipse.jpt.common.utility.internal.model.value.AbstractTreeNodeValueModel + */ +public interface TreeNodeValueModel + extends WritablePropertyValueModel +{ + + /** + * Return the node's parent node; null if the node + * is the root. + */ + TreeNodeValueModel parent(); + + /** + * Return the path to the node. + */ + TreeNodeValueModel[] path(); + + /** + * Return a list value model of the node's child nodes. + */ + ListValueModel> childrenModel(); + + /** + * Return the node's child at the specified index. + */ + TreeNodeValueModel child(int index); + + /** + * Return the size of the node's list of children. + */ + int childrenSize(); + + /** + * Return the index in the node's list of children of the specified child. + */ + int indexOfChild(TreeNodeValueModel child); + + /** + * Return whether the node is a leaf (i.e. it has no children) + */ + boolean isLeaf(); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeValueModel.java new file mode 100644 index 0000000000..7a3e44d765 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeValueModel.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +import java.util.Iterator; +import org.eclipse.jpt.common.utility.model.Model; + +/** + * Interface used to abstract tree accessing and + * change notification and make it more pluggable. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the model + */ +public interface TreeValueModel + extends Model +{ + /** + * Return the tree's nodes. + */ + Iterator nodes(); + String NODES = "nodes"; //$NON-NLS-1$ + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableCollectionValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableCollectionValueModel.java new file mode 100644 index 0000000000..6fe5fa7bc1 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableCollectionValueModel.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +/** + * Extend {@link CollectionValueModel} to allow the setting of the + * collection's values. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the model + */ +public interface WritableCollectionValueModel + extends CollectionValueModel +{ + + /** + * Set the values and fire a collection change notification. + * @see CollectionValueModel#VALUES + */ + void setValues(Iterable values); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableListValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableListValueModel.java new file mode 100644 index 0000000000..df8e5366b9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableListValueModel.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +/** + * Extend {@link ListValueModel} to allow the setting of the + * lists's values. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of values held by the model + */ +public interface WritableListValueModel + extends ListValueModel +{ + + /** + * Set the list values and fire a list change notification. + * @see ListValueModel#LIST_VALUES + */ + void setListValues(Iterable values); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritablePropertyValueModel.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritablePropertyValueModel.java new file mode 100644 index 0000000000..fe35c351f2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritablePropertyValueModel.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.model.value; + +/** + * Extend {@link PropertyValueModel} to allow the setting of the property's value. + *

+ * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + * + * @param the type of value held by the model + */ +public interface WritablePropertyValueModel + extends PropertyValueModel +{ + + /** + * Set the value and fire a property change notification. + * @see PropertyValueModel#VALUE + */ + void setValue(T value); + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/CallbackSynchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/CallbackSynchronizer.java new file mode 100644 index 0000000000..029227a8c7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/CallbackSynchronizer.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.synchronizers; + +import java.io.Serializable; +import java.util.EventListener; + +/** + * Extend {@link Synchronizer} to notify listeners + * when a synchronization "cycle" is complete; i.e. the synchronization has, + * for the moment, quiesced. + */ +public interface CallbackSynchronizer + extends Synchronizer +{ + /** + * Add the specified listener to be notified whenever the synchronizer has + * quiesced. + * @see #removeListener(Listener) + */ + void addListener(Listener listener); + + /** + * Remove the specified listener. + * @see #addListener(Listener) + */ + void removeListener(Listener listener); + + + // ********** listener ********** + + /** + * Interface implemented by listeners to be notified whenever the + * synchronizer has quiesced. + */ + public interface Listener + extends EventListener + { + /** + * The specified synchronizer has quiesced. + */ + void synchronizationQuiesced(CallbackSynchronizer synchronizer); + } + + + /** + * Singleton implementation of the {@link CallbackSynchronizer} interface that will do + * nothing. + */ + final class Null implements CallbackSynchronizer, Serializable { + public static final CallbackSynchronizer INSTANCE = new Null(); + public static CallbackSynchronizer instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void start() { + // do nothing + } + public void synchronize() { + // do nothing + } + public void stop() { + // do nothing + } + public void addListener(Listener listener) { + // do nothing + } + public void removeListener(Listener listener) { + // do nothing + } + @Override + public String toString() { + return "CallbackSynchronizer.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/Synchronizer.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/Synchronizer.java new file mode 100644 index 0000000000..230fb86eb3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/Synchronizer.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.synchronizers; + +import java.io.Serializable; + +/** + * This interface defines the protocol for starting, stopping, and executing a + * long-running, repeatable, and possibly recursive "synchronization" process. + * The intent is for the synchronizer to synchronize a "secondary" model with + * a "primary" model. Any change to the "primary" model will trigger the + * synchronization. The synchronizer implementation will determine whether the + * "secondary" model remains in sync synchronously or asynchronously. + *

+ * The assumption is that the {@link #start()} and {@link #stop()} methods will be called from + * a single master thread that would control the synchronizer's lifecycle and + * the {@link #synchronize()} method will be called multiple times, possibly from + * multiple threads. + */ +public interface Synchronizer { + + /** + * Enable the synchronizer to allow future synchronizations as requested + * by calls to {@link #synchronize()}. + */ + void start(); + + /** + * Synchronize the dependent model with the primary model. Do nothing if + * {@link #start()} has not previously been called. Do nothing if {@link #stop} + * has been called (without any intermediate call to {@link #start()}. + */ + void synchronize(); + + /** + * Stop the synchronizer immediately or, if a synchronization is currently + * in progress, when it completes. Return when the synchronizer is stopped. + * No further synchonizations will performed until {@link #start()} is called. + */ + void stop(); + + + /** + * Singleton implementation of the {@link Synchronizer} interface that will do + * nothing. + */ + final class Null implements Synchronizer, Serializable { + public static final Synchronizer INSTANCE = new Null(); + public static Synchronizer instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void start() { + // do nothing + } + public void synchronize() { + // do nothing + } + public void stop() { + // do nothing + } + @Override + public String toString() { + return "Synchronizer.Null"; //$NON-NLS-1$ + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} -- cgit v1.2.3