Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkmoore2011-02-05 16:22:02 +0000
committerkmoore2011-02-05 16:22:02 +0000
commit824998d08071af6d9877fb5314aa25d360d1c172 (patch)
tree80d029dbe3dc505c45ea0413e5218fda4b7be5e1 /common/plugins/org.eclipse.jpt.common.utility/src
parentc73d0c7b4a6a3150dd604d6c747069408ace5719 (diff)
downloadwebtools.dali-824998d08071af6d9877fb5314aa25d360d1c172.tar.gz
webtools.dali-824998d08071af6d9877fb5314aa25d360d1c172.tar.xz
webtools.dali-824998d08071af6d9877fb5314aa25d360d1c172.zip
rename org.eclipse.jpt.utility to org.eclipse.jpt.common.utility and move to common component
Diffstat (limited to 'common/plugins/org.eclipse.jpt.common.utility/src')
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Command.java87
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/CommandExecutor.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/Filter.java125
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/IndentingPrintWriter.java155
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/JavaType.java135
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/MethodSignature.java73
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ObjectReference.java29
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/ReadOnlyObjectReference.java43
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AbstractAssociation.java69
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ArrayTools.java3122
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Association.java46
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/AsynchronousCommandExecutor.java168
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Bag.java197
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiFilter.java122
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiStringConverter.java149
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BidiTransformer.java93
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BitTools.java214
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanReference.java48
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/BooleanTools.java105
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ClassName.java431
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Classpath.java939
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CollectionTools.java1957
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CommandRunnable.java37
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeCommand.java44
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/CompositeException.java96
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ConsumerThreadCoordinator.java253
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ExceptionHandler.java87
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FileTools.java1002
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/FlaggedObjectReference.java69
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/HashBag.java877
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IdentityHashBag.java924
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/IntReference.java40
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCTools.java349
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/JDBCType.java162
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/KeyedSet.java129
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/LazyReadOnlyObjectReference.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ListenerList.java171
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NameTools.java376
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NonNullBooleanTransformer.java79
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotBooleanTransformer.java56
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NotNullFilter.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/NullList.java153
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Queue.java75
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Range.java87
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyBooleanReference.java46
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReadOnlyIntReference.java145
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReflectionTools.java1544
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ReverseComparator.java40
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/RunnableCommand.java37
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleAssociation.java69
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleBooleanReference.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleCommandExecutor.java46
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleFilter.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleIntReference.java186
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleJavaType.java213
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleMethodSignature.java240
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleObjectReference.java98
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleQueue.java90
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStack.java100
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleStringMatcher.java259
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SimpleThreadFactory.java53
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Stack.java75
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StatefulCommandExecutor.java33
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringConverter.java80
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringMatcher.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/StringTools.java4708
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBag.java220
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedBoolean.java437
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedInt.java914
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedObject.java472
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedQueue.java348
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/SynchronizedStack.java325
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommand.java63
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/ThreadLocalCommandExecutor.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Tools.java89
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/Transformer.java95
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/XMLStringEncoder.java182
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/EmptyEnumeration.java62
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/enumerations/IteratorEnumeration.java57
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayIterable.java77
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ArrayListIterable.java59
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ChainIterable.java96
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneIterable.java66
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CloneListIterable.java92
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeIterable.java98
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/CompositeListIterable.java135
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyIterable.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/EmptyListIterable.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/FilteringIterable.java95
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/GraphIterable.java156
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListIterable.java27
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ListListIterable.java35
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneIterable.java85
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/LiveCloneListIterable.java85
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/PeekableIterable.java56
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/QueueIterable.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyCompositeListIterable.java100
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyIterable.java50
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/ReadOnlyListIterable.java50
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementIterable.java55
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SingleElementListIterable.java58
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneIterable.java124
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SnapshotCloneListIterable.java102
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/StackIterable.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubIterableWrapper.java47
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SubListIterableWrapper.java52
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperIterableWrapper.java48
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/SuperListIterableWrapper.java54
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationIterable.java91
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TransformationListIterable.java111
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterables/TreeIterable.java137
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayIterator.java88
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ArrayListIterator.java93
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ChainIterator.java159
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneIterator.java191
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CloneListIterator.java291
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeIterator.java162
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/CompositeListIterator.java270
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyIterator.java69
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EmptyListIterator.java93
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/EnumerationIterator.java52
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/FilteringIterator.java148
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/GraphIterator.java283
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/PeekableIterator.java112
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/QueueIterator.java59
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyCompositeListIterator.java252
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyIterator.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReadOnlyListIterator.java108
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ResultSetIterator.java162
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/ReverseIterator.java82
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementIterator.java67
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SingleElementListIterator.java98
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/StackIterator.java59
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubIteratorWrapper.java59
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SubListIteratorWrapper.java90
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperIteratorWrapper.java58
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SuperListIteratorWrapper.java88
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedIterator.java76
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/SynchronizedListIterator.java122
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationIterator.java103
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TransformationListIterator.java152
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/iterators/TreeIterator.java254
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AbstractModel.java1007
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/AspectChangeSupport.java349
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/ChangeSupport.java2844
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/SingleAspectChangeSupport.java380
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTChangeListenerWrapper.java454
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTCollectionChangeListenerWrapper.java161
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTListChangeListenerWrapper.java211
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTPropertyChangeListenerWrapper.java87
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTStateChangeListenerWrapper.java86
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/listener/awt/AWTTreeChangeListenerWrapper.java161
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractCollectionValueModel.java124
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractListValueModel.java124
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModel.java124
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractPropertyValueModelAdapter.java117
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AbstractTreeNodeValueModel.java194
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectAdapter.java266
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectCollectionValueModelAdapter.java155
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectListValueModelAdapter.java197
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectPropertyValueModelAdapter.java178
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/AspectTreeValueModelAdapter.java119
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/BufferedWritablePropertyValueModel.java392
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationPropertyValueModel.java112
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CachingTransformationWritablePropertyValueModel.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ChangePropertyValueModelAdapter.java99
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionAspectAdapter.java158
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionListValueModelAdapter.java217
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionPropertyValueModelAdapter.java139
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CollectionValueModelWrapper.java132
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeBooleanPropertyValueModel.java338
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeCollectionValueModel.java448
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositeListValueModel.java683
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/CompositePropertyValueModel.java198
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ExtendedListValueModelWrapper.java211
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringCollectionValueModel.java179
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringPropertyValueModel.java142
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/FilteringWritablePropertyValueModel.java123
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemAspectListValueModelAdapter.java274
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemChangeListValueModelAdapter.java68
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemCollectionListValueModelAdapter.java101
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemListListValueModelAdapter.java109
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemPropertyListValueModelAdapter.java84
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemStateListValueModelAdapter.java74
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ItemTreeListValueModelAdapter.java101
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListAspectAdapter.java176
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCollectionValueModelAdapter.java233
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListCurator.java226
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListPropertyValueModelAdapter.java167
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ListValueModelWrapper.java164
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullCollectionValueModel.java58
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullListValueModel.java71
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullPropertyValueModel.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/NullTreeValueModel.java52
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyAspectAdapter.java128
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyCollectionValueModelAdapter.java141
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyListValueModelAdapter.java157
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/PropertyValueModelWrapper.java92
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ReadOnlyWritablePropertyValueModelWrapper.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SetCollectionValueModel.java134
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleCollectionValueModel.java188
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimpleListValueModel.java322
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SimplePropertyValueModel.java66
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelAdapter.java125
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/SortedListValueModelWrapper.java250
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StatePropertyValueModelAdapter.java96
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticCollectionValueModel.java73
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticListValueModel.java93
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticPropertyValueModel.java53
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/StaticTreeValueModel.java57
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationListValueModel.java309
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationPropertyValueModel.java144
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TransformationWritablePropertyValueModel.java131
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreeAspectAdapter.java155
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/TreePropertyValueModelAdapter.java144
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueAspectAdapter.java201
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueChangeAdapter.java75
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueCollectionAdapter.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueListAdapter.java123
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValuePropertyAdapter.java82
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueStateAdapter.java73
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/ValueTreeAdapter.java107
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyCollectionValueModelAdapter.java62
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/WritablePropertyListValueModelAdapter.java62
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencePropertyValueModel.java346
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/prefs/PreferencesCollectionValueModel.java203
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/AbstractTreeModel.java216
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/CheckBoxModelAdapter.java43
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ColumnAdapter.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ComboBoxModelAdapter.java140
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DateSpinnerModelAdapter.java198
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/DocumentAdapter.java375
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListModelAdapter.java292
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ListSpinnerModelAdapter.java218
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/NumberSpinnerModelAdapter.java223
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ObjectListSelectionModel.java427
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/PrimitiveListTreeModel.java239
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/RadioButtonModelAdapter.java151
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/SpinnerModelAdapter.java207
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TableModelAdapter.java420
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/ToggleButtonModelAdapter.java224
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/model/value/swing/TreeModelAdapter.java914
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AbstractNode.java941
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/AsynchronousValidator.java50
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/DefaultProblem.java85
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Node.java377
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/PluggableValidator.java121
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/Problem.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/node/SynchronousValidator.java44
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CachingComboBoxModel.java42
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/CheckBoxTableCellRenderer.java206
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ComboBoxTableCellRenderer.java328
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/Displayable.java44
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/EmptyIcon.java54
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListBrowser.java140
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/FilteringListPanel.java455
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/ListChooser.java430
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NodeSelector.java32
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/NonCachingComboBoxModel.java73
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleDisplayable.java170
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListBrowser.java86
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SimpleListCellRenderer.java128
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/SpinnerTableCellRenderer.java186
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/swing/TableCellEditorAdapter.java96
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/AsynchronousSynchronizer.java188
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java120
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/CallbackSynchronousSynchronizer.java83
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/synchronizers/SynchronousSynchronizer.java263
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/Model.java143
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ChangeEvent.java66
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionAddEvent.java124
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionChangeEvent.java105
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionClearEvent.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionEvent.java63
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/CollectionRemoveEvent.java112
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListAddEvent.java134
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListChangeEvent.java105
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListClearEvent.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListEvent.java64
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListMoveEvent.java120
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListRemoveEvent.java134
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/ListReplaceEvent.java150
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/PropertyChangeEvent.java109
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/StateChangeEvent.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeAddEvent.java81
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeChangeEvent.java90
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeClearEvent.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeEvent.java62
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/event/TreeRemoveEvent.java81
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeAdapter.java109
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ChangeListener.java25
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeAdapter.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CollectionChangeListener.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/CommandChangeListener.java44
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeAdapter.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ListChangeListener.java87
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/MultiMethodReflectiveChangeListener.java160
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeAdapter.java31
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/PropertyChangeListener.java37
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/ReflectiveChangeListener.java377
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SimpleChangeListener.java131
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/SingleMethodReflectiveChangeListener.java60
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeAdapter.java31
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/StateChangeListener.java37
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeAdapter.java51
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/listener/TreeChangeListener.java67
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/CollectionValueModel.java42
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/ListValueModel.java57
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/PropertyValueModel.java36
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeNodeValueModel.java74
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/TreeValueModel.java36
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableCollectionValueModel.java34
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritableListValueModel.java34
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/model/value/WritablePropertyValueModel.java33
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/CallbackSynchronizer.java92
-rw-r--r--common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/synchronizers/Synchronizer.java83
316 files changed, 62122 insertions, 0 deletions
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}.
+ * <p>
+ * 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).
+ * <p>
+ * 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.
+ * <p>
+ * 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 <T> the type of objects to be filtered
+ */
+public interface Filter<T> {
+
+ /**
+ * 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<S> implements Filter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Filter INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R> Filter<R> 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<S> implements Filter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Filter INSTANCE = new Opaque();
+ @SuppressWarnings("unchecked")
+ public static <R> Filter<R> 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<S> implements Filter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Filter INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> Filter<R> 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>'$'</code> 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. <code>int</code>, <code>float</code>).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ boolean isPrimitive();
+
+ /**
+ * Return whether the type is a "primitive wrapper" (e.g. {@link java.lang.Integer},
+ * {@link java.lang.Float}).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ boolean isPrimitiveWrapper();
+
+ /**
+ * Return whether the type is a "variable primitive" (e.g. <code>int</code>, <code>float</code>,
+ * but not <code>void</code>).
+ * <p>
+ * <strong>NB:</strong> variables cannot be declared <code>void</code>
+ */
+ 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}).
+ * <p>
+ * <strong>NB:</strong> variables cannot be declared <code>void</code>
+ */
+ 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. <code>"[[J"</code>, <code>"[Ljava.lang.Object;"</code>,
+ * <code>"java.util.Map$Entry"</code>).
+ */
+ 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:<ul>
+ * <li><code>"[[J"</code> => <code>"long[][]"</code>
+ * <li><code>"java.util.Map$Entry"</code> => <code>"java.util.Map.Entry"</code>
+ * </ul>
+ */
+ String declaration();
+
+ /**
+ * Append the version of the type's name that can be used in source code:<ul>
+ * <li><code>"[[J"</code> => <code>"long[][]"</code>
+ * <li><code>"java.util.Map$Entry"</code> => <code>"java.util.Map.Entry"</code>
+ * </ul>
+ */
+ void appendDeclarationTo(StringBuilder sb);
+
+ /**
+ * Print the version of the type's name that can be used in source code:<ul>
+ * <li><code>"[[J"</code> => <code>"long[][]"</code>
+ * <li><code>"java.util.Map$Entry"</code> => <code>"java.util.Map.Entry"</code>
+ * </ul>
+ */
+ 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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:<p>
+ * <code>"foo(int, java.lang.String)"</code>
+ */
+ String getSignature();
+
+ /**
+ * Append a string representation of the method's signature:<p>
+ * <code>"foo(int, java.lang.String)"</code>
+ */
+ void appendSignatureTo(StringBuilder sb);
+
+ /**
+ * Print a string representation of the method's signature:<p>
+ * <code>"foo(int, java.lang.String)"</code>
+ */
+ 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<V>
+ extends ReadOnlyObjectReference<V>
+{
+ /**
+ * Set the value.
+ * Return the previous value.
+ */
+ V setValue(V value);
+
+ /**
+ * Set the value to <code>null</code>.
+ * 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<V>
+{
+ /**
+ * 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 <code>null</code>.
+ */
+ boolean isNull();
+
+ /**
+ * Return whether the current value is not <code>null</code>.
+ */
+ 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<K, V>
+ implements Association<K, V>
+{
+ /**
+ * 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.
+ * <p>
+ * <code>Arrays.newArray(Object[] array)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.newArray(Object[] array, int length)</code>
+ */
+ public static <E> 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 <E> Class<? extends E> componentType(E[] array) {
+ Class<?> rawComponentType = array.getClass().getComponentType();
+ @SuppressWarnings("unchecked")
+ Class<? extends E> componentType = (Class<? extends E>) 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> E[] newArray(Class<? extends E> 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> E[] newArray_(Class<? extends E> componentType, int length) {
+ return (E[]) ((componentType == OBJECT_CLASS) ?
+ new Object[length] :
+ Array.newInstance(componentType, length));
+ }
+ private static final Class<Object> OBJECT_CLASS = Object.class;
+
+
+ // ********** conversion **********
+
+ /**
+ * Return an array corresponding to the specified iterable.
+ * <p>
+ * <code>Iterable.toArray()</code>
+ * @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.
+ * <p>
+ * <code>Iterable.toArray()</code>
+ * @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.
+ * <p>
+ * <code>Iterable.toArray(Object[])</code>
+ * @see Collection#toArray(Object[])
+ */
+ public static <E> E[] array(Iterable<? extends E> 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.
+ * <p>
+ * <code>Iterable.toArray(Object[])</code>
+ * @see Collection#toArray(Object[])
+ */
+ public static <E> E[] array(Iterable<? extends E> iterable, int iterableSize, E[] array) {
+ return array(iterable.iterator(), iterableSize, array);
+ }
+
+ /**
+ * Return an array corresponding to the specified iterator.
+ * <p>
+ * <code>Iterator.toArray()</code>
+ * @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.
+ * <p>
+ * <code>Iterator.toArray()</code>
+ * @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.
+ * <p>
+ * <code>Iterator.toArray(Object[])</code>
+ * @see Collection#toArray(Object[])
+ */
+ public static <E> E[] array(Iterator<? extends E> 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.
+ * <p>
+ * <code>Iterator.toArray(Object[])</code>
+ * @see Collection#toArray(Object[])
+ */
+ public static <E> E[] array(Iterator<? extends E> 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> 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> 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.
+ * <p>
+ * <code>Arrays.add(Object[] array, Object o)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.add(Object[] array, int index, Object o)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.add(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.add(char[] array, int index, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.add(int[] array, int value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.add(int[] array, int index, int value)</code>
+ */
+ 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.
+ *<p>
+ * <code>Arrays.addAll(Object[] array, Collection collection)</code>
+ */
+ public static <E> E[] addAll(E[] array, Collection<? extends E> collection) {
+ return addAll(array, collection, collection.size());
+ }
+
+ /**
+ * check collection size
+ */
+ private static <E> E[] addAll(E[] array, Collection<? extends E> collection, int collectionSize) {
+ return (collectionSize == 0) ? array : addAll_(array, collection, collectionSize);
+ }
+
+ /**
+ * assume the collection is non-empty
+ */
+ private static <E> E[] addAll_(E[] array, Collection<? extends E> collection) {
+ return addAll_(array, collection, collection.size());
+ }
+
+ /**
+ * assume collection size > zero
+ */
+ private static <E> E[] addAll_(E[] array, Collection<? extends E> collection, int collectionSize) {
+ return addAll(array, collection, array.length, collectionSize);
+ }
+
+ /**
+ * assume collection size > zero; check array length
+ */
+ private static <E> E[] addAll(E[] array, Collection<? extends E> 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> E[] addAll_(E[] array, Collection<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> E[] addAll(E[] array, Iterable<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> E[] addAll(E[] array, Iterable<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> E[] addAll(E[] array, Iterator<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> E[] addAll(E[] array, Iterator<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array1, Object[] array2)</code>
+ */
+ public static <E> E[] addAll(E[] array1, E... array2) {
+ return addAll(array1, array2, array2.length);
+ }
+
+ /**
+ * check array 2 length
+ */
+ private static <E> E[] addAll(E[] array1, E[] array2, int array2Length) {
+ return (array2Length == 0) ? array1 : addAll_(array1, array2, array2Length);
+ }
+
+ /**
+ * assume array 2 length > 0
+ */
+ private static <E> 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> 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> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array1, int index, Object[] array2)</code>
+ */
+ public static <E> E[] addAll(E[] array1, int index, E... array2) {
+ return addAll(array1, index, array2, array2.length);
+ }
+
+ /**
+ * check array 2 length
+ */
+ private static <E> 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> 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> 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> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, int index, Collection c)</code>
+ */
+ public static <E> E[] addAll(E[] array, int index, Collection<? extends E> collection) {
+ return addAll(array, index, collection, collection.size());
+ }
+
+ /**
+ * check collection size
+ */
+ private static <E> E[] addAll(E[] array, int index, Collection<? extends E> collection, int collectionSize) {
+ return (collectionSize == 0) ? array : addAll_(array, index, collection, collectionSize);
+ }
+
+ /**
+ * assume collection size > 0
+ */
+ private static <E> E[] addAll_(E[] array, int index, Collection<? extends E> collection, int collectionSize) {
+ return addAll(array, index, collection, array.length, collectionSize);
+ }
+
+ /**
+ * assume collection size > 0; check array length
+ */
+ private static <E> E[] addAll(E[] array, int index, Collection<? extends E> 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> E[] addAll_(E[] array, int index, Collection<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, int index, Iterable iterable)</code>
+ */
+ public static <E> E[] addAll(E[] array, int index, Iterable<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, int index, Iterable iterable)</code>
+ */
+ public static <E> E[] addAll(E[] array, int index, Iterable<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, int index, Iterator iterator)</code>
+ */
+ public static <E> E[] addAll(E[] array, int index, Iterator<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(Object[] array, int index, Iterator iterator)</code>
+ */
+ public static <E> E[] addAll(E[] array, int index, Iterator<? extends E> iterator, int iteratorSize) {
+ return iterator.hasNext() ? addAll_(array, index, CollectionTools.list(iterator, iteratorSize)) : array;
+ }
+
+ /**
+ * assume collection is non-empty
+ */
+ private static <E> E[] addAll_(E[] array, int index, Collection<? extends E> 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.
+ * <p>
+ * <code>Arrays.addAll(char[] array1, char[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.add(char[] array1, int index, char[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.addAll(int[] array1, int[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.add(int[] array1, int index, int[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.clear(Object[] array)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.concatenate(Object[]... arrays)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.concatenate(char[]... arrays)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.concatenate(int[]... arrays)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.contains(Object[] array, Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.contains(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.contains(int[] array, int value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.containsAll(Object[] array, Collection collection)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.containsAll(Object[] array, Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.containsAll(Object[] array, Iterator iterator)</code>
+ */
+ public static boolean containsAll(Object[] array, Iterator<?> iterator) {
+ // use hashed lookup
+ HashSet<Object> 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.
+ * <p>
+ * <code>Arrays.containsAll(Object[] array1, Object[] array2)</code>
+ */
+ public static boolean containsAll(Object[] array1, Object... array2) {
+ // use hashed lookup
+ HashSet<Object> 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.
+ * <p>
+ * <code>Arrays.containsAll(char[] array1, char[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.containsAll(int[] array1, int[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.identical(Object[] array1, Object[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.indexOf(Object[] array, Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.identityIndexOf(Object[] array, Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.indexOf(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.indexOf(int[] array, int value)</code>
+ */
+ 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 <E extends Comparable<? super E>> int insertionIndexOf(E[] sortedArray, Comparable<E> 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 <E> int insertionIndexOf(E[] sortedArray, E value, Comparator<? super E> 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.
+ * <p>
+ * <code>Arrays.lastIndexOf(Object[] array, Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.lastIndexOf(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.lastIndexOf(int[] array, int value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.min(char[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.min(int[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.max(char[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.max(int[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.move(Object[] array, int targetIndex, int sourceIndex)</code>
+ */
+ public static <E> E[] move(E[] array, int targetIndex, int sourceIndex) {
+ return (targetIndex == sourceIndex) ? array : move_(array, targetIndex, sourceIndex);
+ }
+
+ /**
+ * assume target index != source index
+ */
+ private static <E> 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.
+ * <p>
+ * <code>Arrays.move(Object[] array, int targetIndex, int sourceIndex, int length)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.move(int[] array, int targetIndex, int sourceIndex)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.move(int[] array, int targetIndex, int sourceIndex, int length)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.move(char[] array, int targetIndex, int sourceIndex)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.move(char[] array, int targetIndex, int sourceIndex, int length)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.remove(Object[] array, Object value)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.remove(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.remove(int[] array, int value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeFirst(Object[] array)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeFirst(char[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeFirst(int[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeLast(Object[] array)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeLast(char[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeLast(int[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array, Collection collection)</code>
+ */
+ public static <E> E[] removeAll(E[] array, Collection<?> collection) {
+ return collection.isEmpty() ? array : removeAll_(array, collection);
+ }
+
+ /**
+ * assume collection is non-empty
+ */
+ private static <E> E[] removeAll_(E[] array, Collection<?> collection) {
+ return removeAll(array, collection, array.length);
+ }
+
+ /**
+ * assume collection is non-empty; check array length
+ */
+ private static <E> 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> 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.
+ * <p>
+ * <code>Arrays.removeAll(Object[] array1, Object[] array2)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays#removeAll(char[] array1, char[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays#removeAll(int[] array1, int[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeAllOccurrences(Object[] array, Object value)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeAllOccurrences(char[] array, char value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeAllOccurrences(int[] array, int value)</code>
+ */
+ 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> E[] removeDuplicateElements(E... array) {
+ int len = array.length;
+ if ((len == 0) || (len == 1)) {
+ return array;
+ }
+ ArrayList<E> 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.
+ * <p>
+ * <code>Arrays.removeElementAtIndex(Object[] array, int index)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.removeElementAtIndex(char[] array, int index)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeElementAtIndex(int[] array, int index)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.removeElementsAtIndex(Object[] array, int index, int length)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>Arrays.replaceAll(Object[] array, Object oldValue, Object newValue)</code>
+ */
+ public static <E> 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.
+ *<p>
+ * <code> Arrays.replaceAll(int[] array, int oldValue, int newValue)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.replaceAll(char[] array, char oldValue, char newValue)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array, Iterable iterable)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array, Iterator iterator)</code>
+ */
+ public static <E> 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> 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> 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array, Collection collection)</code>
+ */
+ public static <E> 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> 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> 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.
+ * <p>
+ * <code>Arrays.retainAll(Object[] array1, Object[] array2)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.retainAll(char[] array1, char[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.retainAll(int[] array1, int[] array2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.reverse(Object... array)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.reverse(char... array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.reverse(int... array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.rotate(Object[] array)</code>
+ */
+ public static <E> E[] rotate(E... array) {
+ return rotate(array, 1);
+ }
+
+ /**
+ * Return the rotated array after rotating it the specified distance.
+ * <p>
+ * <code>Arrays.rotate(Object[] array, int distance)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.rotate(char[] array)</code>
+ */
+ public static char[] rotate(char... array) {
+ return rotate(array, 1);
+ }
+
+ /**
+ * Return the rotated array after rotating it the specified distance.
+ * <p>
+ * <code>Arrays.rotate(char[] array, int distance)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.rotate(int[] array)</code>
+ */
+ public static int[] rotate(int... array) {
+ return rotate(array, 1);
+ }
+
+ /**
+ * Return the rotated array after rotating it the specified distance.
+ * <p>
+ * <code>Arrays.rotate(int[] array, int distance)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.shuffle(Object... array)</code>
+ */
+ public static <E> E[] shuffle(E... array) {
+ return shuffle(array, RANDOM);
+ }
+
+ /**
+ * Return the array after "shuffling" it.
+ * <p>
+ * <code>Arrays.shuffle(Object[] array, Random r)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.shuffle(char... array)</code>
+ */
+ public static char[] shuffle(char... array) {
+ return shuffle(array, RANDOM);
+ }
+
+ /**
+ * Return the array after "shuffling" it.
+ * <p>
+ * <code>Arrays.shuffle(char[] array, Random r)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.shuffle(int... array)</code>
+ */
+ public static int[] shuffle(int... array) {
+ return shuffle(array, RANDOM);
+ }
+
+ /**
+ * Return the array after "shuffling" it.
+ * <p>
+ * <code>Arrays.shuffle(int[] array, Random r)</code>
+ */
+ 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.
+ * <p>
+ * <code>Arrays.subArray(E[] array, int fromIndex, int toIndex)</code>
+ */
+ public static <E> 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.
+ * <p>
+ * <code>Arrays.subArray(int[] array, int fromIndex, int toIndex)</code>
+ */
+ 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.
+ * <p>
+ * <code>
+ * Arrays.subArray(char[] array, int fromIndex, int toIndex)</code>
+ * </code>
+ */
+ 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".
+ * <p>
+ * <code>Arrays.swap(Object[] array, int i, int j)</code>
+ */
+ public static <E> E[] swap(E[] array, int i, int j) {
+ return (i == j) ? array : swap_(array, i, j);
+ }
+
+ /**
+ * assume the indices are different
+ */
+ private static <E> 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".
+ * <p>
+ * <code>Arrays.swap(char[] array, int i, int j)</code>
+ */
+ 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".
+ * <p>
+ * <code>Arrays.swap(int[] array, int i, int j)</code>
+ */
+ 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> 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> 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> 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> E[] sort(E[] array, Comparator<? super E> comparator) {
+ Arrays.sort(array, comparator);
+ return array;
+ }
+
+ /**
+ * Return the array after it has been "sorted".
+ * @see Arrays#sort(Object[], int, int)
+ */
+ public static <E> 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> E[] sort(E[] array, int fromIndex, int toIndex, Comparator<? super E> 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<K, V> {
+
+ /**
+ * 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.
+ * <p>
+ * <strong>NB:</strong> 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<Command> commands = new SynchronizedQueue<Command>();
+
+ /**
+ * 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.
+ * <p>
+ * Note: We don't clear the command queue here; so if a command has been
+ * added to the queue <em>before</em> getting here, the first command will
+ * be executed promptly (albeit, asynchronously).
+ * The command queue will be non-empty if:<ul>
+ * <li>{@link #execute(Command)} was called after the command executor was
+ * constructed but before {@link #start()} was called; or
+ * <li>{@link #execute(Command)} was called after {@link #stop()} was called
+ * but before {@link #start()} was called (to restart the command executor); or
+ * <li>{@link #stop()} was called when there were still outstanding commands
+ * remaining in the command queue
+ * </ul>
+ */
+ 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.
+ * <p>
+ * The <code>Bag</code> 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<E> extends java.util.Collection<E> {
+
+ /**
+ * Compares the specified object with this bag for equality. Returns
+ * <code>true</code> 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 <code>null</code> element is defined to be zero.
+ * This ensures that <code>b1.equals(b2)</code> implies that
+ * <code>b1.hashCode() == b2.hashCode()</code> for any two bags
+ * <code>b1</code> and <code>b2</code>, 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<E> 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<Entry<E>> entries();
+
+
+ /**
+ * A bag entry (element-count pair).
+ * The {@link Bag#entries()} method returns an iterator whose
+ * elements are of this class. The <em>only</em> way to obtain a reference
+ * to a bag entry is from the iterator returned by this method. These
+ * <code>Bag.Entry</code> objects are valid <em>only</em> 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<E> {
+
+ /**
+ * 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 <code>Bag.Entry</code> 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<E> extends AbstractCollection<E> implements Bag<E>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Bag INSTANCE = new Empty();
+ @SuppressWarnings("unchecked")
+ public static <T> Bag<T> instance() {
+ return INSTANCE;
+ }
+ // ensure single instance
+ private Empty() {
+ super();
+ }
+ @Override
+ public Iterator<E> iterator() {
+ return EmptyIterator.instance();
+ }
+ @Override
+ public int size() {
+ return 0;
+ }
+ public Iterator<E> uniqueIterator() {
+ return EmptyIterator.instance();
+ }
+ public int uniqueCount() {
+ return 0;
+ }
+ public int count(Object o) {
+ return 0;
+ }
+ public Iterator<Bag.Entry<E>> 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<T> extends Filter<T> {
+
+ /**
+ * 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<S> implements BidiFilter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiFilter INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R> BidiFilter<R> 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<S> implements BidiFilter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiFilter INSTANCE = new Opaque();
+ @SuppressWarnings("unchecked")
+ public static <R> BidiFilter<R> 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<S> implements BidiFilter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiFilter INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> BidiFilter<R> 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<T> extends StringConverter<T> {
+
+ /**
+ * 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<S> implements BidiStringConverter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiStringConverter INSTANCE = new Default();
+ @SuppressWarnings("unchecked")
+ public static <R> BidiStringConverter<R> 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<S> implements BidiStringConverter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiStringConverter INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> BidiStringConverter<R> 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<Boolean>, Serializable {
+ public static final BidiStringConverter<Boolean> INSTANCE = new BooleanConverter();
+ public static BidiStringConverter<Boolean> 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<Integer>, Serializable {
+ public static final BidiStringConverter<Integer> INSTANCE = new IntegerConverter();
+ public static BidiStringConverter<Integer> 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<T1, T2> extends Transformer<T1, T2> {
+
+ /**
+ * 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<S1, S2> implements BidiTransformer<S1, S2>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiTransformer INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R1, R2> BidiTransformer<R1, R2> 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<S1, S2> implements BidiTransformer<S1, S2>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final BidiTransformer INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R1, R2> BidiTransformer<R1, R2> 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 <code>boolean</code> value.
+ * Return the previous value.
+ */
+ boolean setValue(boolean value);
+
+ /**
+ * Set the <code>boolean</code> value to the NOT of its current value.
+ * Return the new value.
+ */
+ boolean flip();
+
+ /**
+ * Set the <code>boolean</code> value to the NOT of the specified value.
+ * Return the previous value.
+ */
+ boolean setNot(boolean v);
+
+ /**
+ * Set the <code>boolean</code> value to <code>true</code>.
+ * Return the previous value.
+ */
+ boolean setTrue();
+
+ /**
+ * Set the <code>boolean</code> value to <code>false</code>.
+ * 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 <code>null</code> 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.
+ * <p>
+ * The simple name of an array type is the simple name of the
+ * component type with <code>"[]"</code> appended. In particular,
+ * the simple name of an array type whose component type is
+ * anonymous is simply <code>"[]"</code>.
+ * @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.
+ * <code>"java.lang.Object"</code> returns
+ * <code>"java.lang"</code>).
+ * Return an empty string if the specified class is:<ul>
+ * <li>in the "default" package
+ * <li>an array class
+ * <li>a primtive class
+ * </ul>
+ * @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 <code>'$'</code>).
+ * 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 <code>'$'</code> followed by a legal class name; e.g.
+ * <code>"TopLevelClass$1LocalClass$MemberClass"</code>).
+ * 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 <code>"$nnnXXX"</code>,
+ * where the <code>'$'</code> is
+ * followed by a series of numeric digits which are followed by the
+ * local class name; e.g. <code>"TopLevelClass$1LocalClass"</code>).
+ * 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 <code>"$nnn"</code> where all the characters past the
+ * last <code>'$'</code> are ASCII numeric digits;
+ * e.g. <code>"TopLevelClass$1"</code>).
+ * 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 <code>true</code> 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 <code>void</code> nor one of the primitive variable classes,
+ * <code>boolean</code>, <code>int</code>, <code>float</code>, etc.).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ public static boolean isReference(String className) {
+ return ! isPrimitive(className);
+ }
+
+ /**
+ * Return whether the specified class is a primitive
+ * class (i.e. <code>void</code> or one of the primitive variable classes,
+ * <code>boolean</code>, <code>int</code>, <code>float</code>, etc.).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ 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. <code>java.lang.Void</code> or one of the primitive
+ * variable wrapper classes, <code>java.lang.Boolean</code>,
+ * <code>java.lang.Integer</code>, <code>java.lang.Float</code>, etc.).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ 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. <code>boolean</code>, <code>int</code>, <code>float</code>, etc.,
+ * but not <code>void</code>).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ 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. <code>java.lang.Boolean</code>,
+ * <code>java.lang.Integer</code>, <code>java.lang.Float</code>, etc.,
+ * but not <code>java.lang.Void</code>).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ 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 <code>null</code> 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 <code>null</code> 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 <code>null</code> 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 <code>null</code> 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 <code>0</code> 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;
+
+/**
+ * <code>Classpath</code> 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 <code>Classpath</code>s
+ * 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 <code>rt.jar</code>.
+ */
+ 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<String> jarFileNames = new ArrayList<String>();
+ 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:<ul>
+ * <li><code>"java/lang/String.class"</code> is converted to <code>"java.lang.String"</code>
+ * <li><code>"java/lang/String.java"</code> is converted to <code>"java.lang.String"</code>
+ * </ul>
+ */
+ 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. <code>File(java/lang/String.class)</code> is converted to
+ * <code>"java.lang.String"</code>.
+ */
+ public static String convertToClassName(File classFile) {
+ return convertToClassName(classFile.getPath());
+ }
+
+ /**
+ * Convert a relative file name to a class;
+ * e.g. <code>"java/lang/String.class"</code> is converted to
+ * <code>java.lang.String.class</code>.
+ */
+ public static Class<?> convertToClass(String classFileName) throws ClassNotFoundException {
+ return Class.forName(convertToClassName(classFileName));
+ }
+
+ /**
+ * Convert a relative file to a class;
+ * e.g. <code>File(java/lang/String.class)</code> is converted to
+ * <code>java.lang.String.class</code>.
+ */
+ 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. <code>"java.lang.String"</code> is converted to
+ * <code>"java/lang/String"</code>.
+ */
+ public static String convertToArchiveEntryNameBase(String className) {
+ return className.replace('.', '/');
+ }
+
+ /**
+ * Convert a class to an archive entry name base;
+ * e.g. <code>java.lang.String.class</code> is converted to
+ * <code>"java/lang/String"</code>.
+ */
+ public static String convertToArchiveEntryNameBase(Class<?> javaClass) {
+ return convertToArchiveEntryNameBase(javaClass.getName());
+ }
+
+ /**
+ * Convert a class name to an archive class file entry name;
+ * e.g. <code>"java.lang.String"</code> is converted to
+ * <code>"java/lang/String.class"</code>.
+ */
+ 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. <code>java.lang.String.class</code> is converted to
+ * <code>"java/lang/String.class"</code>.
+ */
+ 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. <code>"java.lang.String"</code> is converted to
+ * <code>"java/lang/String"</code> on Unix and
+ * <code>"java\\lang\\String"</code> 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. <code>java.lang.String.class</code> is converted to
+ * <code>"java/lang/String"</code> on Unix and
+ * <code>"java\\lang\\String"</code> 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. <code>"java.lang.String"</code> is converted to
+ * <code>"java/lang/String.class"</code> on Unix and
+ * <code>"java\\lang\\String.class"</code> 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. <code>java.lang.String.class</code> is converted to
+ * <code>"java/lang/String.class"</code> on Unix and
+ * <code>"java\\lang\\String.class"</code> 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. <code>"java.lang.String"</code> is converted to
+ * <code>File(java/lang/String.class)</code>.
+ */
+ 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. <code>java.lang.String.class</code> is converted to
+ * <code>File(java/lang/String.class)</code>.
+ */
+ 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. <code>"java.lang.String"</code> is converted to
+ * <code>"java/lang/String.java"</code> on Unixl and
+ * <code>"java\\lang\\String.java"</code> 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. <code>java.lang.String.class</code> is converted to
+ * <code>"java/lang/String.java"</code> on Unix and
+ * <code>"java\\lang\\String.java"</code> 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. <code>"java.lang.String"</code> is converted to
+ * <code>File(java/lang/String.java)</code>.
+ */
+ 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. <code>java.lang.String.class</code> is converted to
+ * <code>File(java/lang/String.java)</code>.
+ */
+ public static File convertToJavaFile(Class<?> javaClass) {
+ return convertToJavaFile(javaClass.getName());
+ }
+
+
+ // ***** class => resource *****
+
+ /**
+ * Convert a class to a resource name;
+ * e.g. <code>java.lang.String.class</code> is converted to
+ * <code>"/java/lang/String.class"</code>.
+ */
+ public static String convertToResourceName(Class<?> javaClass) {
+ return '/' + convertToArchiveClassFileEntryName(javaClass);
+ }
+
+ /**
+ * Convert a class to a resource;
+ * e.g. <code>java.lang.String.class</code> is converted to
+ * <code>URL(jar:file:/C:/jdk/1.4.2_04/jre/lib/rt.jar!/java/lang/String.class)</code>.
+ */
+ 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 <code>".zip"</code> or <code>".jar"</code>.
+ */
+ 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 <code>".zip"</code> or <code>".jar"</code>.
+ */
+ 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. <code>"C:\jdk1.4.2_04\jre\lib\rt.jar"</code>.
+ */
+ 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<String> 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:<pre>
+ * "C:\dev\foo.jar;;C:\dev\bar.jar"
+ * </pre>will be parsed into three file names:<pre>
+ * { "C:\dev\foo.jar", "", "C:\dev\bar.jar" }
+ * </pre>
+ */
+ private static Entry[] buildEntries(String[] fileNames) {
+ List<Entry> entries = new ArrayList<Entry>();
+ 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<String> 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<Entry> entries = new ArrayList<Entry>();
+ 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<Entry> getEntries() {
+ return new ArrayIterable<Entry>(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 <code>"rt.jar"</code> or <code>"toplink.jar"</code>.
+ */
+ 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<String> getClassNames() {
+ return this.getClassNames(Filter.Null.<String>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<String> getClassNames(Filter<String> filter) {
+ Collection<String> classNames = new HashSet<String>(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<String> classNames) {
+ this.addClassNamesTo(classNames, Filter.Null.<String>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<String> classNames, Filter<String> 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<String> classNames() {
+ return this.classNames(Filter.Null.<String>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<String> classNames(Filter<String> filter) {
+ return new CompositeIterator<String>(this.entryClassNamesIterators(filter));
+ }
+
+ private Iterator<Iterator<String>> entryClassNamesIterators(final Filter<String> filter) {
+ return new TransformationIterator<Entry, Iterator<String>>(new ArrayIterator<Entry>(this.entries)) {
+ @Override
+ protected Iterator<String> 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<URL> 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<URL>(urls);
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.getPath());
+ }
+
+
+ // ********** inner class **********
+
+ /**
+ * <code>Entry</code> models a Java classpath entry, which can be either a
+ * directory containing <code>.class</code> files or a JAR file (or,
+ * similarly, a <code>.zip</code> 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<String> getClassNames() {
+ return this.getClassNames(Filter.Null.<String>instance());
+ }
+
+ /**
+ * Return the names of all the classes discovered in the entry
+ * and accepted by the specified filter.
+ * @see #classNames(Filter)
+ */
+ public Iterable<String> getClassNames(Filter<String> filter) {
+ Collection<String> classNames = new ArrayList<String>(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<String> classNames) {
+ this.addClassNamesTo(classNames, Filter.Null.<String>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<String> classNames, Filter<String> 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<String> classNames, Filter<String> filter) {
+ int start = this.canonicalFile.getAbsolutePath().length() + 1;
+ for (Iterator<File> 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<File> classFilesForDirectory() {
+ return new FilteringIterator<File>(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<String> classNames, Filter<String> filter) {
+ ZipFile zipFile = null;
+ try {
+ zipFile = new ZipFile(this.canonicalFile);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ for (Enumeration<? extends ZipEntry> 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 <code>".class"</code> 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<String> classNames() {
+ return this.classNames(Filter.Null.<String>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<String> classNames(Filter<String> 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<String> classNamesForDirectory(Filter<String> filter) {
+ return new FilteringIterator<String>(this.classNamesForDirectory(), filter);
+ }
+
+ /**
+ * Transform the class files to class names.
+ */
+ private Iterator<String> classNamesForDirectory() {
+ final int start = this.canonicalFile.getAbsolutePath().length() + 1;
+ return new TransformationIterator<File, String>(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<String> classNamesForArchive(Filter<String> 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<String> classNames = new HashSet<String>(zipFile.size());
+ for (Enumeration<? extends ZipEntry> 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.
+ * <p>
+ * <code>Collection.addAll(Iterable iterable)</code>
+ */
+ public static <E> boolean addAll(Collection<? super E> collection, Iterable<? extends E> 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.
+ * <p>
+ * <code>Collection.addAll(Iterable iterable)</code>
+ */
+ public static <E> boolean addAll(Collection<? super E> collection, Iterable<? extends E> 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.
+ * <p>
+ * <code>Collection.addAll(Iterator iterator)</code>
+ */
+ public static <E> boolean addAll(Collection<? super E> collection, Iterator<? extends E> iterator) {
+ return iterator.hasNext() ? addAll_(collection, iterator) : false;
+ }
+
+ /**
+ * assume the iterator is not empty
+ */
+ private static <E> boolean addAll_(Collection<? super E> collection, Iterator<? extends E> 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.
+ * <p>
+ * <code>Collection.addAll(Iterator iterator)</code>
+ */
+ public static <E> boolean addAll(Collection<? super E> collection, Iterator<? extends E> 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.
+ * <p>
+ * <code>Collection.addAll(Object[] array)</code>
+ */
+ public static <E> boolean addAll(Collection<? super E> collection, E... array) {
+ return (array.length == 0) ? false : addAll_(collection, array);
+ }
+
+ /**
+ * assume the array is not empty
+ */
+ private static <E> boolean addAll_(Collection<? super E> 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.
+ * <p>
+ * <code>List.addAll(Iterable iterable)</code>
+ */
+ public static <E> boolean addAll(List<? super E> list, int index, Iterable<E> 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.
+ * <p>
+ * <code>List.addAll(Iterable iterable)</code>
+ */
+ public static <E> boolean addAll(List<? super E> list, int index, Iterable<E> 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.
+ * <p>
+ * <code>List.addAll(Iterator iterator)</code>
+ */
+ public static <E> boolean addAll(List<? super E> list, int index, Iterator<? extends E> 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.
+ * <p>
+ * <code>List.addAll(Iterator iterator)</code>
+ */
+ public static <E> boolean addAll(List<? super E> list, int index, Iterator<? extends E> 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.
+ * <p>
+ * <code>List.addAll(Object[] array)</code>
+ */
+ public static <E> boolean addAll(List<? super E> 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.
+ * <p>
+ * <code>HashBag(Enumeration enumeration)</code>
+ */
+ public static <E> HashBag<E> bag(Enumeration<? extends E> enumeration) {
+ return bag(enumeration, new HashBag<E>());
+ }
+
+ /**
+ * Return a bag corresponding to the specified enumeration.
+ * The specified enumeration size is a performance hint.
+ * <p>
+ * <code>HashBag(Enumeration enumeration)</code>
+ */
+ public static <E> HashBag<E> bag(Enumeration<? extends E> enumeration, int enumerationSize) {
+ return bag(enumeration, new HashBag<E>(enumerationSize));
+ }
+
+ private static <E> HashBag<E> bag(Enumeration<? extends E> enumeration, HashBag<E> bag) {
+ while (enumeration.hasMoreElements()) {
+ bag.add(enumeration.nextElement());
+ }
+ return bag;
+ }
+
+ /**
+ * Return a bag corresponding to the specified iterable.
+ * <p>
+ * <code>HashBag(Iterable iterable)</code>
+ */
+ public static <E> HashBag<E> bag(Iterable<? extends E> iterable) {
+ return bag(iterable.iterator());
+ }
+
+ /**
+ * Return a bag corresponding to the specified iterable.
+ * The specified iterable size is a performance hint.
+ * <p>
+ * <code>HashBag(Iterable iterable)</code>
+ */
+ public static <E> HashBag<E> bag(Iterable<? extends E> iterable, int iterableSize) {
+ return bag(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a bag corresponding to the specified iterator.
+ * <p>
+ * <code>HashBag(Iterator iterator)</code>
+ */
+ public static <E> HashBag<E> bag(Iterator<? extends E> iterator) {
+ return bag(iterator, new HashBag<E>());
+ }
+
+ /**
+ * Return a bag corresponding to the specified iterator.
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>HashBag(Iterator iterator)</code>
+ */
+ public static <E> HashBag<E> bag(Iterator<? extends E> iterator, int iteratorSize) {
+ return bag(iterator, new HashBag<E>(iteratorSize));
+ }
+
+ private static <E> HashBag<E> bag(Iterator<? extends E> iterator, HashBag<E> bag) {
+ while (iterator.hasNext()) {
+ bag.add(iterator.next());
+ }
+ return bag;
+ }
+
+ /**
+ * Return a bag corresponding to the specified array.
+ * <p>
+ * <code>HashBag(Object[] array)</code>
+ */
+ public static <E> HashBag<E> bag(E... array) {
+ int len = array.length;
+ HashBag<E> bag = new HashBag<E>(len);
+ for (E item : array) {
+ bag.add(item);
+ }
+ return bag;
+ }
+
+
+ // ********** collection **********
+
+ /**
+ * Return a collection corresponding to the specified enumeration.
+ */
+ public static <E> HashBag<E> collection(Enumeration<? extends E> enumeration) {
+ return bag(enumeration);
+ }
+
+ /**
+ * Return a collection corresponding to the specified enumeration.
+ * The specified enumeration size is a performance hint.
+ */
+ public static <E> HashBag<E> collection(Enumeration<? extends E> enumeration, int enumerationSize) {
+ return bag(enumeration, enumerationSize);
+ }
+
+ /**
+ * Return a collection corresponding to the specified iterable.
+ */
+ public static <E> HashBag<E> collection(Iterable<? extends E> iterable) {
+ return collection(iterable.iterator());
+ }
+
+ /**
+ * Return a collection corresponding to the specified iterable.
+ * The specified iterable size is a performance hint.
+ */
+ public static <E> HashBag<E> collection(Iterable<? extends E> iterable, int iterableSize) {
+ return collection(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a collection corresponding to the specified iterator.
+ */
+ public static <E> HashBag<E> collection(Iterator<? extends E> iterator) {
+ return bag(iterator);
+ }
+
+ /**
+ * Return a collection corresponding to the specified iterator.
+ * The specified iterator size is a performance hint.
+ */
+ public static <E> HashBag<E> collection(Iterator<? extends E> iterator, int iteratorSize) {
+ return bag(iterator, iteratorSize);
+ }
+
+ /**
+ * Return a collection corresponding to the specified array.
+ */
+ public static <E> HashBag<E> collection(E... array) {
+ return bag(array);
+ }
+
+
+ // ********** contains **********
+
+ /**
+ * Return whether the specified enumeration contains the
+ * specified element.
+ * <p>
+ * <code>Enumeration.contains(Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.contains(Object o)</code>
+ */
+ public static boolean contains(Iterable<?> iterable, Object value) {
+ return contains(iterable.iterator(), value);
+ }
+
+ /**
+ * Return whether the specified iterator contains the
+ * specified element.
+ * <p>
+ * <code>Iterator.contains(Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.containsAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.containsAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.containsAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Collection collection)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Collection collection)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.containsAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Collection collection)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Collection collection)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.containsAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collections.diffEnd(List list1, List list2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collections.diffRange(List list1, List list2)</code>
+ * @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.
+ * <p>
+ * <code>Collections.diffStart(List list1, List list2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collections.identityDiffEnd(List list1, List list2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collections.identityDiffStart(List list1, List list2)</code>
+ * @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.
+ * <p>
+ * <code>Collections.identityDiffStart(List list1, List list2)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.elementsAreEqual(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.elementsAreEqual(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.identical(Iterable iterable)</code>
+ */
+ public static boolean elementsAreIdentical(Iterable<?> iterable1, Iterable<?> iterable2) {
+ return elementsAreIdentical(iterable1.iterator(), iterable2.iterator());
+ }
+
+ /**
+ * Return whether the specified iterators return the same elements.
+ * <p>
+ * <code>Iterator.identical(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.get(int index)</code>
+ */
+ public static <E> E get(Iterable<? extends E> iterable, int index) {
+ return get(iterable.iterator(), index);
+ }
+
+ /**
+ * Return the element corresponding to the specified index
+ * in the specified iterator.
+ * <p>
+ * <code>Iterator.get(int index)</code>
+ */
+ public static <E> E get(Iterator<? extends E> 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.
+ * <p>
+ * <code>Iterable.indexOf(Object o)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterator.indexOf(Object o)</code>
+ */
+ 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 <em>maximum</em> insertion index;
+ * otherwise return the <em>minimum</em> insertion index.
+ */
+ public static <E extends Comparable<? super E>> int insertionIndexOf(List<E> sortedList, Comparable<E> 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 <em>maximum</em> insertion index;
+ * otherwise return the <em>minimum</em> insertion index.
+ */
+ public static <E> int insertionIndexOf(List<E> sortedList, E value, Comparator<? super E> 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.
+ * <p>
+ * <code>Arrays.iterable(Object[] array)</code>
+ */
+ public static <E> Iterable<E> iterable(E... array) {
+ return new ArrayIterable<E>(array);
+ }
+
+ /**
+ * Return an iterator on the elements in the specified array.
+ * <p>
+ * <code>Arrays.iterator(Object[] array)</code>
+ */
+ public static <E> Iterator<E> iterator(E... array) {
+ return new ArrayIterator<E>(array);
+ }
+
+
+ // ********** last **********
+
+ /**
+ * Return the specified iterable's last element.
+ * <p>
+ * <code>Iterable.last()</code>
+ *
+ * @exception java.util.NoSuchElementException iterable is empty.
+ */
+ public static <E> E last(Iterable<E> iterable) {
+ return last(iterable.iterator());
+ }
+
+ /**
+ * Return the specified iterator's last element.
+ * <p>
+ * <code>Iterator.last()</code>
+ *
+ * @exception java.util.NoSuchElementException iterator is empty.
+ */
+ public static <E> E last(Iterator<E> 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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>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.
+ * <p>
+ * <code>Iterable.toList()</code>
+ */
+ public static <E> ArrayList<E> list(Iterable<? extends E> iterable) {
+ return list(iterable.iterator());
+ }
+
+ /**
+ * Return a list corresponding to the specified iterable.
+ * The specified iterable size is a performance hint.
+ * <p>
+ * <code>Iterable.toList()</code>
+ */
+ public static <E> ArrayList<E> list(Iterable<? extends E> iterable, int iterableSize) {
+ return list(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a list corresponding to the specified iterator.
+ * <p>
+ * <code>Iterator.toList()</code>
+ */
+ public static <E> ArrayList<E> list(Iterator<? extends E> iterator) {
+ return list(iterator, new ArrayList<E>());
+ }
+
+ /**
+ * Return a list corresponding to the specified iterator.
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>Iterator.toList()</code>
+ */
+ public static <E> ArrayList<E> list(Iterator<? extends E> iterator, int iteratorSize) {
+ return list(iterator, new ArrayList<E>(iteratorSize));
+ }
+
+ private static <E> ArrayList<E> list(Iterator<? extends E> iterator, ArrayList<E> 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 <E> ArrayList<E> list(E... array) {
+ return new ArrayList<E>(Arrays.asList(array));
+ }
+
+ /**
+ * Return a list iterator for the specified array.
+ * <p>
+ * <code>Arrays.listIterator(Object[] array)</code>
+ */
+ public static <E> ListIterator<E> listIterator(E... array) {
+ return listIterator(array, 0);
+ }
+
+ /**
+ * Return a list iterator for the specified array
+ * starting at the specified position in the array.
+ * <p>
+ * <code>Arrays.listIterator(Object[] array, int index)</code>
+ */
+ public static <E> ListIterator<E> 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.
+ * <p>
+ * <code>Arrays.listIterator(Object[] array, int index, int length)</code>
+ */
+ public static <E> ListIterator<E> listIterator(E[] array, int start, int length) {
+ return new ArrayListIterator<E>(array, start, length);
+ }
+
+
+ // ********** move **********
+
+ /**
+ * Move an element from the specified source index to the specified target
+ * index. Return the altered list.
+ * <p>
+ * <code>List.move(int targetIndex, int sourceIndex)</code>
+ */
+ public static <E> List<E> move(List<E> list, int targetIndex, int sourceIndex) {
+ return (targetIndex == sourceIndex) ? list : move_(list, targetIndex, sourceIndex);
+ }
+
+ /**
+ * assume targetIndex != sourceIndex
+ */
+ private static <E> List<E> move_(List<E> 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.
+ * <p>
+ * <code>List.move(int targetIndex, int sourceIndex, int length)</code>
+ */
+ public static <E> List<E> move(List<E> 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<E> temp = new ArrayList<E>(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.
+ * <p>
+ * <code>Collection.removeAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.removeAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.removeAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.removeAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.removeAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.removeAllOccurrences(Object value)</code>
+ */
+ 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.
+ * <p>
+ * <code>List.remove(int index, int length)</code>
+ */
+ public static <E> ArrayList<E> removeElementsAtIndex(List<E> list, int index, int length) {
+ List<E> subList = list.subList(index, index + length);
+ ArrayList<E> result = new ArrayList<E>(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 <E> boolean removeDuplicateElements(List<E> list) {
+ int size = list.size();
+ if ((size == 0) || (size == 1)) {
+ return false;
+ }
+ return removeDuplicateElements(list, size);
+ }
+
+ /**
+ * assume list is non-empty
+ */
+ static <E> boolean removeDuplicateElements(List<E> list, int size) {
+ LinkedHashSet<E> temp = new LinkedHashSet<E>(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.
+ * <p>
+ * <code>Collection.retainAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.retainAll(Iterable iterable)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.retainAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.retainAll(Iterator iterator)</code>
+ */
+ 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.
+ * <p>
+ * <code>Collection.retainAll(Object[] array)</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.reverseList()</code>
+ */
+ public static <E> ArrayList<E> reverseList(Iterable<? extends E> 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.
+ * <p>
+ * <code>Iterable.reverseList()</code>
+ */
+ public static <E> ArrayList<E> reverseList(Iterable<? extends E> iterable, int iterableSize) {
+ return reverseList(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a list with entries in reverse order from those
+ * returned by the specified iterator.
+ * <p>
+ * <code>Iterator.reverseList()</code>
+ */
+ public static <E> ArrayList<E> reverseList(Iterator<? extends E> iterator) {
+ return (ArrayList<E>) 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.
+ * <p>
+ * <code>Iterator.reverseList()</code>
+ */
+ public static <E> ArrayList<E> reverseList(Iterator<? extends E> iterator, int size) {
+ return (ArrayList<E>) reverse(list(iterator, size));
+ }
+
+
+ // ********** rotate **********
+
+ /**
+ * Return the list after it has been "rotated" by one position.
+ * <p>
+ * <code>List.rotate()</code>
+ */
+ public static <E> List<E> rotate(List<E> list) {
+ return rotate(list, 1);
+ }
+
+
+ // ********** set **********
+
+ /**
+ * Return a set corresponding to the specified iterable.
+ * <p>
+ * <code>HashSet(Iterable iterable)</code>
+ */
+ public static <E> HashSet<E> set(Iterable<? extends E> iterable) {
+ return set(iterable.iterator());
+ }
+
+ /**
+ * Return a set corresponding to the specified iterable.
+ * The specified iterable size is a performance hint.
+ * <p>
+ * <code>HashSet(Iterable iterable)</code>
+ */
+ public static <E> HashSet<E> set(Iterable<? extends E> iterable, int iterableSize) {
+ return set(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a set corresponding to the specified iterator.
+ * <p>
+ * <code>HashSet(Iterator iterator)</code>
+ */
+ public static <E> HashSet<E> set(Iterator<? extends E> iterator) {
+ return set(iterator, new HashSet<E>());
+ }
+
+ /**
+ * Return a set corresponding to the specified iterator.
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>HashSet(Iterator iterator)</code>
+ */
+ public static <E> HashSet<E> set(Iterator<? extends E> iterator, int iteratorSize) {
+ return set(iterator, new HashSet<E>(iteratorSize));
+ }
+
+ private static <E> HashSet<E> set(Iterator<? extends E> iterator, HashSet<E> set) {
+ while (iterator.hasNext()) {
+ set.add(iterator.next());
+ }
+ return set;
+ }
+
+ /**
+ * Return a set corresponding to the specified array.
+ * <p>
+ * <code>HashSet(Object[] array)</code>
+ */
+ public static <E> HashSet<E> set(E... array) {
+ HashSet<E> set = new HashSet<E>(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.
+ * <p>
+ * <code>Object.toIterator()</code>
+ */
+ public static <E> Iterator<E> singletonIterator(E value) {
+ return new SingleElementIterator<E>(value);
+ }
+
+ /**
+ * Return a list iterator that returns only the single,
+ * specified object.
+ * <p>
+ * <code>Object.toListIterator()</code>
+ */
+ public static <E> ListIterator<E> singletonListIterator(E value) {
+ return new SingleElementListIterator<E>(value);
+ }
+
+
+ // ********** size **********
+
+ /**
+ * Return the number of elements returned by the specified iterable.
+ * <p>
+ * <code>Iterable.size()</code>
+ */
+ public static int size(Iterable<?> iterable) {
+ return size(iterable.iterator());
+ }
+
+ /**
+ * Return the number of elements returned by the specified iterator.
+ * <p>
+ * <code>Iterator.size()</code>
+ */
+ 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.
+ * <p>
+ * <code>Iterable.sort()</code>
+ */
+ public static <E extends Comparable<? super E>> Iterable<E> sort(Iterable<E> iterable) {
+ return sort(iterable, null);
+ }
+
+ /**
+ * Return an iterable containing the sorted elements of the specified iterable.
+ * The specified iterable size is a performance hint.
+ * <p>
+ * <code>Iterable.sort()</code>
+ */
+ public static <E extends Comparable<? super E>> Iterable<E> sort(Iterable<E> iterable, int iterableSize) {
+ return sort(iterable, null, iterableSize);
+ }
+
+ /**
+ * Return an iterable containing the sorted elements of the specified iterable.
+ * <p>
+ * <code>Iterable.sort(Comparator comparator)</code>
+ */
+ public static <E> Iterable<E> sort(Iterable<E> iterable, Comparator<? super E> 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.
+ * <p>
+ * <code>Iterable.sort(Comparator comparator)</code>
+ */
+ public static <E> Iterable<E> sort(Iterable<E> iterable, Comparator<? super E> comparator, int iterableSize) {
+ return sort(list(iterable, iterableSize), comparator);
+ }
+
+ /**
+ * Return the iterator after it has been "sorted".
+ * <p>
+ * <code>Iterator.sort()</code>
+ */
+ public static <E extends Comparable<? super E>> ListIterator<E> sort(Iterator<? extends E> iterator) {
+ return sort(iterator, null);
+ }
+
+ /**
+ * Return the iterator after it has been "sorted".
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>Iterator.sort()</code>
+ */
+ public static <E extends Comparable<? super E>> ListIterator<E> sort(Iterator<? extends E> iterator, int iteratorSize) {
+ return sort(iterator, null, iteratorSize);
+ }
+
+ /**
+ * Return the iterator after it has been "sorted".
+ * <p>
+ * <code>Iterator.sort(Comparator comparator)</code>
+ */
+ public static <E> ListIterator<E> sort(Iterator<? extends E> iterator, Comparator<? super E> comparator) {
+ return sort(list(iterator), comparator).listIterator();
+ }
+
+ /**
+ * Return the iterator after it has been "sorted".
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>Iterator.sort(Comparator comparator)</code>
+ */
+ public static <E> ListIterator<E> sort(Iterator<? extends E> iterator, Comparator<? super E> comparator, int iteratorSize) {
+ return sort(list(iterator, iteratorSize), comparator).listIterator();
+ }
+
+
+ // ********** sorted set **********
+
+ /**
+ * Return a sorted set corresponding to the specified iterable.
+ * <p>
+ * <code>TreeSet(Iterable iterable)</code>
+ */
+ public static <E extends Comparable<? super E>> TreeSet<E> sortedSet(Iterable<? extends E> iterable) {
+ return sortedSet(iterable.iterator());
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified iterable.
+ * The specified iterable size is a performance hint.
+ * <p>
+ * <code>TreeSet(Iterable iterable)</code>
+ */
+ public static <E extends Comparable<? super E>> TreeSet<E> sortedSet(Iterable<? extends E> iterable, int iterableSize) {
+ return sortedSet(iterable.iterator(), iterableSize);
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified iterable
+ * and comparator.
+ * <p>
+ * <code>TreeSet(Iterable iterable, Comparator c)</code>
+ */
+ public static <E> TreeSet<E> sortedSet(Iterable<? extends E> iterable, Comparator<? super E> 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.
+ * <p>
+ * <code>TreeSet(Iterable iterable, Comparator c)</code>
+ */
+ public static <E> TreeSet<E> sortedSet(Iterable<? extends E> iterable, Comparator<? super E> comparator, int iterableSize) {
+ return sortedSet(iterable.iterator(), comparator, iterableSize);
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified iterator.
+ * <p>
+ * <code>TreeSet(Iterator iterator)</code>
+ */
+ public static <E extends Comparable<? super E>> TreeSet<E> sortedSet(Iterator<? extends E> iterator) {
+ return sortedSet(iterator, null);
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified iterator.
+ * The specified iterator size is a performance hint.
+ * <p>
+ * <code>TreeSet(Iterator iterator)</code>
+ */
+ public static <E extends Comparable<? super E>> TreeSet<E> sortedSet(Iterator<? extends E> iterator, int iteratorSize) {
+ return sortedSet(iterator, null, iteratorSize);
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified iterator
+ * and comparator.
+ * <p>
+ * <code>TreeSet(Iterator iterator, Comparator c)</code>
+ */
+ public static <E> TreeSet<E> sortedSet(Iterator<? extends E> iterator, Comparator<? super E> 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.
+ * <p>
+ * <code>TreeSet(Iterator iterator, Comparator c)</code>
+ */
+ public static <E> TreeSet<E> sortedSet(Iterator<? extends E> iterator, Comparator<? super E> comparator, int iteratorSize) {
+ return sortedSet(list(iterator, iteratorSize), comparator);
+ }
+
+ private static <E> TreeSet<E> sortedSet(List<E> list, Comparator<? super E> comparator) {
+ TreeSet<E> sortedSet = new TreeSet<E>(comparator);
+ sortedSet.addAll(list);
+ return sortedSet;
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified array.
+ * <p>
+ * <code>TreeSet(Object[] array)</code>
+ */
+ public static <E extends Comparable<? super E>> TreeSet<E> sortedSet(E... array) {
+ return sortedSet(array, null);
+ }
+
+ /**
+ * Return a sorted set corresponding to the specified array
+ * and comparator.
+ * <p>
+ * <code>TreeSet(Object[] array, Comparator c)</code>
+ */
+ public static <E> TreeSet<E> sortedSet(E[] array, Comparator<? super E> comparator) {
+ TreeSet<E> sortedSet = new TreeSet<E>(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}.
+ * <p>
+ * <code>Vector(Iterable iterable)</code>
+ */
+ public static <E> Vector<E> vector(Iterable<? extends E> iterable) {
+ return vector(iterable.iterator());
+ }
+
+ /**
+ * Return a vector corresponding to the specified iterable.
+ * This is useful for legacy code that requires a {@link Vector}.
+ * <p>
+ * <code>Vector(Iterable iterable, int size)</code>
+ */
+ public static <E> Vector<E> vector(Iterable<? extends E> 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}.
+ * <p>
+ * <code>Vector(Iterator iterator)</code>
+ */
+ public static <E> Vector<E> vector(Iterator<? extends E> iterator) {
+ return vector(iterator, new Vector<E>());
+ }
+
+ /**
+ * Return a vector corresponding to the specified iterator.
+ * This is useful for legacy code that requires a {@link Vector}.
+ * <p>
+ * <code>Vector(Iterator iterator, int size)</code>
+ */
+ public static <E> Vector<E> vector(Iterator<? extends E> iterator, int size) {
+ return vector(iterator, new Vector<E>(size));
+ }
+
+ private static <E> Vector<E> vector(Iterator<? extends E> iterator, Vector<E> 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}.
+ * <p>
+ * <code>Vector(Object... array)</code>
+ */
+ public static <E> Vector<E> vector(E... array) {
+ Vector<E> v = new Vector<E>(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 <E> Iterable<E> iterable(Iterator<? extends E> iterator) {
+ return new SingleUseIterable<E>(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<E> implements Iterable<E> {
+ private Iterator<E> iterator;
+
+ public SingleUseIterable(Iterator<? extends E> iterator) {
+ super();
+ if (iterator == null) {
+ throw new NullPointerException();
+ }
+ this.iterator = new SuperIteratorWrapper<E>(iterator);
+ }
+
+ public Iterator<E> iterator() {
+ if (this.iterator == null) {
+ throw new IllegalStateException("This method has already been called."); //$NON-NLS-1$
+ }
+ Iterator<E> 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 <E> List<E> copy(List<E> dest, List<? extends E> src) {
+ Collections.copy(dest, src);
+ return dest;
+ }
+
+ /**
+ * Return the list after it has been "filled".
+ * @see Collections#fill(List, Object)
+ */
+ public static <E> List<E> fill(List<E> list, E value) {
+ Collections.fill(list, value);
+ return list;
+ }
+
+ /**
+ * Return the list after it has been "reversed".
+ * @see Collections#reverse(List)
+ */
+ public static <E> List<E> reverse(List<E> list) {
+ Collections.reverse(list);
+ return list;
+ }
+
+ /**
+ * Return the list after it has been "rotated".
+ * @see Collections#rotate(List, int)
+ */
+ public static <E> List<E> rotate(List<E> list, int distance) {
+ Collections.rotate(list, distance);
+ return list;
+ }
+
+ /**
+ * Return the list after it has been "shuffled".
+ * @see Collections#shuffle(List)
+ */
+ public static <E> List<E> shuffle(List<E> list) {
+ Collections.shuffle(list);
+ return list;
+ }
+
+ /**
+ * Return the list after it has been "shuffled".
+ * @see Collections#shuffle(List, Random)
+ */
+ public static <E> List<E> shuffle(List<E> 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 <E extends Comparable<? super E>> List<E> sort(List<E> 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 <E> List<E> sort(List<E> list, Comparator<? super E> 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 <E> List<E> swap(List<E> 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;
+
+/**
+ * <code>CompositeCommand</code> provides support for treating a collection of
+ * {@link Command}s as a single command.
+ */
+public class CompositeCommand
+ implements Command
+{
+ private final Iterable<Command> commands;
+
+ public CompositeCommand(Command... commands) {
+ this(new ArrayIterable<Command>(commands));
+ }
+
+ public CompositeCommand(Iterable<Command> 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<Throwable> 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 <code>ConsumerThreadCoordinator</code> 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.
+ * <p>
+ * <strong>NB:</strong> 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<Throwable> exceptions = new Vector<Throwable>();
+
+
+ // ********** 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.
+ * <p>
+ * If this thread is interrupted <em>during</em> {@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:<ul>
+ * <li>when the consumer thread suspends, waiting for something to consume
+ * <li>the consuming of whatever is being produced
+ * </ul>
+ */
+ 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.
+ * <p>
+ * 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.
+ * <em>USE WITH CARE.</em>
+ * File#deleteAll()?
+ */
+ public static void deleteDirectory(String directoryName) {
+ deleteDirectory(new File(directoryName));
+ }
+
+ /**
+ * Delete the specified directory and all of its contents.
+ * <em>USE WITH CARE.</em>
+ * 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).
+ * <em>USE WITH CARE.</em>
+ * File#deleteFiles()
+ */
+ public static void deleteDirectoryContents(String directoryName) {
+ deleteDirectoryContents(new File(directoryName));
+ }
+
+ /**
+ * Delete the contents of the specified directory
+ * (but not the directory itself).
+ * <em>USE WITH CARE.</em>
+ * 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<File> 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<File> filesIn(File directory) {
+ return filesIn(directory.listFiles());
+ }
+
+ private static Iterator<File> filesIn(File[] files) {
+ return new FilteringIterator<File>(new ArrayIterator<File>(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<File> directoriesIn(String directoryName) {
+ return directoriesIn(new File(directoryName));
+ }
+
+ /**
+ * Return an iterator on all the subdirectories
+ * in the specified directory.
+ * File#subDirectories()
+ */
+ public static Iterator<File> directoriesIn(File directory) {
+ return directoriesIn(directory.listFiles());
+ }
+
+ private static Iterator<File> directoriesIn(File[] files) {
+ return new FilteringIterator<File>(new ArrayIterator<File>(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<File> 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<File> filesInTree(File directory) {
+ return filesInTreeAsSet(directory).iterator();
+ }
+
+ private static Set<File> filesInTreeAsSet(File directory) {
+ Set<File> files = new HashSet<File>(10000);
+ addFilesInTreeTo(directory, files);
+ return files;
+ }
+
+ private static void addFilesInTreeTo(File directory, Collection<File> 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<File> 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<File> directoriesInTree(File directory) {
+ File[] files = directory.listFiles();
+ return new CompositeIterator<File>(directoriesIn(files), directoriesInTrees(directoriesIn(files)));
+ }
+
+ private static Iterator<File> directoriesInTrees(Iterator<File> directories) {
+ return new CompositeIterator<File>(
+ new TransformationIterator<File, Iterator<File>>(directories) {
+ @Override
+ protected Iterator<File> 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<File> canonicalFiles(Iterator<File> files) {
+ return new TransformationIterator<File, File>(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<File> canonicalFiles(Collection<File> 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<String> canonicalFileNames(Iterator<String> fileNames) {
+ return new TransformationIterator<String, String>(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<String> canonicalFileNames(Collection<String> 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<File> filter(Iterator<File> files, final FileFilter fileFilter) {
+ return new FilteringIterator<File>(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<File> path = new ArrayList<File>();
+ 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
+ * <code>null</code>.
+ * <p>
+ * The reference can be set multiple times, but it can
+ * never be "unset" once it is "set".
+ */
+public class FlaggedObjectReference<V>
+ extends SimpleObjectReference<V>
+{
+ 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
+ * <code>null</code>.
+ */
+ 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 <code>null</code>
+ * element.
+ * <p>
+ * This class offers constant time performance for the basic operations
+ * (<code>add</code>, <code>remove</code>, <code>contains</code> and
+ * <code>size</code>), 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.
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If multiple
+ * threads access a bag concurrently, and at least one of the threads modifies
+ * the bag, it <em>must</em> 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
+ * <code>Collections.synchronizedCollection</code> method. This is
+ * best done at creation time, to prevent accidental unsynchronized access
+ * to the bag:
+ * <pre>
+ * Collection c = Collections.synchronizedCollection(new HashBag(...));
+ * </pre>
+ * <p>
+ * The iterators returned by this class's <code>iterator</code> method are
+ * <em>fail-fast</em>: if the bag is modified at any time after the iterator is
+ * created, in any way except through the iterator's own <code>remove</code>
+ * 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.
+ * <p>
+ * 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 <code>ConcurrentModificationException</code> on a best-effort basis.
+ * Therefore, it would be wrong to write a program that depended on this
+ * exception for its correctness: <em>the fail-fast behavior of iterators
+ * should be used only to detect bugs.</em>
+ *
+ * @param <E> the type of elements maintained by the bag
+ *
+ * @see Collection
+ * @see Bag
+ * @see SynchronizedBag
+ * @see Collections#synchronizedCollection(Collection)
+ * @see IdentityHashBag
+ */
+public class HashBag<E>
+ extends AbstractCollection<E>
+ implements Bag<E>, Cloneable, Serializable
+{
+ /** The hash table. Resized as necessary. Length MUST Always be a power of two. */
+ transient Entry<E>[] 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 <code>(int) (capacity * loadFactor)</code>.)
+ *
+ * @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<? extends E> 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<? extends E> 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> 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> 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<E> getEntry(Object o) {
+ int hash = this.hash(o);
+ for (Entry<E> 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> 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<E>[] oldTable = this.table;
+ int oldCapacity = oldTable.length;
+
+ if (oldCapacity == MAXIMUM_CAPACITY) {
+ this.threshold = Integer.MAX_VALUE;
+ return;
+ }
+
+ int newCapacity = 2 * oldCapacity;
+ Entry<E>[] newTable = this.buildTable(newCapacity);
+
+ for (int i = oldCapacity; i-- > 0; ) {
+ for (Entry<E> old = oldTable[i]; old != null; ) {
+ Entry<E> 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<E>[] 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> 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> 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<E> buildEntry(int hash, Object o, int cnt, Entry next) {
+ return new Entry<E>(hash, (E) o, cnt, (Entry<E>) 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> 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<E>[] 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<E> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ HashBag<E> clone = (HashBag<E>) 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<E> implements Bag.Entry<E> {
+ final int hash;
+ final E object;
+ int count;
+ Entry<E> next;
+
+ Entry(int hash, E object, int count, Entry<E> 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<E> iterator() {
+ return (this.size == 0) ? EMPTY_ITERATOR : new HashIterator();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator<E> uniqueIterator() {
+ return (this.size == 0) ? EMPTY_ITERATOR : new UniqueIterator();
+ }
+
+ public int uniqueCount() {
+ return this.uniqueCount;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator<Bag.Entry<E>> 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<E> {
+ private int index = HashBag.this.table.length; // start at the end of the table
+ private Entry<E> nextEntry = null;
+ private int nextEntryCount = 0;
+ private Entry<E> 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> e = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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<E> et = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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> 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> 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<Entry<E>> {
+ private int index = HashBag.this.table.length; // start at the end of the table
+ private Entry<E> nextEntry = null;
+ private Entry<E> 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> e = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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<E> next() {
+ if (HashBag.this.modCount != this.expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ Entry<E> et = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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> 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> 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<E> {
+ 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<E> b = (Bag<E>) o;
+ if (b.size() != this.size()) {
+ return false;
+ }
+ if (b.uniqueCount() != this.uniqueCount()) {
+ return false;
+ }
+ for (Iterator<Bag.Entry<E>> stream = b.entries(); stream.hasNext(); ) {
+ Bag.Entry<E> 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<E> 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 <code>IdentityHashBag</code>,
+ * two objects <code>o1</code> and <code>o2</code> are considered
+ * equal if and only if <code>(o1 == o2)</code>. (In normal {@link Bag}
+ * implementations (like {@link HashBag}) two objects <code>o1</code>
+ * and <code>o2</code> are considered equal if and only if
+ * <code>(o1 == null ? o2 == null : o1.equals(o2))</code>.)
+ * <p>
+ * <strong>
+ * This class is <em>not</em> 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 <code>equals</code> method when comparing objects. This class is
+ * designed for use only in the rare cases wherein object-identity
+ * semantics are required.
+ * </strong>
+ * <p>
+ * 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 <code>null</code>
+ * element.
+ * <p>
+ * This class offers constant time performance for the basic operations
+ * (<code>add</code>, <code>remove</code>, <code>contains</code> and
+ * <code>size</code>), 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.
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If multiple
+ * threads access a bag concurrently, and at least one of the threads modifies
+ * the bag, it <em>must</em> 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
+ * <code>Collections.synchronizedCollection</code> method. This is
+ * best done at creation time, to prevent accidental unsynchronized access
+ * to the bag:
+ * <pre>
+ * Collection c = Collections.synchronizedCollection(new IdentityHashBag(...));
+ * </pre>
+ * <p>
+ * The iterators returned by this class's <code>iterator</code> method are
+ * <em>fail-fast</em>: if the bag is modified at any time after the iterator is
+ * created, in any way except through the iterator's own <code>remove</code>
+ * 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.
+ * <p>
+ * 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 <code>ConcurrentModificationException</code> on a best-effort basis.
+ * Therefore, it would be wrong to write a program that depended on this
+ * exception for its correctness: <em>the fail-fast behavior of iterators
+ * should be used only to detect bugs.</em>
+ *
+ * @param <E> the type of elements maintained by the bag
+ *
+ * @see Collection
+ * @see Bag
+ * @see SynchronizedBag
+ * @see Collections#synchronizedCollection(Collection)
+ * @see HashBag
+ */
+public class IdentityHashBag<E>
+ extends AbstractCollection<E>
+ implements Bag<E>, Cloneable, Serializable
+{
+ /** The hash table. Resized as necessary. Length MUST Always be a power of two. */
+ transient Entry<E>[] 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 <code>(int) (capacity * loadFactor)</code>.)
+ *
+ * @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<? extends E> 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<? extends E> 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> 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> 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<E> getEntry(Object o) {
+ for (Entry<E> 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> 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<E>[] oldTable = this.table;
+ int oldCapacity = oldTable.length;
+
+ if (oldCapacity == MAXIMUM_CAPACITY) {
+ this.threshold = Integer.MAX_VALUE;
+ return;
+ }
+
+ int newCapacity = 2 * oldCapacity;
+ Entry<E>[] newTable = this.buildTable(newCapacity);
+
+ for (int i = oldCapacity; i-- > 0; ) {
+ for (Entry<E> old = oldTable[i]; old != null; ) {
+ Entry<E> 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<E>[] 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> 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> 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<E> buildEntry(int hash, Object o, int cnt, Entry next) {
+ return new Entry<E>(hash, (E) o, cnt, (Entry<E>) 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> 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<Object>(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<Object>(c));
+ }
+
+ /**
+ * This implementation simply clears out all of the hash table buckets.
+ */
+ @Override
+ public void clear() {
+ Entry<E>[] 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<E> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ IdentityHashBag<E> clone = (IdentityHashBag<E>) 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<E> implements Bag.Entry<E> {
+ final int hash; // cache the hash for re-hashes
+ final E object;
+ int count;
+ Entry<E> next;
+
+ Entry(int hash, E object, int count, Entry<E> 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<E> iterator() {
+ return (this.size == 0) ? EMPTY_ITERATOR : new HashIterator();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator<E> uniqueIterator() {
+ return (this.size == 0) ? EMPTY_ITERATOR : new UniqueIterator();
+ }
+
+ public int uniqueCount() {
+ return this.uniqueCount;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator<Bag.Entry<E>> 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<E> {
+ private int index = IdentityHashBag.this.table.length; // start at the end of the table
+ private Entry<E> nextEntry = null;
+ private int nextEntryCount = 0;
+ private Entry<E> 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> e = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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<E> et = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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> 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> 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<Entry<E>> {
+ private int index = IdentityHashBag.this.table.length; // start at the end of the table
+ private Entry<E> nextEntry = null;
+ private Entry<E> 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> e = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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<E> next() {
+ if (IdentityHashBag.this.modCount != this.expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ Entry<E> et = this.nextEntry;
+ int i = this.index;
+ Entry<E>[] 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> 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> 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<E> {
+ 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<E> b = (IdentityHashBag<E>) o;
+ if (b.size() != this.size()) {
+ return false;
+ }
+ if (b.uniqueCount() != this.uniqueCount()) {
+ return false;
+ }
+ for (Iterator<Bag.Entry<E>> stream = b.entries(); stream.hasNext(); ) {
+ Bag.Entry<E> 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<Object>(this).equals(o);
+// } else {
+// return false;
+// }
+ }
+
+ private boolean equals_(Object o) {
+ // hmmm...
+ return (o instanceof Bag<?>) &&
+ new HashBag<Object>(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<E> 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 <code>int</code> value.
+ * Return the previous value.
+ */
+ int setValue(int value);
+
+ /**
+ * Set the <code>int</code> value to zero.
+ * Return the previous value.
+ */
+ int setZero();
+
+ /**
+ * Increment and return the <code>int</code> value.
+ */
+ int increment();
+
+ /**
+ * Decrement and return the <code>int</code> 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<String, JDBCToJavaTypeMapping> 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<String, JDBCToJavaTypeMapping> jdbcToJavaTypeMappings() {
+ if (JDBC_TO_JAVA_TYPE_MAPPINGS == null) {
+ JDBC_TO_JAVA_TYPE_MAPPINGS = buildJDBCToJavaTypeMappings();
+ }
+ return JDBC_TO_JAVA_TYPE_MAPPINGS;
+ }
+
+ private static HashMap<String, JDBCToJavaTypeMapping> buildJDBCToJavaTypeMappings() {
+ HashMap<String, JDBCToJavaTypeMapping> mappings = new HashMap<String, JDBCToJavaTypeMapping>();
+ 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<String, JDBCToJavaTypeMapping> 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<String, JDBCToJavaTypeMapping> 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<String, JavaToJDBCTypeMapping> 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<String, JavaToJDBCTypeMapping> javaToJDBCTypeMappings() {
+ if (JAVA_TO_JDBC_TYPE_MAPPINGS == null) {
+ JAVA_TO_JDBC_TYPE_MAPPINGS = buildJavaToJDBCTypeMappings();
+ }
+ return JAVA_TO_JDBC_TYPE_MAPPINGS;
+ }
+
+ private static HashMap<String, JavaToJDBCTypeMapping> buildJavaToJDBCTypeMappings() {
+ HashMap<String, JavaToJDBCTypeMapping> mappings = new HashMap<String, JavaToJDBCTypeMapping>();
+ 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<String, JavaToJDBCTypeMapping> 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<String, JavaToJDBCTypeMapping> 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<K, V> {
+
+ private final Set<V> itemSet;
+ private final Set<V> unmodifiableItemSet;
+ private final Map<K,V> map;
+
+
+ public KeyedSet() {
+ this.itemSet = new HashSet<V>();
+ this.unmodifiableItemSet = Collections.unmodifiableSet(this.itemSet);
+ this.map = new HashMap<K,V>();
+ }
+
+ /**
+ * Return an unmodifiable representation of the set of items.
+ */
+ public Set<V> 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<K,V> 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<K,V> 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.
+ * <p>
+ * There are some penalties:<ul>
+ * <li>The reference's use of generics will require casting (and the requisite
+ * VM testing) with every access
+ * <li>If the value calculated during lazy initialization is <code>null</code>,
+ * access will be <code>synchronized</code> <em>every</em> time.
+ * </ul>
+ * @see SimpleObjectReference
+ * @see SynchronizedObject
+ */
+public abstract class LazyReadOnlyObjectReference<V>
+ implements ReadOnlyObjectReference<V>, 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 <code>volatile</code>.
+ */
+ 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<V> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ LazyReadOnlyObjectReference<V> clone = (LazyReadOnlyObjectReference<V>) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new InternalError();
+ }
+ }
+
+ /**
+ * This method will <em>not</em> 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<L extends EventListener>
+ implements Serializable
+{
+ /**
+ * We can mark this volatile and not synchronize the read methods because
+ * we never change the <em>contents</em> 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<L> 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<L> 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<L> listenerClass, int length) {
+ return (L[]) Array.newInstance(listenerClass, length);
+ }
+
+ /**
+ * Return the listeners.
+ */
+ public Iterable<L> getListeners() {
+ return new ArrayIterable<L>(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<L> getListenerType() {
+ return (Class<L>) 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<L> listenerClass = (Class<L>) 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<L> listenerClass = (Class<L>) 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> convertToLowerCase(Collection<String> strings) {
+ HashSet<String> result = new HashSet<String>(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<String> 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<String> javaReservedWords() {
+ return new ArrayIterator<String>(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 <code>NonNullBooleanTransformer</code> will transform a possibly-null
+ * {@link Boolean} to a non-null {@link Boolean}:<ul>
+ * <li>When the original {@link Boolean} is <em>not</em> <code>null</code>,
+ * the transformer will return it unchanged.
+ * <li>When the original {@link Boolean} is <code>null</code>,
+ * the transformer will return its client-specified "null value"
+ * ({@link Boolean#TRUE} or {@link Boolean#FALSE}).
+ * </ul>
+ */
+public final class NonNullBooleanTransformer
+ implements Transformer<Boolean, Boolean>
+{
+ // not null
+ private final Boolean nullValue;
+
+ /**
+ * A {@link Transformer} that will return the original {@link Boolean} when
+ * it is non-<code>null</code>; otherwise the {@link Transformer} will return
+ * {@link Boolean#TRUE}.
+ */
+ public static final Transformer<Boolean, Boolean> TRUE = new NonNullBooleanTransformer(Boolean.TRUE);
+
+ /**
+ * A {@link Transformer} that will return the original {@link Boolean} when
+ * it is non-<code>null</code>; otherwise the {@link Transformer} will return
+ * {@link Boolean#FALSE}.
+ */
+ public static final Transformer<Boolean, Boolean> FALSE = new NonNullBooleanTransformer(Boolean.FALSE);
+
+ /**
+ * Return a transformer that will return the specified value if the original
+ * value is <code>null</code>. Throw a {@link NullPointerException} if the
+ * specified value is <code>null</code>.
+ */
+ public Transformer<Boolean, Boolean> 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 <code>null</code>.
+ */
+ public Transformer<Boolean, Boolean> 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 <code>NotBooleanTransformer</code> will transform a
+ * {@link Boolean} to its NOT value:<ul>
+ * <li>If the original {@link Boolean} is {@link Boolean#TRUE},
+ * the transformer will return {@link Boolean#FALSE}.
+ * <li>If the original {@link Boolean} is {@link Boolean#FALSE},
+ * the transformer will return {@link Boolean#TRUE}.
+ * <li>If the original {@link Boolean} is <code>null</code>,
+ * the transformer will return <code>null</code>.
+ * </ul>
+ */
+public class NotBooleanTransformer
+ implements BidiTransformer<Boolean, Boolean>
+{
+ public static final BidiTransformer<Boolean, Boolean> INSTANCE = new NotBooleanTransformer();
+
+ public static BidiTransformer<Boolean, Boolean> 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<T>
+ implements Filter<T>, Serializable
+{
+ @SuppressWarnings("rawtypes")
+ public static final Filter INSTANCE = new NotNullFilter();
+
+ @SuppressWarnings("unchecked")
+ public static <R> Filter<R> 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<E>
+ implements List<E>, Serializable
+{
+
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final NullList INSTANCE = new NullList();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> List<E> 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<? extends E> c) {
+ return false; // the list did not change
+ }
+
+ public boolean addAll(int index, Collection<? extends E> 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<E> iterator() {
+ return EmptyIterator.instance();
+ }
+
+ public int lastIndexOf(Object o) {
+ return -1;
+ }
+
+ public ListIterator<E> listIterator() {
+ return EmptyListIterator.instance();
+ }
+
+ public ListIterator<E> 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<E> 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> 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 <E> the type of elements contained by the queue
+ */
+public interface Queue<E> {
+
+ /**
+ * "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<E> implements Queue<E>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Queue INSTANCE = new Empty();
+ @SuppressWarnings("unchecked")
+ public static <T> Queue<T> 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 <code>boolean</code> that cannot be
+ * changed by clients.
+ */
+public interface ReadOnlyBooleanReference
+{
+ /**
+ * Return the current <code>boolean</code> value.
+ */
+ boolean getValue();
+
+ /**
+ * Return whether the current <code>boolean</code> value is equal to the
+ * specified value.
+ */
+ boolean is(boolean value);
+
+ /**
+ * Return whether the current <code>boolean</code> value is not equal to
+ * the specified value.
+ */
+ boolean isNot(boolean value);
+
+ /**
+ * Return whether the current <code>boolean</code> value is
+ * <code>true</code>.
+ */
+ boolean isTrue();
+
+ /**
+ * Return whether the current <code>boolean</code> value is
+ * <code>false</code>.
+ */
+ 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 <code>int</code> that cannot be
+ * changed by clients.
+ */
+public interface ReadOnlyIntReference
+ extends Comparable<ReadOnlyIntReference>
+{
+ /**
+ * Return the current <code>int</code> value.
+ */
+ int getValue();
+
+ /**
+ * Return whether the current <code>int</code> value is equal to the
+ * specified value.
+ */
+ boolean equals(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is not equal to
+ * the specified value.
+ */
+ boolean notEqual(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is zero.
+ */
+ boolean isZero();
+
+ /**
+ * Return whether the current <code>int</code> value is not zero.
+ */
+ boolean isNotZero();
+
+ /**
+ * Return whether the current <code>int</code> value is greater than
+ * the specified value.
+ */
+ boolean isGreaterThan(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is greater than
+ * or equal to the specified value.
+ */
+ boolean isGreaterThanOrEqual(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is less than
+ * the specified value.
+ */
+ boolean isLessThan(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is less than
+ * or equal to the specified value.
+ */
+ boolean isLessThanOrEqual(int v);
+
+ /**
+ * Return whether the current <code>int</code> value is positive.
+ */
+ boolean isPositive();
+
+ /**
+ * Return whether the current <code>int</code> value is not positive
+ * (i.e. negative or zero).
+ */
+ boolean isNotPositive();
+
+ /**
+ * Return whether the current <code>int</code> value is negative.
+ */
+ boolean isNegative();
+
+ /**
+ * Return whether the current <code>int</code> value is not negative
+ * (i.e. zero or positive).
+ */
+ boolean isNotNegative();
+
+ /**
+ * Return the absolute value of the current <code>int</code> value.
+ */
+ int abs();
+
+ /**
+ * Return the negative value of the current <code>int</code> value.
+ */
+ int neg();
+
+ /**
+ * Return the current <code>int</code> value plus the specified value.
+ */
+ int add(int v);
+
+ /**
+ * Return current <code>int</code> value minus the specified value.
+ */
+ int subtract(int v);
+
+ /**
+ * Return current <code>int</code> value multiplied by the specified value.
+ */
+ int multiply(int v);
+
+ /**
+ * Return current <code>int</code> value divided by the specified value.
+ */
+ int divide(int v);
+
+ /**
+ * Return the remainder of the current <code>int</code> value divided by
+ * the specified value.
+ */
+ int remainder(int v);
+
+ /**
+ * Return the minimum of the current <code>int</code> value and
+ * the specified value.
+ */
+ int min(int v);
+
+ /**
+ * Return the maximum of the current <code>int</code> value and
+ * the specified value.
+ */
+ int max(int v);
+
+ /**
+ * Return the current <code>int</code> 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 <code>java.lang.reflect</code> 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.
+ * <p>
+ * 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.).
+ * <p>
+ * 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<java.lang.Void> 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.
+ * <p>
+ * <code>Object.getFieldValue(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.getFieldValue(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.getStaticFieldValue(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.getStaticFieldValue(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.setFieldValue(String fieldName, Object value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.setFieldValue(String fieldName, Object value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.setStaticFieldValue(String fieldName, Object value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.setStaticFieldValue(String fieldName, Object value)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.getField(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.getField(String fieldName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.getAllFields()</code>
+ */
+ public static Iterable<Field> getAllFields(Class<?> javaClass) {
+ ArrayList<Field> fields = new ArrayList<Field>();
+ 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<Field> fields) {
+ for (Field field : getDeclaredFields(javaClass)) {
+ fields.add(field);
+ }
+ }
+
+ /**
+ * Return the declared fields for the specified class.
+ * Make any private/package/protected fields accessible.
+ * <p>
+ * <code>Class.getAccessibleDeclaredFields()</code>
+ */
+ public static Iterable<Field> getDeclaredFields(Class<?> javaClass) {
+ Field[] fields = javaClass.getDeclaredFields();
+ for (Field field : fields) {
+ field.setAccessible(true);
+ }
+ return new ArrayIterable<Field>(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.
+ * <p>
+ * <code>Object.execute(String methodName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.execute(String methodName, Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.execute(String methodName, Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.execute(String methodName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.execute(String methodName, Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Object.execute(String methodName, Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName, Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName, Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName, Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.executeStaticMethod(String methodName, Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.getAllMethods()</code>
+ */
+ public static Iterable<Method> getAllMethods(Class<?> javaClass) {
+ ArrayList<Method> methods = new ArrayList<Method>();
+ 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<Method> methods) {
+ for (Method method : getDeclaredMethods(javaClass)) {
+ methods.add(method);
+ }
+ }
+
+ /**
+ * Return the declared methods for the specified class.
+ * Make any private/package/protected methods accessible.
+ * <p>
+ * <code>Class.getAccessibleDeclaredMethods()</code>
+ */
+ public static Iterable<Method> getDeclaredMethods(Class<?> javaClass) {
+ Method[] methods = javaClass.getDeclaredMethods();
+ for (Method method : methods) {
+ method.setAccessible(true);
+ }
+ return new ArrayIterable<Method>(methods);
+ }
+
+
+ // ********** constructors **********
+
+ /**
+ * Return the default (zero-argument) constructor
+ * for the specified class.
+ * Make any private/package/protected constructor accessible.
+ * <p>
+ * <code>Class.getDefaultConstructor()</code>
+ */
+ public static <T> Constructor<T> getDefaultConstructor(Class<T> javaClass) {
+ return getConstructor(javaClass);
+ }
+
+ /**
+ * Convenience method.
+ * Return the default (zero-argument) constructor
+ * for the specified class.
+ * Make any private/package/protected constructor accessible.
+ * <p>
+ * <code>Class.getConstructor()</code>
+ */
+ public static <T> Constructor<T> getConstructor(Class<T> 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.
+ * <p>
+ * <code>Class.getConstructor(Class<?> parameterType)</code>
+ */
+ public static <T> Constructor<T> getConstructor(Class<T> 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.
+ * <p>
+ * <code>Class.getConstructor(Class<?>[] parameterTypes)</code>
+ */
+ public static <T> Constructor<T> getConstructor(Class<T> 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.
+ * <p>
+ * <code>Class.getDefaultConstructor()</code>
+ */
+ public static <T> Constructor<T> getDefaultConstructor_(Class<T> 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.
+ * <p>
+ * <code>Class.getConstructor()</code>
+ */
+ public static <T> Constructor<T> getConstructor_(Class<T> 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.
+ * <p>
+ * <code>Class.getConstructor(Class<?> parameterType)</code>
+ */
+ public static <T> Constructor<T> getConstructor_(Class<T> 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.
+ * <p>
+ * <code>Class.getConstructor(Class<?>[] parameterTypes)</code>
+ */
+ public static <T> Constructor<T> getConstructor_(Class<T> javaClass, Class<?>[] parameterTypes)
+ throws NoSuchMethodException
+ {
+ Constructor<T> constructor = javaClass.getDeclaredConstructor(parameterTypes);
+ constructor.setAccessible(true);
+ return constructor;
+ }
+
+ /**
+ * Return the declared constructors for the specified class.
+ * Make any private/package/protected constructors accessible.
+ * <p>
+ * <code>Class.getAccessibleDeclaredConstructors()</code>
+ */
+ public static <T> Iterable<Constructor<T>> getDeclaredConstructors(Class<T> javaClass) {
+ @SuppressWarnings("unchecked")
+ Constructor<T>[] constructors = (Constructor<T>[]) javaClass.getDeclaredConstructors();
+ for (Constructor<T> constructor : constructors) {
+ constructor.setAccessible(true);
+ }
+ return new ArrayIterable<Constructor<T>>(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.
+ * <p>
+ * <code>Class.getArrayDepth()</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.getElementType()</code>
+ */
+ 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 <code>null</code> if the specified class
+ * is not a primitive class.
+ * <p>
+ * <code>Class.getWrapperClass()</code>
+ */
+ 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. <code>java.lang.Void</code> or one of the primitive
+ * variable wrapper classes, <code>java.lang.Boolean</code>,
+ * <code>java.lang.Integer</code>, <code>java.lang.Float</code>, etc.).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ * <p>
+ * <code>Class.isPrimitiveWrapper()</code>
+ */
+ 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. <code>java.lang.Boolean</code>,
+ * <code>java.lang.Integer</code>, <code>java.lang.Float</code>, etc.,
+ * but not <code>java.lang.Void</code>).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ * <p>
+ * <code>Class.isVariablePrimitiveWrapper()</code>
+ */
+ 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. <code>boolean</code>, <code>int</code>,
+ * <code>float</code>, etc., but not <code>void</code>).
+ * <p>
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ * <p>
+ * <code>Class.isVariablePrimitive()</code>
+ */
+ public static boolean classIsVariablePrimitive(Class<?> javaClass) {
+ return javaClass.isPrimitive() && (javaClass != VOID_CLASS);
+ }
+
+ /**
+ * Return the primitive class for the specified primitive class code.
+ * Return <code>null</code> 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 <code>null</code> 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 <code>0</code> 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.
+ * <p>
+ * <code>Class.newInstance()</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance(Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance(Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance()</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance(Class<?> parameterType, Object argument)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance(Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ 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.
+ * <p>
+ * <code>Class.newInstance()</code>
+ */
+ public static <T> T newInstance(Class<T> 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.
+ * <p>
+ * <code>Class.newInstance(Class<?> parameterType, Object argument)</code>
+ */
+ public static <T> T newInstance(Class<T> 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.
+ * <p>
+ * <code>Class.newInstance(Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ public static <T> T newInstance(Class<T> 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.
+ * <p>
+ * <code>Class.newInstance()</code>
+ */
+ public static <T> T newInstance_(Class<T> 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.
+ * <p>
+ * <code>Class.newInstance(Class<?> parameterType, Object argument)</code>
+ */
+ public static <T> T newInstance_(Class<T> 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.
+ * <p>
+ * <code>Class.newInstance(Class<?>[] parameterTypes, Object[] arguments)</code>
+ */
+ public static <T> T newInstance_(Class<T> 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.<ul>
+ * <li><code>"int[]"</code> returns <code>1</code>
+ * <li><code>"java.lang.String[][][]"</code> returns <code>3</code>
+ * </ul>
+ */
+ 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.<ul>
+ * <li><code>"int[]"</code> returns <code>"int"</code>
+ * <li><code>"java.lang.String[][][]"</code> returns <code>"java.lang.String"</code>
+ * </ul>
+ */
+ 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.<ul>
+ * <li><code>"int[]"</code> returns <code>"int"</code>
+ * <li><code>"java.lang.String[][][]"</code> returns <code>"java.lang.String"</code>
+ * </ul>
+ * 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.<ul>
+ * <li><code>"int"</code> returns <code>"int"</code>
+ * <li><code>"int[]"</code> returns <code>"[I"</code>
+ * <li><code>"java.lang.String"</code> returns <code>"java.lang.String"</code>
+ * <li><code>"java.lang.String[][][]"</code> returns <code>"[[[Ljava.lang.String;"</code>
+ * </ul>
+ * @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<Primitive> 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;
+ }
+
+ /**
+ * <strong>NB:</strong> <code>void.class.isPrimitive() == true</code>
+ */
+ private static Iterable<Primitive> 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<Primitive>(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<E extends Comparable<? super E>>
+ implements Comparator<E>, Serializable
+{
+ private final Comparator<E> comparator;
+
+ public ReverseComparator() {
+ this(null);
+ }
+
+ public ReverseComparator(Comparator<E> 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<K, V>
+ extends AbstractAssociation<K, V>
+ 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<K, V> clone() {
+ try {
+ return (SimpleAssociation<K, V>) 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 <code>boolean</code>. */
+ protected volatile boolean value;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Create a <code>boolean</code> reference with the specified initial value.
+ */
+ public SimpleBooleanReference(boolean value) {
+ super();
+ this.value = value;
+ }
+
+ /**
+ * Create a <code>boolean</code> reference with an initial value of
+ * <code>false</code>.
+ */
+ 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 <code>Filter</code>
+ * that holds on to a criterion object that can be used in the
+ * <code>accept(Object)</code> or <code>reject(Object)</code>
+ * methods. Subclasses can override either of these methods,
+ * depending on which is easier to implement. Note that at least
+ * one of these methods <em>must</em> be overridden or
+ * an infinite loop will occur. If both of them are overridden,
+ * only the <code>accept(Object)</code> method will be used.
+ * <p>
+ * Simplifies the implementation of straightforward inner classes.
+ * Here is an example of a filter that can be used by a
+ * <code>FilteringIterator</code> to return only those strings
+ * in the nested iterator start with "prefix":
+ * <pre>
+ * Filter<String> filter = new SimpleFilter<String>("prefix") {
+ * public boolean accept(String o) {
+ * return o.startsWith((String) criterion);
+ * }
+ * };
+ * </pre>
+ */
+public abstract class SimpleFilter<T, S>
+ implements Filter<T>, 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<T, S> clone() {
+ try {
+ return (SimpleFilter<T, S>) 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 <code>int</code> 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 <code>int</code>. */
+ private volatile int value = 0;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a <code>int</code> reference with the specified initial value.
+ */
+ public SimpleIntReference(int count) {
+ super();
+ this.value = count;
+ }
+
+ /**
+ * Construct a <code>int</code> 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:<ul><code>
+ * <li>java.lang.Object
+ * <li>int
+ * <li>java.util.Map$Entry
+ * <li>[Ljava.lang.Object;
+ * <li>[I
+ * <li>[Ljava.util.Map$Entry;
+ * </code></ul>
+ */
+ 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 <code>'$'</code> version of the name is used in {@link Class#forName(String)},
+ * but the <code>'.'</code> 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<V>
+ implements ObjectReference<V>, 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
+ * <code>null</code>.
+ */
+ 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<V> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ SimpleObjectReference<V> clone = (SimpleObjectReference<V>) 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<E>
+ implements Queue<E>, Cloneable, Serializable
+{
+ private LinkedList<E> elements;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct an empty queue.
+ */
+ public SimpleQueue() {
+ super();
+ this.elements = new LinkedList<E>();
+ }
+
+ /**
+ * 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<? extends E> c) {
+ super();
+ this.elements = new LinkedList<E>(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<E> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ SimpleQueue<E> clone = (SimpleQueue<E>) super.clone();
+ @SuppressWarnings("unchecked")
+ LinkedList<E> ll = (LinkedList<E>) 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<E>
+ implements Stack<E>, Cloneable, Serializable
+{
+ private LinkedList<E> elements;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct an empty stack.
+ */
+ public SimpleStack() {
+ super();
+ this.elements = new LinkedList<E>();
+ }
+
+ /**
+ * 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<? extends E> collection) {
+ super();
+ this.elements = new LinkedList<E>(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<E> clone() {
+ try {
+ @SuppressWarnings("unchecked")
+ SimpleStack<E> clone = (SimpleStack<E>) super.clone();
+ @SuppressWarnings("unchecked")
+ LinkedList<E> ll = (LinkedList<E>) 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<T>
+ implements StringMatcher, Filter<T>, Serializable
+{
+
+ /** An adapter that converts the objects into strings to be matched with the pattern. */
+ private StringConverter<T> 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<T> stringConverter() {
+ return this.stringConverter;
+ }
+
+ /**
+ * Set the string converter used to convert the objects
+ * passed to the matcher into strings.
+ */
+ public synchronized void setStringConverter(StringConverter<T> 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 <code>SimpleThreadFactory</code> 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 <E> the type of elements contained by the stack
+ */
+public interface Stack<E> {
+
+ /**
+ * "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<E> implements Stack<E>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Stack INSTANCE = new Empty();
+ @SuppressWarnings("unchecked")
+ public static <T> Stack<T> 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<T> {
+
+ /**
+ * 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<S> implements StringConverter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final StringConverter INSTANCE = new Default();
+ @SuppressWarnings("unchecked")
+ public static <R> StringConverter<R> 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<S> implements StringConverter<S>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final StringConverter INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> StringConverter<R> 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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:<pre>
+ * separate("012345", '-', 2) => "01-23-45"
+ * </pre>
+ */
+ 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<String> quote(Iterator<String> 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<String> delimit(Iterable<String> strings, char delimiter) {
+ return new TransformationIterable<String, String>(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<String> delimit(Iterator<String> strings, char delimiter) {
+ return new TransformationIterator<String, String>(strings, new CharStringDelimiter(delimiter));
+ }
+
+ private static class CharStringDelimiter implements Transformer<String, String> {
+ 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<String> delimit(Iterable<String> strings, String delimiter) {
+ return (delimiter.length() == 1) ?
+ delimit(strings, delimiter.charAt(0)) :
+ new TransformationIterable<String, String>(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<String> delimit(Iterator<String> strings, String delimiter) {
+ return (delimiter.length() == 1) ?
+ delimit(strings, delimiter.charAt(0)) :
+ new TransformationIterator<String, String>(strings, new StringStringDelimiter(delimiter));
+ }
+
+ private static class StringStringDelimiter implements Transformer<String, String> {
+ 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<char[]> quoteCharArrays(Iterator<char[]> 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<char[]> delimitCharArrays(Iterable<char[]> strings, char delimiter) {
+ return new TransformationIterable<char[], char[]>(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<char[]> delimitCharArrays(Iterator<char[]> strings, char delimiter) {
+ return new TransformationIterator<char[], char[]>(strings, new CharCharArrayDelimiter(delimiter));
+ }
+
+ private static class CharCharArrayDelimiter implements Transformer<char[], char[]> {
+ 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<char[]> delimitCharArrays(Iterable<char[]> strings, char[] delimiter) {
+ return new TransformationIterable<char[], char[]>(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<char[]> delimitCharArrays(Iterator<char[]> strings, char[] delimiter) {
+ return new TransformationIterator<char[], char[]>(strings, new CharArrayCharArrayDelimiter(delimiter));
+ }
+
+ private static class CharArrayCharArrayDelimiter implements Transformer<char[], char[]> {
+ 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: "<foo>".
+ */
+ 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: "<foo>".
+ */
+ 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<String> capitalize(Iterable<String> strings) {
+ return new TransformationIterable<String, String>(strings, STRING_CAPITALIZER);
+ }
+
+ /**
+ * Modify each of the specified strings, capitalizing the first letter of
+ * each.
+ */
+ public static Iterator<String> capitalize(Iterator<String> strings) {
+ return new TransformationIterator<String, String>(strings, STRING_CAPITALIZER);
+ }
+
+ private static final Transformer<String, String> STRING_CAPITALIZER = new Transformer<String, String>() {
+ 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<char[]> capitalizeCharArrays(Iterable<char[]> strings) {
+ return new TransformationIterable<char[], char[]>(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<char[]> capitalizeCharArrays(Iterator<char[]> strings) {
+ return new TransformationIterator<char[], char[]>(strings, CHAR_ARRAY_CAPITALIZER);
+ }
+
+ private static final Transformer<char[], char[]> CHAR_ARRAY_CAPITALIZER = new Transformer<char[], char[]>() {
+ 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:<pre>
+ * ClassName[00-F3-EE-42](add'l info)
+ * </pre>
+ */
+ 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:<pre>
+ * ClassName[00-F3-EE-42]
+ * </pre>
+ */
+ 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:<pre>
+ * ClassName[00-F3-EE-42]
+ * </pre>
+ */
+ 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:<pre>
+ * ClassName[00-F3-EE-42]
+ * </pre>
+ */
+ 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:<pre>
+ * ["foo", "bar", "baz"]
+ * </pre>
+ */
+ public static <T> String append(StringBuilder sb, T[] array) {
+ return append(sb, new ArrayListIterator<T>(array));
+ }
+
+ /**
+ * Append the string representations of the objects in the specified iterable
+ * to the specified string builder:<pre>
+ * ["foo", "bar", "baz"]
+ * </pre>
+ */
+ public static <T> String append(StringBuilder sb, Iterable<T> iterable) {
+ return append(sb, iterable.iterator());
+ }
+
+ /**
+ * Append the string representations of the objects in the specified iterator
+ * to the specified string builder:<pre>
+ * ["foo", "bar", "baz"]
+ * </pre>
+ */
+ public static <T> String append(StringBuilder sb, Iterator<T> 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 <code>null</code>s.
+ */
+ public static boolean stringsAreEqual(String s1, String s2) {
+ return Tools.valuesAreEqual(s1, s2);
+ }
+
+ /**
+ * Return whether the specified strings are equal.
+ * Check for <code>null</code>s.
+ */
+ public static boolean stringsAreEqual(char[] s1, char[] s2) {
+ return (s1 == null) ?
+ (s2 == null) :
+ ((s2 != null) && stringsAreEqual_(s1, s2));
+ }
+
+ /**
+ * no <code>null</code> 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 <code>null</code>s.
+ */
+ 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 <code>null</code>s.
+ */
+ public static boolean stringsAreEqualIgnoreCase(char[] s1, char[] s2) {
+ return (s1 == null) ?
+ (s2 == null) :
+ ((s2 != null) && stringsAreEqualIgnoreCase_(s1, s2));
+ }
+
+ /**
+ * no <code>null</code> 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<String> convertToJavaStringLiterals(Iterable<String> strings) {
+ return new TransformationIterable<String, String>(strings, STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER);
+ }
+
+ public static Iterator<String> convertToJavaStringLiterals(Iterator<String> strings) {
+ return new TransformationIterator<String, String>(strings, STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER);
+ }
+
+ private static final Transformer<String, String> STRING_TO_JAVA_STRING_LITERAL_TRANSFORMER = new Transformer<String, String>() {
+ public String transform(String string) {
+ return StringTools.convertToJavaStringLiteral(string);
+ }
+ };
+
+ // cannot name method simply 'convertToJavaStringLiterals' because of type-erasure...
+ public static Iterable<char[]> convertToJavaCharArrayLiterals(Iterable<char[]> strings) {
+ return new TransformationIterable<char[], char[]>(strings, CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER);
+ }
+
+ // cannot name method simply 'convertToJavaStringLiterals' because of type-erasure...
+ public static Iterator<char[]> convertToJavaCharArrayLiterals(Iterator<char[]> strings) {
+ return new TransformationIterator<char[], char[]>(strings, CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER);
+ }
+
+ private static final Transformer<char[], char[]> CHAR_ARRAY_TO_JAVA_STRING_LITERAL_TRANSFORMER = new Transformer<char[], char[]>() {
+ 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<E>
+ implements Bag<E>, Serializable
+{
+ /** Backing bag. */
+ private final Bag<E> 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<E> 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<E> 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<E>(), mutex);
+ }
+
+ /**
+ * Construct a synchronized bag that locks on itself.
+ */
+ public SynchronizedBag() {
+ this(new HashBag<E>());
+ }
+
+
+ // ********** 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<Bag.Entry<E>> 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<E> 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<? extends E> 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<E> 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> 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 <code>boolean</code> value.
+ * It also provides protocol for suspending a thread until the
+ * <code>boolean</code> value is set to <code>true</code> or <code>false</code>,
+ * with optional time-outs.
+ *
+ * @see SimpleBooleanReference
+ */
+public class SynchronizedBoolean
+ implements BooleanReference, Cloneable, Serializable
+{
+ /** Backing <code>boolean</code>. */
+ private boolean value;
+
+ /** Object to synchronize on. */
+ private final Object mutex;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructors **********
+
+ /**
+ * Create a synchronized <code>boolean</code> with the specified
+ * initial value and mutex.
+ */
+ public SynchronizedBoolean(boolean value, Object mutex) {
+ super();
+ this.value = value;
+ this.mutex = mutex;
+ }
+
+ /**
+ * Create a synchronized <code>boolean</code> with the
+ * specified initial value.
+ * The synchronized <code>boolean</code> itself will be the mutex.
+ */
+ public SynchronizedBoolean(boolean value) {
+ super();
+ this.value = value;
+ this.mutex = this;
+ }
+
+ /**
+ * Create a synchronized <code>boolean</code>
+ * with an initial value of <code>false</code>
+ * and specified mutex.
+ */
+ public SynchronizedBoolean(Object mutex) {
+ this(false, mutex);
+ }
+
+ /**
+ * Create a synchronized <code>boolean</code>
+ * with an initial value of <code>false</code>.
+ * The synchronized <code>boolean</code> 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 <code>boolean</code> value changes
+ * to the specified value. If the <code>boolean</code> 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 <code>boolean</code> value
+ * changes to the NOT of the specified value.
+ * If the <code>boolean</code> 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 <code>boolean</code> value
+ * changes to <code>true</code>.
+ * If the <code>boolean</code> value is already <code>true</code>,
+ * return immediately.
+ */
+ public void waitUntilTrue() throws InterruptedException {
+ this.waitUntilValueIs(true);
+ }
+
+ /**
+ * Suspend the current thread until the <code>boolean</code> value
+ * changes to <code>false</code>.
+ * If the <code>boolean</code> value is already <code>false</code>,
+ * return immediately.
+ */
+ public void waitUntilFalse() throws InterruptedException {
+ this.waitUntilValueIs(false);
+ }
+
+ /**
+ * Suspend the current thread until the <code>boolean</code> value changes to
+ * <em>not</em> the specified value, then change it back to the specified
+ * value and continue executing. If the <code>boolean</code> value is already
+ * <em>not</em> 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 <code>boolean</code> value
+ * changes to <code>false</code>,
+ * then change it back to <code>true</code> and continue executing.
+ * If the <code>boolean</code> value is already <code>false</code>,
+ * set the value to <code>true</code> immediately.
+ */
+ public void waitToSetTrue() throws InterruptedException {
+ this.waitToSetValue(true);
+ }
+
+ /**
+ * Suspend the current thread until the <code>boolean</code> value
+ * changes to <code>true</code>,
+ * then change it back to <code>false</code> and continue executing.
+ * If the <code>boolean</code> value is already <code>true</code>,
+ * set the value to <code>false</code> immediately.
+ */
+ public void waitToSetFalse() throws InterruptedException {
+ this.waitToSetValue(false);
+ }
+
+
+ // ********** timed waits **********
+
+ /**
+ * Suspend the current thread until the <code>boolean</code> value changes
+ * to the specified value or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code> if
+ * the specified value was achieved;
+ * return <code>false</code> if a time-out occurred.
+ * If the <code>boolean</code> value is already the specified value,
+ * return <code>true</code> 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 <code>boolean</code> value
+ * changes to the NOT of the specified value or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code> if
+ * the NOT of the specified value was achieved;
+ * return <code>false</code> if a time-out occurred.
+ * If the <code>boolean</code> 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 <code>boolean</code> value changes
+ * to <code>true</code> or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code> if
+ * <code>true</code> was achieved;
+ * return <code>false</code> if a time-out occurred.
+ * If the <code>boolean</code> value is already <code>true</code>,
+ * return <code>true</code> 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 <code>boolean</code> value changes
+ * to <code>false</code> or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code> if
+ * <code>false</code> was achieved;
+ * return <code>false</code> if a time-out occurred.
+ * If the <code>boolean</code> value is already <code>true</code>,
+ * return <code>true</code> 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 <code>boolean</code> value changes
+ * to <em>not</em> the specified value, then change it back to the specified
+ * value and continue executing. If the <code>boolean</code> value does not
+ * change to <code>false</code> before the time-out, simply continue
+ * executing without changing the value.
+ * The time-out is specified in milliseconds. Return <code>true</code>
+ * if the value was set to the specified value; return <code>false</code>
+ * if a time-out occurred. If the <code>boolean</code> value is already
+ * <em>not</em> the specified value, set the value to the specified value
+ * immediately and return <code>true</code>.
+ * 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 <code>boolean</code> value changes
+ * to <code>false</code>, then change it back to <code>true</code> and
+ * continue executing. If the <code>boolean</code> value does not change to
+ * <code>false</code> before the time-out, simply continue executing without
+ * changing the value. The time-out is specified in milliseconds. Return
+ * <code>true</code> if the value was set to <code>true</code>;
+ * return <code>false</code> if a time-out occurred. If the
+ * <code>boolean</code> value is already <code>false</code>, set the
+ * value to <code>true</code> immediately and return <code>true</code>.
+ * 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 <code>boolean</code> value changes
+ * to <code>true</code>, then change it back to <code>false</code> and
+ * continue executing. If the <code>boolean</code> value does not change to
+ * <code>true</code> before the time-out, simply continue executing without
+ * changing the value. The time-out is specified in milliseconds. Return
+ * <code>true</code> if the value was set to <code>false</code>;
+ * return <code>false</code> if a time-out occurred. If the
+ * <code>boolean</code> value is already <code>true</code>, set the
+ * value to <code>false</code> immediately and return <code>true</code>.
+ * 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 <code>int</code>.
+ * 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 <code>int</code>. */
+ 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 <em>not</em> 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 <em>not</em> 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 <em>not</em> 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 <em>not</em> 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 <em>not</em> 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 <em>not</em> 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the specified value was removed; return <code>false</code> if a
+ * time-out occurred. If the value is already <em>not</em> the specified
+ * value, return <code>true</code> 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 <code>true</code>
+ * if the value is now zero; return <code>false</code>
+ * if a time-out occurred. If the value is already zero,
+ * return <code>true</code> 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 <code>true</code>
+ * if the value is now not zero; return <code>false</code>
+ * if a time-out occurred. If the value is already <em>not</em>
+ * zero, return <code>true</code> 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the value is now positive; return <code>false</code>
+ * if a time-out occurred. If the value is already positive,
+ * return <code>true</code> 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 <code>true</code>
+ * if the value is now not positive; return <code>false</code>
+ * if a time-out occurred. If the value is already not positive,
+ * return <code>true</code> 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 <code>true</code>
+ * if the value is now negative; return <code>false</code>
+ * if a time-out occurred. If the value is already negative,
+ * return <code>true</code> 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 <code>true</code>
+ * if the value is now not negative; return <code>false</code>
+ * if a time-out occurred. If the value is already not negative,
+ * return <code>true</code> 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 <code>true</code>
+ * if the value was set to the specified value; return <code>false</code>
+ * if a time-out occurred.
+ * If the value is already something other than the specified value, set
+ * the value immediately and return <code>true</code>.
+ * 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 <code>true</code>
+ * if the value was set to zero; return <code>false</code>
+ * if a time-out occurred.
+ * If the value is already something other than zero, set
+ * the value to zero immediately and return <code>true</code>.
+ * 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 <code>true</code>
+ * if the value was set to the specified new value; return
+ * <code>false</code> 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 <code>V</code>.
+ * It also provides protocol for suspending a thread until the
+ * value is set to <code>null</code> or a non-<code>null</code> value,
+ * with optional time-outs.
+ *
+ * @parm V the type of the synchronized object's value
+ * @see SimpleObjectReference
+ */
+public class SynchronizedObject<V>
+ implements ObjectReference<V>, 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 <code>null</code>.
+ * 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 <code>null</code>. 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 <em>not</em> 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 <code>null</code>.
+ * If the value is already <code>null</code>, return immediately.
+ */
+ public void waitUntilNull() throws InterruptedException {
+ this.waitUntilValueIs(null);
+ }
+
+ /**
+ * Suspend the current thread until the value changes
+ * to something other than <code>null</code>.
+ * If the value is already <em>not</em> <code>null</code>,
+ * 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 <em>not</em> 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 <code>null</code>, then change it
+ * back to <code>null</code> and continue executing.
+ * If the value is already <em>not</em> <code>null</code>,
+ * set the value to <code>null</code> 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 <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * 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 <code>true</code>
+ * if the specified value was removed; return <code>false</code> if a
+ * time-out occurred. If the value is already <em>not</em> the specified
+ * value, return <code>true</code> 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 <code>null</code> or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * if a time-out occurred. If the value is already <code>null</code>,
+ * return <code>true</code> 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 <code>null</code> or the specified time-out occurs.
+ * The time-out is specified in milliseconds. Return <code>true</code>
+ * if the specified value was achieved; return <code>false</code>
+ * if a time-out occurred. If the value is already <em>not</em>
+ * <code>null</code>, return <code>true</code> 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 <code>true</code>
+ * if the value was set to the specified value; return <code>false</code>
+ * if a time-out occurred.
+ * If the value is already something other than the specified value, set
+ * the value immediately and return <code>true</code>.
+ * 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 <code>null</code>, then change it back to <code>null</code>
+ * and continue executing. If the value does not change to something
+ * other than <code>null</code> before the time-out, simply continue
+ * executing without changing the value.
+ * The time-out is specified in milliseconds. Return <code>true</code>
+ * if the value was set to <code>null</code>; return <code>false</code>
+ * if a time-out occurred.
+ * If the value is already something other than <code>null</code>, set
+ * the value to <code>null</code> immediately and return <code>true</code>.
+ * 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 <code>true</code>
+ * if the value was set to the specified new value; return
+ * <code>false</code> 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<V> clone() {
+ try {
+ synchronized (this.mutex) {
+ @SuppressWarnings("unchecked")
+ SynchronizedObject<V> clone = (SynchronizedObject<V>) 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<E>
+ implements Queue<E>, Serializable
+{
+ /** Backing queue. */
+ private final Queue<E> 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<E> 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<E> 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<E>(), mutex);
+ }
+
+ /**
+ * Construct an empty synchronized queue that locks on itself.
+ */
+ public SynchronizedQueue() {
+ this(new SimpleQueue<E>());
+ }
+
+
+ // ********** 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 <code>true</code> if the specified
+ * empty status was achieved; return <code>false</code> if a time-out occurred.
+ * If the queue's empty status is already the specified value,
+ * return <code>true</code> 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 <code>true</code> if
+ * the queue is empty; return <code>false</code> if a time-out occurred.
+ * If the queue is already empty, return <code>true</code> 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 <code>true</code> if
+ * the queue is not empty; return <code>false</code> if a time-out occurred.
+ * If the queue already has something on it, return <code>true</code> 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 <code>true</code> if the
+ * item was enqueued; return <code>false</code> if a time-out occurred.
+ * If the queue is already empty, "enqueue" the specified item and
+ * return <code>true</code> 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<E> q) {
+ synchronized (this.mutex) {
+ this.drainTo_(q);
+ }
+ }
+
+ /**
+ * Pre-condition: synchronized
+ */
+ private void drainTo_(Queue<E> 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<E>
+ implements Stack<E>, Serializable
+{
+ /** Backing stack. */
+ private final Stack<E> 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<E> 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<E> 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<E>(), mutex);
+ }
+
+ /**
+ * Construct an empty synchronized stack that locks on itself.
+ */
+ public SynchronizedStack() {
+ this(new SimpleStack<E>());
+ }
+
+
+ // ********** 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 <code>true</code> if the specified
+ * empty status was achieved; return <code>false</code> if a time-out occurred.
+ * If the stack's empty status is already the specified value,
+ * return <code>true</code> 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 <code>true</code> if
+ * the stack is empty; return <code>false</code> if a time-out occurred.
+ * If the stack is already empty, return <code>true</code> 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 <code>true</code> if
+ * the stack is not empty; return <code>false</code> if a time-out occurred.
+ * If the stack already has something on it, return <code>true</code> 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 <code>true</code> if the
+ * item was pushed; return <code>false</code> if a time-out occurred.
+ * If the stack is already empty, "push" the specified item and
+ * return <code>true</code> 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<Command> 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<Command> buildThreadLocal() {
+ return new ThreadLocal<Command>();
+ }
+
+ 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<CommandExecutor> 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<CommandExecutor> buildThreadLocal() {
+ return new ThreadLocal<CommandExecutor>();
+ }
+
+ 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
+ * <code>null</code> 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
+ * <code>null</code> 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 <code>T1</code> to an object of type
+ * <code>T2</code>.
+ *
+ * @param <T1> the type of the object passed to the transformer
+ * @param <T2> the type of the object returned by the transformer
+ */
+public interface Transformer<T1, T2> {
+
+ /**
+ * 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<S1, S2> implements Transformer<S1, S2>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Transformer INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R1, R2> Transformer<R1, R2> 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<S1, S2> implements Transformer<S1, S2>, Serializable {
+ @SuppressWarnings("rawtypes")
+ public static final Transformer INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R1, R2> Transformer<R1, R2> 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": '/' => "&#x2f;"
+ */
+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. '/' => "&#x2f;").
+ */
+ 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 <code>EmptyEnumeration</code> is just that.
+ *
+ * @param <E> the type of elements returned by the enumeration
+ */
+public final class EmptyEnumeration<E>
+ implements Enumeration<E>
+{
+
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final EmptyEnumeration INSTANCE = new EmptyEnumeration();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Enumeration<T> 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 <code>IteratorEnumeration</code> wraps an
+ * {@link Iterator} so that it can be treated like an
+ * {@link Enumeration}.
+ * Hopefully we don't have much need for this....
+ *
+ * @param <E> the type of elements returned by the enumeration
+ */
+public class IteratorEnumeration<E>
+ implements Enumeration<E>
+{
+ private final Iterator<? extends E> iterator;
+
+ /**
+ * Construct an enumeration that wraps the specified iterable.
+ */
+ public IteratorEnumeration(Iterable<? extends E> iterable) {
+ this(iterable.iterator());
+ }
+
+ /**
+ * Construct an enumeration that wraps the specified iterator.
+ */
+ public IteratorEnumeration(Iterator<? extends E> 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 <code>ArrayIterable</code> provides an {@link Iterable}
+ * for an array of objects of type <code>E</code>.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see ArrayIterator
+ * @see ArrayListIterable
+ */
+public class ArrayIterable<E>
+ implements Iterable<E>
+{
+ 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<E> iterator() {
+ return new ArrayIterator<E>(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 <code>ArrayListIterable</code> provides a {@link ListIterable}
+ * for an array of objects of type <code>E</code>.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see ArrayIterable
+ * @see ArrayListIterator
+ */
+public class ArrayListIterable<E>
+ extends ArrayIterable<E>
+ implements ListIterable<E>
+{
+ /**
+ * 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<E> iterator() {
+ return new ArrayListIterator<E>(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 <code>ChainIterable</code> 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 <code>null</code> when it is passed the last
+ * link of the chain).
+ * To use, supply a starting link and supply a {@link ChainIterator.Linker} or
+ * subclass <code>ChainIterable</code> 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 <code>null</code>, the iterable will be empty.
+ * Note this iterable does not support <code>null</code> elements.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see ChainIterator
+ */
+public class ChainIterable<E>
+ implements Iterable<E>
+{
+ private final E startLink;
+ private final ChainIterator.Linker<E> 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<E> linker) {
+ super();
+ this.startLink = startLink;
+ this.linker = linker;
+ }
+
+ protected ChainIterator.Linker<E> buildDefaultLinker() {
+ return new DefaultLinker();
+ }
+
+ public Iterator<E> iterator() {
+ return new ChainIterator<E>(this.startLink, this.linker);
+ }
+
+ /**
+ * Return the next link in the chain; null if there are no more links.
+ * <p>
+ * 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<E> {
+ 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 <E> the type of elements returned by the iterable's iterator
+ *
+ * @see SnapshotCloneIterable
+ * @see LiveCloneIterable
+ */
+public abstract class CloneIterable<E>
+ implements Iterable<E>
+{
+ final CloneIterator.Remover<E> remover;
+
+
+ // ********** constructors **********
+
+ protected CloneIterable() {
+ super();
+ this.remover = this.buildDefaultRemover();
+ }
+
+ protected CloneIterable(CloneIterator.Remover<E> remover) {
+ super();
+ this.remover = remover;
+ }
+
+ protected CloneIterator.Remover<E> buildDefaultRemover() {
+ return new DefaultRemover();
+ }
+
+
+ // ********** default removal **********
+
+ /**
+ * Remove the specified element from the original collection.
+ * <p>
+ * 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<E> {
+ 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 <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see SnapshotCloneListIterable
+ * @see LiveCloneListIterable
+ */
+public abstract class CloneListIterable<E>
+ implements ListIterable<E>
+{
+ final CloneListIterator.Mutator<E> mutator;
+
+
+ // ********** constructors **********
+
+ protected CloneListIterable() {
+ super();
+ this.mutator = this.buildDefaultMutator();
+ }
+
+ protected CloneListIterable(CloneListIterator.Mutator<E> mutator) {
+ super();
+ this.mutator = mutator;
+ }
+
+ protected CloneListIterator.Mutator<E> buildDefaultMutator() {
+ return new DefaultMutator();
+ }
+
+
+ // ********** default mutations **********
+
+ /**
+ * At the specified index, add the specified element to the original list.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<E> {
+ 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 <code>CompositeIterable</code> wraps an {@link Iterable}
+ * of {@link Iterable}s and makes them appear to be a single
+ * {@link Iterable}.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see CompositeIterator
+ * @see CompositeListIterable
+ */
+public class CompositeIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends Iterable<? extends E>> iterables;
+
+
+ /**
+ * Construct an iterable with the specified collection of iterables.
+ */
+ public CompositeIterable(Iterable<? extends Iterable<? extends E>> iterables) {
+ super();
+ this.iterables = iterables;
+ }
+
+ /**
+ * Construct an iterable with the specified object prepended
+ * to the specified iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeIterable(E object, Iterable<? extends E> iterable) {
+ this(new SingleElementIterable<E>(object), iterable);
+ }
+
+ /**
+ * Construct an iterable with the specified object appended
+ * to the specified iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeIterable(Iterable<? extends E> iterable, E object) {
+ this(iterable, new SingleElementIterable<E>(object));
+ }
+
+ /**
+ * Construct an iterable with the specified iterables.
+ */
+ public CompositeIterable(Iterable<? extends E>... iterables) {
+ this(new ArrayIterable<Iterable<? extends E>>(iterables));
+ }
+
+ /**
+ * combined iterators
+ */
+ public Iterator<E> iterator() {
+ return new CompositeIterator<E>(this.iterators());
+ }
+
+ /**
+ * iterator of iterators
+ */
+ protected Iterator<? extends Iterator<? extends E>> iterators() {
+ return new TransformationIterator<Iterable<? extends E>, Iterator<? extends E>>(this.iterables()) {
+ @Override
+ protected Iterator<? extends E> transform(Iterable<? extends E> next) {
+ return next.iterator();
+ }
+ };
+ }
+
+ /**
+ * iterator of iterables
+ */
+ protected Iterator<? extends Iterable<? extends E>> 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 <code>CompositeListIterable</code> wraps a {@link ListIterable}
+ * of {@link ListIterable}s and makes them appear to be a single
+ * {@link ListIterable}.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see CompositeListIterator
+ * @see CompositeIterable
+ * @see ReadOnlyCompositeListIterable
+ */
+public class CompositeListIterable<E>
+ implements ListIterable<E>
+{
+ private final ListIterable<? extends ListIterable<E>> iterables;
+
+
+ /**
+ * Construct a list iterable with the specified list of list iterables.
+ */
+ public CompositeListIterable(List<ListIterable<E>> iterables) {
+ this(new ListListIterable<ListIterable<E>>(iterables));
+ }
+
+ /**
+ * Construct a list iterable with the specified list of list iterables.
+ */
+ public CompositeListIterable(ListIterable<? extends ListIterable<E>> iterables) {
+ super();
+ this.iterables = iterables;
+ }
+
+ /**
+ * Construct a list iterable with the specified object prepended
+ * to the specified list.
+ */
+ public CompositeListIterable(E object, List<E> list) {
+ this(object, new ListListIterable<E>(list));
+ }
+
+ /**
+ * Construct a list iterable with the specified object prepended
+ * to the specified list iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeListIterable(E object, ListIterable<E> iterable) {
+ this(new SingleElementListIterable<E>(object), iterable);
+ }
+
+ /**
+ * Construct a list iterable with the specified object appended
+ * to the specified list.
+ */
+ public CompositeListIterable(List<E> list, E object) {
+ this(new ListListIterable<E>(list), object);
+ }
+
+ /**
+ * Construct a list iterable with the specified object appended
+ * to the specified list iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeListIterable(ListIterable<E> iterable, E object) {
+ this(iterable, new SingleElementListIterable<E>(object));
+ }
+
+ /**
+ * Construct a list iterable with the specified list iterables.
+ */
+ public CompositeListIterable(ListIterable<E>... iterables) {
+ this(new ArrayListIterable<ListIterable<E>>(iterables));
+ }
+
+ /**
+ * Construct a list iterable with the specified lists.
+ */
+ public CompositeListIterable(List<E>... lists) {
+ this(new TransformationListIterable<List<E>, ListIterable<E>>(new ArrayListIterable<List<E>>(lists)) {
+ @Override
+ protected ListIterable<E> transform(List<E> list) {
+ return new ListListIterable<E>(list);
+ }
+ });
+ }
+
+ /**
+ * combined list iterators
+ */
+ public ListIterator<E> iterator() {
+ return new CompositeListIterator<E>(this.iterators());
+ }
+
+ /**
+ * list iterator of list iterators
+ */
+ protected ListIterator<? extends ListIterator<E>> iterators() {
+ return new TransformationListIterator<ListIterable<E>, ListIterator<E>>(this.iterables()) {
+ @Override
+ protected ListIterator<E> transform(ListIterable<E> next) {
+ return next.iterator();
+ }
+ };
+ }
+
+ /**
+ * list iterator of list iterables
+ */
+ protected ListIterator<? extends ListIterable<E>> 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 <code>EmptyIterable</code> 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 <E> the type of elements returned by the iterable's iterator
+ *
+ * @see EmptyIterator
+ * @see EmptyListIterable
+ */
+public final class EmptyIterable<E>
+ implements Iterable<E>, Serializable
+{
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final Iterable INSTANCE = new EmptyIterable();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Iterable<T> instance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Ensure single instance.
+ */
+ private EmptyIterable() {
+ super();
+ }
+
+ public Iterator<E> 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 <code>EmptyListIterable</code> 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 <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see EmptyListIterator
+ * @see EmptyIterable
+ */
+public final class EmptyListIterable<E>
+ implements ListIterable<E>, Serializable
+{
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final ListIterable INSTANCE = new EmptyListIterable();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> ListIterable<T> instance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Ensure single instance.
+ */
+ private EmptyListIterable() {
+ super();
+ }
+
+ public ListIterator<E> 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 <code>FilteringIterable</code> 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.
+ * <p>
+ * As an alternative to building a {@link Filter}, a subclass
+ * of <code>FilteringIterable</code> can override the
+ * {@link #accept(Object)} method.
+ *
+ * @param <E> the type of elements to be filtered
+ *
+ * @see FilteringIterator
+ */
+public class FilteringIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends E> iterable;
+ private final Filter<E> 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<? extends E> iterable) {
+ super();
+ this.iterable = iterable;
+ this.filter = this.buildDefaultFilter();
+ }
+
+ /**
+ * Construct an iterable with the specified nested
+ * iterable and filter.
+ */
+ public FilteringIterable(Iterable<? extends E> iterable, Filter<E> filter) {
+ super();
+ this.iterable = iterable;
+ this.filter = filter;
+ }
+
+ protected Filter<E> buildDefaultFilter() {
+ return new DefaultFilter();
+ }
+
+ public Iterator<E> iterator() {
+ return new FilteringIterator<E>(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.
+ * <p>
+ * 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<E> {
+ 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 <code>GraphIterable</code> 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 <code>GraphIterable</code> 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 <em>neighbors</em>), but does not provide a method for
+ * getting <em>all</em> 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.)
+ * <p>
+ * 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.
+ * <p>
+ * It is up to the user of this class to ensure a <em>complete</em> graph.
+ * <p>
+ * To use, supply:<ul>
+ * <li> either the initial node of the graph or an {@link Iterable}
+ * of the initial collection of graph nodes
+ * <li> a {@link GraphIterator.MisterRogers} that tells who the neighbors are
+ * of each node
+ * (alternatively, subclass <code>GraphIterable</code>
+ * and override the {@link #neighbors(Object)} method)
+ * </ul>
+ * The {@link Iterator#remove()} operation is not supported. This behavior, if
+ * desired, must be implemented by the user of this class.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see GraphIterator
+ */
+public class GraphIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends E> roots;
+ private final GraphIterator.MisterRogers<E> 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<E>(root));
+ }
+
+ /**
+ * Construct an iterable containing the nodes of a graph
+ * with the specified root and Mr. Rogers.
+ */
+ public GraphIterable(E root, GraphIterator.MisterRogers<E> misterRogers) {
+ this(new SingleElementIterable<E>(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<E> 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<? extends E> 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<? extends E> roots, GraphIterator.MisterRogers<E> misterRogers) {
+ super();
+ this.roots = roots;
+ this.misterRogers = misterRogers;
+ }
+
+ protected GraphIterator.MisterRogers<E> buildDefaultMisterRogers() {
+ return new DefaultMisterRogers();
+ }
+
+ public Iterator<E> iterator() {
+ return new GraphIterator<E>(this.roots, this.misterRogers);
+ }
+
+ /**
+ * Return the immediate neighbors of the specified object.
+ * <p>
+ * This method can be overridden by a subclass as an
+ * alternative to building a {@link GraphIterator.MisterRogers}.
+ */
+ protected Iterator<? extends E> 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<E> {
+ public Iterator<? extends E> 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 <code>ListIterable</code> simply extends {@link Iterable}
+ * to return a {@link ListIterator} of type <code>E</code>.
+ *
+ * @param <E> the type of elements returned by the iterable's iterators
+ */
+public interface ListIterable<E>
+ extends Iterable<E>
+{
+ /**
+ * Return a list iterator over a set of elements of type E.
+ */
+ ListIterator<E> 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 <code>ListListIterable</code> adapts a {@link List}
+ * to the {@link ListIterable} interface.
+ *
+ * @param <E> the type of elements returned by the iterable's iterators
+ */
+public class ListListIterable<E>
+ implements ListIterable<E>
+{
+ private final List<E> list;
+
+ public ListListIterable(List<E> list) {
+ super();
+ this.list = list;
+ }
+
+ public ListIterator<E> 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 <code>LiveCloneIterable</code> 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").
+ * <p>
+ * The original collection passed to the <code>LiveCloneIterable</code>'s
+ * constructor should be thread-safe (e.g. {@link java.util.Vector});
+ * otherwise you run the risk of a corrupted collection.
+ * <p>
+ * By default, the iterator returned by a <code>LiveCloneIterable</code> does not
+ * support the {@link Iterator#remove()} operation; this is because it does not
+ * have access to the original collection. But if the <code>LiveCloneIterable</code>
+ * is supplied with an {@link CloneIterator.Remover} it will delegate the
+ * {@link Iterator#remove()} operation to the <code>Remover</code>.
+ * Alternatively, a subclass can override the iterable's {@link #remove(Object)}
+ * method.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see CloneIterator
+ * @see SnapshotCloneIterable
+ * @see LiveCloneListIterable
+ */
+public class LiveCloneIterable<E>
+ extends CloneIterable<E>
+{
+ private final Collection<? extends E> 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<? extends E> 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<? extends E> collection, CloneIterator.Remover<E> remover) {
+ super(remover);
+ this.collection = collection;
+ }
+
+
+ // ********** Iterable implementation **********
+
+ public Iterator<E> iterator() {
+ return new CloneIterator<E>(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 <code>LiveCloneListIterable</code> 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").
+ * <p>
+ * The original list passed to the <code>LiveCloneListIterable</code>'s
+ * constructor should be thread-safe (e.g. {@link java.util.Vector});
+ * otherwise you run the risk of a corrupted list.
+ * <p>
+ * By default, the list iterator returned by a <code>LiveCloneListIterable</code>
+ * does not support the modify operations; this is because it does not
+ * have access to the original list. But if the <code>LiveCloneListIterable</code>
+ * is supplied with an {@link CloneListIterator.Mutator} it will delegate the
+ * modify operations to the <code>Mutator</code>.
+ * Alternatively, a subclass can override the list iterable's mutation
+ * methods.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see CloneListIterator
+ * @see SnapshotCloneListIterable
+ * @see LiveCloneIterable
+ */
+public class LiveCloneListIterable<E>
+ extends CloneListIterable<E>
+{
+ private final List<? extends E> 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<? extends E> 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<? extends E> list, CloneListIterator.Mutator<E> mutator) {
+ super(mutator);
+ this.list = list;
+ }
+
+
+ // ********** ListIterable implementation **********
+
+ public ListIterator<E> iterator() {
+ return new CloneListIterator<E>(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 <code>PeekableIterable</code> 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()}.
+ * <p>
+ * One, possibly undesirable, side-effect of using this iterator is that
+ * the nested iterator's <code>next()</code> method will be invoked
+ * <em>before</em> 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 <E> the type of elements returned by the iterable's iterator
+ *
+ * @see PeekableIterator
+ */
+public class PeekableIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends E> iterable;
+
+ /**
+ * Construct a peekable iterable that wraps the specified
+ * iterable.
+ */
+ public PeekableIterable(Iterable<? extends E> iterable) {
+ super();
+ this.iterable = iterable;
+ }
+
+ public PeekableIterator<E> iterator() {
+ return new PeekableIterator<E>(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 <code>QueueIterable</code> provides an {@link Iterable}
+ * for a {@link Queue} of objects of type <code>E</code>. The queue's elements
+ * are {@link Queue#dequeue() dequeue}d" as the iterable's iterator returns
+ * them with calls to {@link Iterator#next()}.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see Queue
+ * @see QueueIterator
+ */
+public class QueueIterable<E>
+ implements Iterable<E>
+{
+ private final Queue<E> queue;
+
+ /**
+ * Construct an iterable for the specified queue.
+ */
+ public QueueIterable(Queue<E> queue) {
+ super();
+ this.queue = queue;
+ }
+
+ public Iterator<E> iterator() {
+ return new QueueIterator<E>(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 <code>ReadOnlyCompositeListIterable</code> 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 <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see ReadOnlyCompositeListIterator
+ * @see CompositeListIterable
+ */
+public class ReadOnlyCompositeListIterable<E>
+ implements ListIterable<E>
+{
+ private final ListIterable<? extends ListIterable<? extends E>> iterables;
+
+
+ /**
+ * Construct a list iterable with the specified list of list iterables.
+ */
+ public ReadOnlyCompositeListIterable(ListIterable<? extends ListIterable<? extends E>> 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<? extends E> iterable) {
+ this(new SingleElementListIterable<E>(object), iterable);
+ }
+
+ /**
+ * Construct a list iterable with the specified object appended
+ * to the specified list iterable.
+ */
+ @SuppressWarnings("unchecked")
+ public ReadOnlyCompositeListIterable(ListIterable<? extends E> iterable, E object) {
+ this(iterable, new SingleElementListIterable<E>(object));
+ }
+
+ /**
+ * Construct a list iterable with the specified list iterables.
+ */
+ public ReadOnlyCompositeListIterable(ListIterable<? extends E>... iterables) {
+ this(new ArrayListIterable<ListIterable<? extends E>>(iterables));
+ }
+
+ /**
+ * combined list iterators
+ */
+ public ListIterator<E> iterator() {
+ return new ReadOnlyCompositeListIterator<E>(this.iterators());
+ }
+
+ /**
+ * list iterator of list iterators
+ */
+ protected ListIterator<? extends ListIterator<? extends E>> iterators() {
+ return new TransformationListIterator<ListIterable<? extends E>, ListIterator<? extends E>>(this.iterables()) {
+ @Override
+ protected ListIterator<? extends E> transform(ListIterable<? extends E> next) {
+ return next.iterator();
+ }
+ };
+ }
+
+ /**
+ * list iterator of list iterables
+ */
+ protected ListIterator<? extends ListIterable<? extends E>> 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 <code>ReadOnlyIterable</code> wraps another {@link Iterable}
+ * and returns a read-only {@link Iterator}.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see ReadOnlyIterator
+ * @see ReadOnlyListIterable
+ */
+public class ReadOnlyIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends E> iterable;
+
+
+ /**
+ * Construct an iterable the returns a read-only iterator on the elements
+ * in the specified iterable.
+ */
+ public ReadOnlyIterable(Iterable<? extends E> iterable) {
+ super();
+ this.iterable = iterable;
+ }
+
+ public Iterator<E> iterator() {
+ return new ReadOnlyIterator<E>(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 <code>ReadOnlyListIterable</code> wraps another {@link ListIterable}
+ * and returns a read-only {@link ListIterator}.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see ReadOnlyListIterator
+ * @see ReadOnlyIterable
+ */
+public class ReadOnlyListIterable<E>
+ implements ListIterable<E>
+{
+ private final ListIterable<? extends E> listIterable;
+
+
+ /**
+ * Construct a list iterable the returns a read-only list iterator on the elements
+ * in the specified list iterable.
+ */
+ public ReadOnlyListIterable(ListIterable<? extends E> iterable) {
+ super();
+ this.listIterable = iterable;
+ }
+
+ public ListIterator<E> iterator() {
+ return new ReadOnlyListIterator<E>(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 <code>SingleElementIterable</code> 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 <code>false</code> to any subsequent
+ * call to {@link Iterator#hasNext()}.
+ * <p>
+ * A <code>SingleElementIterable</code> is equivalent to the
+ * {@link Iterable} returned by:
+ * {@link java.util.Collections#singleton(Object)}.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see SingleElementIterator
+ * @see SingleElementListIterable
+ */
+public class SingleElementIterable<E>
+ implements Iterable<E>
+{
+ private final E element;
+
+ /**
+ * Construct an iterable that contains only the specified element.
+ */
+ public SingleElementIterable(E element) {
+ super();
+ this.element = element;
+ }
+
+ public Iterator<E> iterator() {
+ return new SingleElementIterator<E>(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 <code>SingleElementListIterable</code> 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 <code>false</code> to any subsequent
+ * call to {@link ListIterator#hasNext()}. Likewise, it will return <code>false</code>
+ * 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.
+ * <p>
+ * A <code>SingleElementListIterable</code> is equivalent to the
+ * {@link Iterable} returned by:
+ * {@link java.util.Collections#singletonList(Object)}.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see SingleElementListIterator
+ * @see SingleElementIterable
+ */
+public class SingleElementListIterable<E>
+ implements ListIterable<E>
+{
+ private final E element;
+
+ /**
+ * Construct a list iterable that contains only the specified element.
+ */
+ public SingleElementListIterable(E element) {
+ super();
+ this.element = element;
+ }
+
+ public ListIterator<E> iterator() {
+ return new SingleElementListIterator<E>(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 <code>SnapshotCloneIterable</code> 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()}.
+ * <p>
+ * The original collection passed to the <code>SnapshotCloneIterable</code>'s
+ * constructor should be thread-safe (e.g. {@link java.util.Vector});
+ * otherwise you run the risk of a corrupted collection.
+ * <p>
+ * By default, the iterator returned by a <code>SnapshotCloneIterable</code> does not
+ * support the {@link Iterator#remove()} operation; this is because it does not
+ * have access to the original collection. But if the <code>SnapshotCloneIterable</code>
+ * is supplied with a {@link CloneIterator.Remover} it will delegate the
+ * {@link Iterator#remove()} operation to the <code>Remover</code>.
+ * Alternatively, a subclass can override the iterable's {@link #remove(Object)}
+ * method.
+ * <p>
+ * This iterable is useful for multiple passes over a collection that should not
+ * be changed (e.g. by another thread) between passes.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see CloneIterator
+ * @see LiveCloneIterable
+ * @see SnapshotCloneListIterable
+ */
+public class SnapshotCloneIterable<E>
+ extends CloneIterable<E>
+{
+ 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<? extends E> 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<? extends E> iterator, CloneIterator.Remover<E> 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<? extends E> 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<? extends E> collection, CloneIterator.Remover<E> remover) {
+ super(remover);
+ this.array = collection.toArray();
+ }
+
+
+ // ********** Iterable implementation **********
+
+ public Iterator<E> iterator() {
+ return new LocalCloneIterator<E>(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<E> extends CloneIterator<E> {
+ protected LocalCloneIterator(Remover<E> 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 <code>SnapshotCloneListIterable</code> 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.
+ * <p>
+ * The original list passed to the <code>SnapshotCloneListIterable</code>'s
+ * constructor should be thread-safe (e.g. {@link java.util.Vector});
+ * otherwise you run the risk of a corrupted list.
+ * <p>
+ * By default, the list iterator returned by a <code>SnapshotCloneListIterable</code> does not
+ * support the {@link ListIterator} mutation operations; this is because it does not
+ * have access to the original list. But if the <code>SnapshotCloneListIterable</code>
+ * is supplied with a {@link CloneListIterator.Mutator} it will delegate the
+ * {@link ListIterator} mutation operations to the <code>Mutator</code>.
+ * Alternatively, a subclass can override the list iterable's mutation
+ * methods.
+ * <p>
+ * This list iterable is useful for multiple passes over a list that should not
+ * be changed (e.g. by another thread) between passes.
+ *
+ * @param <E> the type of elements returned by the list iterable's list iterator
+ *
+ * @see CloneListIterator
+ * @see LiveCloneListIterable
+ * @see SnapshotCloneIterable
+ */
+public class SnapshotCloneListIterable<E>
+ extends CloneListIterable<E>
+{
+ 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<? extends E> 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<? extends E> list, CloneListIterator.Mutator<E> mutator) {
+ super(mutator);
+ this.array = list.toArray();
+ }
+
+
+ // ********** ListIterable implementation **********
+
+ public ListIterator<E> iterator() {
+ return new LocalCloneListIterator<E>(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<E> extends CloneListIterator<E> {
+ protected LocalCloneListIterator(Mutator<E> 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 <code>StackIterable</code> provides an {@link Iterable}
+ * for a {@link Stack} of objects of type <code>E</code>. The stack's elements
+ * are {@link Stack#pop() "popped"} as the iterable's iterator returns
+ * them with calls to {@link Iterator#next()}.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see Stack
+ * @see StackIterator
+ */
+public class StackIterable<E>
+ implements Iterable<E>
+{
+ private final Stack<E> stack;
+
+ /**
+ * Construct an iterable for the specified stack.
+ */
+ public StackIterable(Stack<E> stack) {
+ super();
+ this.stack = stack;
+ }
+
+ public Iterator<E> iterator() {
+ return new StackIterator<E>(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 <code>E1</code>, converting it into an
+ * iterable of elements of type <code>E2</code>. <em>Assume</em> the wrapped
+ * iterable contains only elements of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements contained by the wrapped iterable
+ * @param <E2> output: the type of elements returned by the iterable's iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterators.SubIteratorWrapper
+ */
+public class SubIterableWrapper<E1, E2>
+ implements Iterable<E2>
+{
+ private final Iterable<E1> iterable;
+
+
+ public SubIterableWrapper(Iterable<E1> iterable) {
+ super();
+ this.iterable = iterable;
+ }
+
+ public Iterator<E2> iterator() {
+ return new SubIteratorWrapper<E1, E2>(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 <code>E1</code>, converting it into
+ * a list iterable of elements of type <code>E2</code>. Assume the wrapped
+ * iterable contains only elements of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements contained by the wrapped list iterable
+ * @param <E2> output: the type of elements returned by the iterable's list iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterators.SubListIteratorWrapper
+ */
+public class SubListIterableWrapper<E1, E2>
+ implements ListIterable<E2>
+{
+ private final ListIterable<E1> iterable;
+
+
+ public SubListIterableWrapper(List<E1> list) {
+ this(new ListListIterable<E1>(list));
+ }
+
+ public SubListIterableWrapper(ListIterable<E1> iterable) {
+ super();
+ this.iterable = iterable;
+ }
+
+ public ListIterator<E2> iterator() {
+ return new SubListIteratorWrapper<E1, E2>(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 <code>E</code>, converting it into an
+ * iterable of elements of type <code>E</code>. This shouldn't be a problem since there
+ * is no way to add invalid elements to the iterable.
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterators.SuperIteratorWrapper
+ */
+public class SuperIterableWrapper<E>
+ implements Iterable<E>
+{
+ private final Iterable<E> iterable;
+
+
+ @SuppressWarnings("unchecked")
+ public SuperIterableWrapper(Iterable<? extends E> iterable) {
+ super();
+ // this should be a safe cast - the iterator will only ever
+ // return E (or a sub-type) from #next()
+ this.iterable = (Iterable<E>) iterable;
+ }
+
+ public Iterator<E> 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 <code>E</code>,
+ * converting it into a list iterable of elements of type <code>E</code>.
+ * 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 <E> the type of elements returned by the iterable's iterators
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterators.SuperListIteratorWrapper
+ */
+public class SuperListIterableWrapper<E>
+ implements ListIterable<E>
+{
+ private final ListIterable<? extends E> iterable;
+
+
+ public <T extends E> SuperListIterableWrapper(List<T> list) {
+ this(new ListListIterable<T>(list));
+ }
+
+ public SuperListIterableWrapper(ListIterable<? extends E> iterable) {
+ super();
+ this.iterable = iterable;
+ }
+
+ public ListIterator<E> iterator() {
+ return new SuperListIteratorWrapper<E>(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 <code>TransformationIterable</code> wraps another {@link Iterable}
+ * and transforms its elements for client consumption. To use, supply a
+ * {@link Transformer} or subclass <code>TransformationIterable</code>
+ * and override the {@link #transform(Object)} method.
+ * Objects of type <code>E1</code> are transformed into objects of type <code>E2</code>;
+ * i.e. the iterable's iterator returns objects of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements to be transformed
+ * @param <E2> output: the type of elements returned by the iterable's iterator
+ *
+ * @see TransformationIterator
+ * @see TransformationListIterable
+ */
+public class TransformationIterable<E1, E2>
+ implements Iterable<E2>
+{
+ private final Iterable<? extends E1> iterable;
+ private final Transformer<E1, ? extends E2> 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<? extends E1> iterable) {
+ super();
+ this.iterable = iterable;
+ this.transformer = this.buildDefaultTransformer();
+ }
+
+ /**
+ * Construct an iterable with the specified nested iterable
+ * and transformer.
+ */
+ public TransformationIterable(Iterable<? extends E1> iterable, Transformer<E1, ? extends E2> transformer) {
+ super();
+ this.iterable = iterable;
+ this.transformer = transformer;
+ }
+
+ protected Transformer<E1, ? extends E2> buildDefaultTransformer() {
+ return new DefaultTransformer();
+ }
+
+ public Iterator<E2> iterator() {
+ return new TransformationIterator<E1, E2>(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<E1, E2> {
+ 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 <code>TransformationListIterable</code> wraps another {@link ListIterable}
+ * and transforms its elements for client consumption. To use, supply a
+ * {@link Transformer} or subclass <code>TransformationListIterable</code>
+ * and override the {@link #transform(Object)} method.
+ * Objects of type <code>E1</code> are transformed into objects of type <code>E2</code>;
+ * i.e. the list iterable's list iterator returns objects of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements to be transformed
+ * @param <E2> output: the type of elements returned by the iterable's iterator
+ *
+ * @see TransformationListIterator
+ * @see TransformationIterable
+ */
+public class TransformationListIterable<E1, E2>
+ implements ListIterable<E2>
+{
+ private final ListIterable<? extends E1> iterable;
+ private final Transformer<E1, ? extends E2> 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<E1> list) {
+ this(new ListListIterable<E1>(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<? extends E1> iterable) {
+ super();
+ this.iterable = iterable;
+ this.transformer = this.buildDefaultTransformer();
+ }
+
+ /**
+ * Construct a list iterable with the specified nested list
+ * and transformer.
+ */
+ public TransformationListIterable(List<E1> list, Transformer<E1, ? extends E2> transformer) {
+ this(new ListListIterable<E1>(list), transformer);
+ }
+
+ /**
+ * Construct a list iterable with the specified nested list iterable
+ * and transformer.
+ */
+ public TransformationListIterable(ListIterable<? extends E1> iterable, Transformer<E1, ? extends E2> transformer) {
+ super();
+ this.iterable = iterable;
+ this.transformer = transformer;
+ }
+
+ protected Transformer<E1, ? extends E2> buildDefaultTransformer() {
+ return new DefaultTransformer();
+ }
+
+ public ListIterator<E2> iterator() {
+ return new TransformationListIterator<E1, E2>(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<E1, E2> {
+ 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 <code>TreeIterable</code> 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.
+ * <p>
+ * To use, supply:<ul>
+ * <li> either the root element of the tree or, if the tree has
+ * multiple roots, an {@link Iterable} of the set of roots
+ * <li> a {@link TreeIterator.Midwife} that delivers the children of each child
+ * (alternatively, subclass <code>TreeIterable</code>
+ * and override the {@link #children(Object)} method)
+ * </ul>
+ *
+ * @param <E> the type of elements returned by the iterable's iterator
+ *
+ * @see TreeIterator
+ */
+public class TreeIterable<E>
+ implements Iterable<E>
+{
+ private final Iterable<? extends E> roots;
+ private final TreeIterator.Midwife<E> 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<E>(root));
+ }
+
+ /**
+ * Construct an iterable containing the nodes of a tree with the specified root
+ * and midwife.
+ */
+ public TreeIterable(E root, TreeIterator.Midwife<E> midwife) {
+ this(new SingleElementIterable<E>(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<E> 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<? extends E> 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<? extends E> roots, TreeIterator.Midwife<E> midwife) {
+ super();
+ this.roots = roots;
+ this.midwife = midwife;
+ }
+
+ protected TreeIterator.Midwife<E> buildDefaultMidwife() {
+ return new DefaultMidwife();
+ }
+
+ public Iterator<E> iterator() {
+ return new TreeIterator<E>(this.roots, this.midwife);
+ }
+
+ /**
+ * Return the immediate children of the specified object.
+ * <p>
+ * This method can be overridden by a subclass as an
+ * alternative to building a {@link TreeIterator.Midwife}.
+ */
+ protected Iterator<? extends E> 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<E> {
+ public Iterator<? extends E> 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 <code>ArrayIterator</code> provides an {@link Iterator}
+ * for an array of objects of type <code>E</code>.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ArrayIterable
+ */
+public class ArrayIterator<E>
+ implements Iterator<E>
+{
+ 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 <code>ArrayListIterator</code> provides a {@link ListIterator}
+ * for an array of objects.
+ * <p>
+ * The name might be a bit confusing:
+ * This is a {@link ListIterator} for an <code>Array</code>;
+ * <em>not</em> an {@link java.util.Iterator Iterator} for an
+ * {@link java.util.ArrayList ArrayList}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ArrayListIterable
+ */
+public class ArrayListIterator<E>
+ extends ArrayIterator<E>
+ implements ListIterator<E>
+{
+ 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 <code>ChainIterator</code> 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 <code>null</code> when it is passed the last
+ * link of the chain).
+ * To use, supply a starting link and supply a {@link Linker} or
+ * subclass <code>ChainIterator</code> and override the
+ * {@link #nextLink(Object)} method.
+ * The starting link will be the first object returned by the iterator.
+ * If the starting link is <code>null</code>, the iterator will be empty.
+ * Note this iterator does not support <code>null</code> elements.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ChainIterable
+ */
+public class ChainIterator<E>
+ implements Iterator<E>
+{
+ private E nextLink;
+ private final Linker<E> 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.<E>instance());
+ }
+
+ /**
+ * Construct an iterator with the specified starting link
+ * and linker.
+ */
+ public ChainIterator(E startLink, Linker<E> 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<T> {
+
+ /**
+ * Return the next link in the chain; null if there are no more links.
+ */
+ T nextLink(T currentLink);
+
+
+ final class Null<S> implements Linker<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Linker INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R> Linker<R> 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<S> implements Linker<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Linker INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> Linker<R> 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 <code>CloneIterator</code> iterates over a copy of a collection,
+ * allowing for concurrent access to the original collection.
+ * <p>
+ * The original collection passed to the <code>CloneIterator</code>'s
+ * constructor should be synchronized (e.g. {@link java.util.Vector});
+ * otherwise you run the risk of a corrupted collection.
+ * <p>
+ * By default, a <code>CloneIterator</code> does not support the
+ * {@link #remove()} operation; this is because it does not have
+ * access to the original collection. But if the <code>CloneIterator</code>
+ * 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 <E> 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<E>
+ implements Iterator<E>
+{
+ private final Iterator<Object> iterator;
+ private E current;
+ private final Remover<E> 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<? extends E> collection) {
+ this(collection, Remover.ReadOnly.<E>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.<E>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<? extends E> collection, Remover<E> 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<E> 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<E> remover, Object... array) {
+ super();
+ this.iterator = new ArrayIterator<Object>(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 <code>E</code>,
+ * 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 (<code>Object[]</code>).
+ */
+ @SuppressWarnings("unchecked")
+ protected E nestedNext() {
+ return (E) this.iterator.next();
+ }
+
+ /**
+ * Remove the specified element from the original collection.
+ * <p>
+ * 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<T> {
+
+ /**
+ * Remove the specified object from the original collection.
+ */
+ void remove(T element);
+
+
+ final class ReadOnly<S> implements Remover<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Remover INSTANCE = new ReadOnly();
+ @SuppressWarnings("unchecked")
+ public static <R> Remover<R> 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 <code>CloneListIterator</code> iterates over a copy of a list,
+ * allowing for concurrent access to the original list.
+ * <p>
+ * The original list passed to the <code>CloneListIterator</code>'s
+ * constructor should be synchronized; otherwise you run the risk of
+ * a corrupted list (e.g. {@link java.util.Vector}.
+ * <p>
+ * By default, a <code>CloneListIterator</code> does not support the
+ * modification operations; this is because it does not have
+ * access to the original list. But if the <code>CloneListIterator</code>
+ * 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 <E> 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<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<Object> listIterator;
+ private int cursor;
+ private State state;
+ private final Mutator<E> 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<? extends E> list) {
+ this(list, Mutator.ReadOnly.<E>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.<E>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<? extends E> list, Mutator<E> 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<E> 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<E> 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 <code>E</code>,
+ * 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 (<code>Object[]</code>).
+ */
+ @SuppressWarnings("unchecked")
+ protected E nestedNext() {
+ return (E) this.listIterator.next();
+ }
+
+ /**
+ * The list passed in during construction held elements of type <code>E</code>,
+ * 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 (<code>Object[]</code>).
+ */
+ @SuppressWarnings("unchecked")
+ protected E nestedPrevious() {
+ return (E) this.listIterator.previous();
+ }
+
+ /**
+ * Add the specified element to the original list.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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<T> {
+
+ /**
+ * 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<S> implements Mutator<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Mutator INSTANCE = new ReadOnly();
+ @SuppressWarnings("unchecked")
+ public static <R> Mutator<R> 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 <code>CompositeIterator</code> wraps a collection
+ * of {@link Iterator}s and makes them appear to be a single
+ * {@link Iterator}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.CompositeIterable
+ */
+public class CompositeIterator<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends Iterator<? extends E>> iterators;
+ private Iterator<? extends E> currentIterator;
+ private Iterator<? extends E> lastIteratorToReturnNext;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct an iterator that returns all the elements held by the
+ * specified iterables.
+ */
+ public CompositeIterator(Iterable<? extends Iterable<? extends E>> iterables) {
+ this(
+ new TransformationIterator<Iterable<? extends E>, Iterator<? extends E>>(iterables.iterator()) {
+ @Override
+ protected Iterator<? extends E> transform(Iterable<? extends E> iterable) {
+ return iterable.iterator();
+ }
+ }
+ );
+ }
+
+ /**
+ * Construct an iterator with the specified collection of iterators.
+ */
+ public CompositeIterator(Iterator<? extends Iterator<? extends E>> iterators) {
+ super();
+ this.iterators = iterators;
+ }
+
+ /**
+ * Construct an iterator with the specified object prepended
+ * to the specified iterable.
+ */
+ public CompositeIterator(E object, Iterable<? extends E> iterable) {
+ this(object, iterable.iterator());
+ }
+
+ /**
+ * Construct an iterator with the specified object prepended
+ * to the specified iterator.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeIterator(E object, Iterator<? extends E> iterator) {
+ this(new SingleElementIterator<E>(object), iterator);
+ }
+
+ /**
+ * Construct an iterator with the specified object appended
+ * to the specified iterable.
+ */
+ public CompositeIterator(Iterable<? extends E> iterable, E object) {
+ this(iterable.iterator(), object);
+ }
+
+ /**
+ * Construct an iterator with the specified object appended
+ * to the specified iterator.
+ */
+ @SuppressWarnings("unchecked")
+ public CompositeIterator(Iterator<? extends E> iterator, E object) {
+ this(iterator, new SingleElementIterator<E>(object));
+ }
+
+ /**
+ * Construct an iterator with the specified iterables.
+ */
+ public CompositeIterator(Iterable<? extends E>... iterables) {
+ this(new ArrayIterable<Iterable<? extends E>>(iterables));
+ }
+
+ /**
+ * Construct an iterator with the specified iterators.
+ */
+ public CompositeIterator(Iterator<? extends E>... iterators) {
+ this(new ArrayIterator<Iterator<? extends E>>(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 <code>CompositeListIterator</code> wraps a list
+ * of {@link ListIterator}s and makes them appear to be a single
+ * {@link ListIterator}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.CompositeListIterable
+ */
+public class CompositeListIterator<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<? extends ListIterator<E>> iterators;
+ private ListIterator<E> nextIterator;
+ private int nextIndex;
+ /**
+ * <code>true</code> if "next" was last returned;
+ * <code>false</code> if "previous" was last returned;
+ * this determines the effect of {@link #remove()} on {@link #nextIndex}
+ */
+ private boolean nextReturned;
+ private ListIterator<E> lastIteratorToReturnElement;
+
+
+ /**
+ * Construct a list iterator on the elements in the specified list of lists.
+ */
+ public CompositeListIterator(List<? extends List<E>> lists) {
+ this(
+ new TransformationListIterator<List<E>, ListIterator<E>>(lists.listIterator()) {
+ @Override
+ protected ListIterator<E> transform(List<E> list) {
+ return list.listIterator();
+ }
+ }
+ );
+ }
+
+ /**
+ * Construct a list iterator on the elements in the specified list of lists.
+ */
+ public CompositeListIterator(ListIterable<? extends ListIterable<E>> listIterables) {
+ this(
+ new TransformationListIterator<ListIterable<E>, ListIterator<E>>(listIterables.iterator()) {
+ @Override
+ protected ListIterator<E> transform(ListIterable<E> list) {
+ return list.iterator();
+ }
+ }
+ );
+ }
+
+ /**
+ * Construct a list iterator with the specified list of list iterators.
+ */
+ public CompositeListIterator(ListIterator<? extends ListIterator<E>> 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<E> list) {
+ this(object, list.listIterator());
+ }
+
+ /**
+ * Construct a list iterator with the specified object prepended
+ * to the specified list.
+ */
+ public CompositeListIterator(E object, ListIterable<E> 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<E> iterator) {
+ this(new SingleElementListIterator<E>(object), iterator);
+ }
+
+ /**
+ * Construct a list iterator with the specified object appended
+ * to the specified list.
+ */
+ public CompositeListIterator(List<E> list, E object) {
+ this(list.listIterator(), object);
+ }
+
+ /**
+ * Construct a list iterator with the specified object appended
+ * to the specified list.
+ */
+ public CompositeListIterator(ListIterable<E> 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<E> iterator, E object) {
+ this(iterator, new SingleElementListIterator<E>(object));
+ }
+
+ /**
+ * Construct a list iterator with the specified lists.
+ */
+ public CompositeListIterator(List<E>... lists) {
+ this(Arrays.asList(lists));
+ }
+
+ /**
+ * Construct a list iterator with the specified lists.
+ */
+ public CompositeListIterator(ListIterable<E>... listIterables) {
+ this(new ArrayListIterable<ListIterable<E>>(listIterables));
+ }
+
+ /**
+ * Construct a list iterator with the specified list iterators.
+ */
+ public CompositeListIterator(ListIterator<E>... iterators) {
+ this(new ArrayListIterator<ListIterator<E>>(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 <code>hasNext()</code>
+ * 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 <code>hasPrevious()</code>
+ * 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 <code>EmptyIterator</code> is just that.
+ *
+ * @param <E> the type of elements (not) returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.EmptyIterable
+ */
+public final class EmptyIterator<E>
+ implements Iterator<E>
+{
+
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final EmptyIterator INSTANCE = new EmptyIterator();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Iterator<T> 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 <code>EmptyListIterator</code> is just that.
+ *
+ * @param <E> the type of elements (not) returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.EmptyListIterable
+ */
+public final class EmptyListIterator<E>
+ implements ListIterator<E>
+{
+
+ // singleton
+ @SuppressWarnings("rawtypes")
+ private static final EmptyListIterator INSTANCE = new EmptyListIterator();
+
+ /**
+ * Return the singleton.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> ListIterator<T> 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 <code>EnumerationIterator</code> wraps an
+ * {@link Enumeration} so that it can be treated like an
+ * {@link Iterator}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ */
+public class EnumerationIterator<E>
+ implements Iterator<E>
+{
+ private final Enumeration<? extends E> enumeration;
+
+ /**
+ * Construct an iterator that wraps the specified enumeration.
+ */
+ public EnumerationIterator(Enumeration<? extends E> 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 <code>FilteringIterator</code> 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()}.
+ * <p>
+ * As an alternative to building a {@link Filter}, a subclass
+ * of <code>FilteringIterator</code> can override the
+ * {@link #accept(Object)} method.
+ * <p>
+ * One, possibly undesirable, side-effect of using this iterator is that
+ * the nested iterator's <code>next()</code> method will be invoked
+ * <em>before</em> 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 <code>true</code>).
+ * This also prevents a filtered iterator from supporting the optional
+ * <code>remove()</code> method.
+ *
+ * @param <E> the type of elements to be filtered
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.FilteringIterable
+ */
+public class FilteringIterator<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends E> iterator;
+ private final Filter<E> 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<? extends E> 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<? extends E> iterator) {
+ this(iterator, Filter.Disabled.<E>instance());
+ }
+
+ /**
+ * Construct an iterator with the specified
+ * iterable and filter.
+ */
+ public FilteringIterator(Iterable<? extends E> iterable, Filter<E> filter) {
+ this(iterable.iterator(), filter);
+ }
+
+ /**
+ * Construct an iterator with the specified nested
+ * iterator and filter.
+ */
+ public FilteringIterator(Iterator<? extends E> iterator, Filter<E> 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 <code>remove()</code>
+ * 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 <code>END</code>.
+ */
+ 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.
+ * <p>
+ * 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 <code>GraphIterator</code> 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 <code>GraphIterator</code> 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 <em>neighbors</em>), but does not provide a method for
+ * getting <em>all</em> 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.)
+ * <p>
+ * 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.
+ * <p>
+ * It is up to the user of this class to ensure a <em>complete</em> graph.
+ * <p>
+ * To use, supply:<ul>
+ * <li> either the initial node of the graph or an {@link Iterator}
+ * over an initial collection of graph nodes
+ * <li> a {@link MisterRogers} that tells who the neighbors are
+ * of each node
+ * (alternatively, subclass <code>GraphIterator</code>
+ * and override the {@link #neighbors(Object)} method)
+ * </ul>
+ * {@link #remove()} is not supported. This behavior, if
+ * desired, must be implemented by the user of this class.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.GraphIterable
+ */
+public class GraphIterator<E>
+ implements Iterator<E>
+{
+ // use a LinkedList since we will be pulling off the front and adding to the end
+ private final LinkedList<Iterator<? extends E>> iterators = new LinkedList<Iterator<? extends E>>();
+ private final HashSet<E> visitedNeighbors = new HashSet<E>();
+ private final MisterRogers<E> misterRogers;
+
+ private Iterator<? extends E> 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<E>(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<? extends E> 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<? extends E> roots) {
+ this(roots, MisterRogers.Disabled.<E>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
+ * <code>neighbors(Object)</code> method instead of building
+ * a <code>MisterRogers</code>.
+ */
+ public GraphIterator(E root) {
+ this(root, MisterRogers.Disabled.<E>instance());
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a graph
+ * with the specified root and Mr. Rogers.
+ */
+ public GraphIterator(E root, MisterRogers<E> misterRogers) {
+ this(new SingleElementIterator<E>(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<E> misterRogers) {
+ this(new ArrayIterator<E>(roots), misterRogers);
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a graph
+ * with the specified roots and Mr. Rogers.
+ */
+ public GraphIterator(Iterable<? extends E> roots, MisterRogers<E> 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<? extends E> roots, MisterRogers<E> 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<? extends Iterator<? extends E>> 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<? extends E> 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<T> {
+
+ /**
+ * Return the immediate neighbors of the specified object.
+ */
+ Iterator<? extends T> neighbors(T next);
+
+
+ final class Null<S> implements MisterRogers<S> {
+ @SuppressWarnings("rawtypes")
+ public static final MisterRogers INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R> MisterRogers<R> instance() {
+ return INSTANCE;
+ }
+ // ensure single instance
+ private Null() {
+ super();
+ }
+ // return no neighbors
+ public Iterator<S> 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<S> implements MisterRogers<S> {
+ @SuppressWarnings("rawtypes")
+ public static final MisterRogers INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> MisterRogers<R> instance() {
+ return INSTANCE;
+ }
+ // ensure single instance
+ private Disabled() {
+ super();
+ }
+ // throw an exception
+ public Iterator<S> 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 <code>PeekableIterator</code> wraps another {@link Iterator}
+ * and allows a {@link #peek()} at the next element to be
+ * returned by {@link #next()}.
+ * <p>
+ * One, possibly undesirable, side-effect of using this iterator is that
+ * the nested iterator's <code>next()</code> method will be invoked
+ * <em>before</em> 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 <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.PeekableIterable
+ */
+public class PeekableIterator<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends E> iterator;
+ private E next;
+ private boolean done;
+
+
+ /**
+ * Construct a peekable iterator that wraps the specified
+ * iterable.
+ */
+ public PeekableIterator(Iterable<? extends E> iterable) {
+ this(iterable.iterator());
+ }
+
+ /**
+ * Construct a peekable iterator that wraps the specified nested
+ * iterator.
+ */
+ public PeekableIterator(Iterator<? extends E> 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 <code>null</code>
+ * and {@link #done} is set to <code>true</code>.
+ */
+ 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 <code>QueueIterator</code> provides an {@link Iterator}
+ * for a {@link Queue} of objects of type <code>E</code>. The queue's elements
+ * are {@link Queue#dequeue() dequeue}d" as the iterator returns them with
+ * calls to {@link #next()}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see Queue
+ * @see org.eclipse.jpt.common.utility.internal.iterables.QueueIterable
+ */
+public class QueueIterator<E>
+ implements Iterator<E>
+{
+ private final Queue<E> queue;
+
+
+ /**
+ * Construct an iterator for the specified queue.
+ */
+ public QueueIterator(Queue<E> 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 <code>ReadOnlyCompositeListIterator</code> 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 <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyCompositeListIterable
+ */
+public class ReadOnlyCompositeListIterator<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<? extends ListIterator<? extends E>> iterators;
+ private ListIterator<? extends E> nextIterator;
+ private int nextIndex;
+
+
+ /**
+ * Construct a read-only list iterator with the specified list of lists.
+ */
+ public ReadOnlyCompositeListIterator(List<? extends List<? extends E>> lists) {
+ this(
+ new TransformationListIterator<List<? extends E>, ListIterator<? extends E>>(lists.listIterator()) {
+ @Override
+ protected ListIterator<? extends E> transform(List<? extends E> list) {
+ return list.listIterator();
+ }
+ }
+ );
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified list of lists.
+ */
+ public ReadOnlyCompositeListIterator(ListIterable<? extends ListIterable<? extends E>> listIterables) {
+ this(
+ new TransformationListIterator<ListIterable<? extends E>, ListIterator<? extends E>>(listIterables.iterator()) {
+ @Override
+ protected ListIterator<? extends E> transform(ListIterable<? extends E> listIterable) {
+ return listIterable.iterator();
+ }
+ }
+ );
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified list of
+ * list iterators.
+ */
+ public ReadOnlyCompositeListIterator(ListIterator<? extends ListIterator<? extends E>> 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<? extends E> 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<? extends E> 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<? extends E> iterator) {
+ this(new SingleElementListIterator<E>(object), iterator);
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified object appended
+ * to the specified list.
+ */
+ public ReadOnlyCompositeListIterator(List<? extends E> 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<? extends E> 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<? extends E> iterator, E object) {
+ this(iterator, new SingleElementListIterator<E>(object));
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified lists.
+ */
+ public ReadOnlyCompositeListIterator(List<? extends E>... lists) {
+ this(Arrays.asList(lists));
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified lists.
+ */
+ public ReadOnlyCompositeListIterator(ListIterable<? extends E>... listIterables) {
+ this(new ArrayListIterable<ListIterable<? extends E>>(listIterables));
+ }
+
+ /**
+ * Construct a read-only list iterator with the specified list iterators.
+ */
+ public ReadOnlyCompositeListIterator(ListIterator<? extends E>... iterators) {
+ this(new ArrayListIterator<ListIterator<? extends E>>(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 <code>ReadOnlyIterator</code> wraps another {@link Iterator}
+ * and removes support for {@link #remove()}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyIterable
+ */
+public class ReadOnlyIterator<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends E> iterator;
+
+ /**
+ * Construct an iterator on the specified collection that
+ * disallows removes.
+ */
+ public ReadOnlyIterator(Iterable<? extends E> c) {
+ this(c.iterator());
+ }
+
+ /**
+ * Construct an iterator with the specified nested iterator
+ * and disallow removes.
+ */
+ public ReadOnlyIterator(Iterator<? extends E> 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 <code>ReadOnlyListIterator</code> wraps another
+ * {@link ListIterator} and removes support for:<ul>
+ * <li>{@link #remove()}
+ * <li>{@link #set(Object)}
+ * <li>{@link #add(Object)}
+ * </ul>
+ *
+ * @param <E> the type of elements returned by the list iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.ReadOnlyListIterable
+ */
+public class ReadOnlyListIterator<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<? extends E> listIterator;
+
+
+ /**
+ * Construct a list iterator on the specified list that
+ * disallows removes, sets, and adds.
+ */
+ public ReadOnlyListIterator(List<? extends E> list) {
+ this(list.listIterator());
+ }
+
+ /**
+ * Construct a list iterator on the specified list that
+ * disallows removes, sets, and adds.
+ */
+ public ReadOnlyListIterator(ListIterable<? extends E> listIterable) {
+ this(listIterable.iterator());
+ }
+
+ /**
+ * Construct a list iterator on the specified list iterator that
+ * disallows removes, sets, and adds.
+ */
+ public ReadOnlyListIterator(ListIterator<? extends E> 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 <code>ResultSetIterator</code> 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.
+ * <p>
+ * To use, supply:<ul>
+ * <li> a {@link ResultSet}
+ * <li> an {@link Adapter} that converts a row in the {@link ResultSet}
+ * into the desired object
+ * (alternatively, subclass <code>ResultSetIterator</code>
+ * and override the {@link #buildNext(ResultSet)} method)
+ * </ul>
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see java.sql.ResultSet
+ */
+public class ResultSetIterator<E>
+ implements Iterator<E>
+{
+ private final ResultSet resultSet;
+ private final Adapter<E> 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<E> 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.<E>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<T> {
+
+ /**
+ * 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<S> implements Adapter<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Adapter INSTANCE = new Default();
+ @SuppressWarnings("unchecked")
+ public static <R> Adapter<R> 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 <code>ReverseIterator</code> wraps another {@link Iterator} and returns
+ * its elements in the reverse order in which the wrapped {@link Iterator}
+ * returns the elements.
+ *
+ * @param <E> the type of elements returned by the iterator
+ */
+public class ReverseIterator<E>
+ implements Iterator<E>
+{
+ /**
+ * The elements in this iterator are already reversed.
+ */
+ private final Iterator<E> iterator;
+
+
+ /**
+ * Construct a reverse iterator for the specified iterator.
+ */
+ public ReverseIterator(Iterator<E> iterator) {
+ this(CollectionTools.reverseList(iterator));
+ }
+
+ /**
+ * Construct a reverse iterator for the specified iterator.
+ */
+ public ReverseIterator(Iterator<E> iterator, int size) {
+ this(CollectionTools.reverseList(iterator, size));
+ }
+
+ /**
+ * Construct a reverse iterator for the specified iterable.
+ */
+ public ReverseIterator(Iterable<E> iterable) {
+ this(CollectionTools.reverseList(iterable));
+ }
+
+ /**
+ * Construct a reverse iterator for the specified iterable.
+ */
+ public ReverseIterator(Iterable<E> iterable, int size) {
+ this(CollectionTools.reverseList(iterable, size));
+ }
+
+ private ReverseIterator(ArrayList<E> 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 <code>SingleElementIterator</code> holds a single element
+ * and returns it with the first call to {@link #next()}, at
+ * which point it will return <code>false</code> to any subsequent
+ * call to {@link #hasNext()}.
+ * <p>
+ * A <code>SingleElementIterator</code> is equivalent to the
+ * {@link Iterator} returned by:
+ * {@link java.util.Collections#singleton(Object element)}<code>.iterator()</code>
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SingleElementIterable
+ */
+public class SingleElementIterator<E>
+ implements Iterator<E>
+{
+ 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 <code>SingleElementListIterator</code> holds a single element
+ * and returns it with the first call to {@link #next()}, at
+ * which point it will return <code>false</code> to any subsequent
+ * call to {@link #hasNext()}. Likewise, it will return <code>false</code>
+ * 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.
+ * <p>
+ * A <code>SingleElementListIterator</code> is equivalent to the
+ * {@link ListIterator} returned by:
+ * {@link java.util.Collections#singletonList(Object element)}<code>.listIterator()</code>
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SingleElementListIterable
+ */
+public class SingleElementListIterator<E>
+ implements ListIterator<E>
+{
+ 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 <code>StackIterator</code> provides an {@link Iterator}
+ * for a {@link Stack} of objects of type <code>E</code>. The stack's elements
+ * are {@link Stack#pop() pop}ped" as the iterator returns them with
+ * calls to {@link #next()}.
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see Stack
+ * @see org.eclipse.jpt.common.utility.internal.iterables.StackIterable
+ */
+public class StackIterator<E>
+ implements Iterator<E>
+{
+ private final Stack<E> stack;
+
+
+ /**
+ * Construct an iterator for the specified stack.
+ */
+ public StackIterator(Stack<E> 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 <code>E1</code>, converting it into an
+ * iterator on elements of type <code>E2</code>. <em>Assume</em> the wrapped
+ * iterator returns only elements of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements returned by the wrapped iterator
+ * @param <E2> output: the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SubIterableWrapper
+ */
+public class SubIteratorWrapper<E1, E2>
+ implements Iterator<E2>
+{
+ private final Iterator<E1> iterator;
+
+
+ public SubIteratorWrapper(Iterable<E1> iterable) {
+ this(iterable.iterator());
+ }
+
+ public SubIteratorWrapper(Iterator<E1> 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 <code>E1</code>, converting it into
+ * a list iterator on elements of type <code>E2</code>. Assume the wrapped
+ * list iterator returns only elements of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements returned by the wrapped list iterator
+ * @param <E2> output: the type of elements returned by the list iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SubListIterableWrapper
+ */
+public class SubListIteratorWrapper<E1, E2>
+ implements ListIterator<E2>
+{
+ private final ListIterator<E1> listIterator;
+
+
+ public SubListIteratorWrapper(List<E1> list) {
+ this(list.listIterator());
+ }
+
+ public SubListIteratorWrapper(ListIterable<E1> listIterable) {
+ this(listIterable.iterator());
+ }
+
+ public SubListIteratorWrapper(ListIterator<E1> 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 <code>E</code>, converting
+ * it into an iterator on elements of type <code>E</code>. 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 <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SuperIterableWrapper
+ */
+public class SuperIteratorWrapper<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends E> iterator;
+
+
+ public SuperIteratorWrapper(Iterable<? extends E> iterable) {
+ this(iterable.iterator());
+ }
+
+ public SuperIteratorWrapper(Iterator<? extends E> 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 <code>E</code>, converting it into a
+ * list iterator on elements of type <code>E</code>. 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 <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.SuperListIterableWrapper
+ */
+public class SuperListIteratorWrapper<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<? extends E> listIterator;
+
+
+ public SuperListIteratorWrapper(List<? extends E> list) {
+ this(list.listIterator());
+ }
+
+ public SuperListIteratorWrapper(ListIterable<? extends E> listIterable) {
+ this(listIterable.iterator());
+ }
+
+ public SuperListIteratorWrapper(ListIterator<? extends E> 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 <E> the type of elements returned by the iterator
+ */
+public class SynchronizedIterator<E>
+ implements Iterator<E>
+{
+ private final Iterator<? extends E> iterator;
+
+ /** Object to synchronize on. */
+ private final Object mutex;
+
+
+ public SynchronizedIterator(Iterable<? extends E> iterable) {
+ this(iterable.iterator());
+ }
+
+ public SynchronizedIterator(Iterable<? extends E> iterable, Object mutex) {
+ this(iterable.iterator(), mutex);
+ }
+
+ public SynchronizedIterator(Iterator<? extends E> iterator) {
+ super();
+ this.iterator = iterator;
+ this.mutex = this;
+ }
+
+ public SynchronizedIterator(Iterator<? extends E> 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 <E> the type of elements returned by the iterator
+ */
+public class SynchronizedListIterator<E>
+ implements ListIterator<E>
+{
+ private final ListIterator<E> listIterator;
+
+ /** Object to synchronize on. */
+ private final Object mutex;
+
+
+ public SynchronizedListIterator(List<E> list) {
+ this(list.listIterator());
+ }
+
+ public SynchronizedListIterator(List<E> list, Object mutex) {
+ this(list.listIterator(), mutex);
+ }
+
+ public SynchronizedListIterator(ListIterable<E> listIterable) {
+ this(listIterable.iterator());
+ }
+
+ public SynchronizedListIterator(ListIterable<E> listIterable, Object mutex) {
+ this(listIterable.iterator(), mutex);
+ }
+
+ public SynchronizedListIterator(ListIterator<E> listIterator) {
+ super();
+ this.listIterator = listIterator;
+ this.mutex = this;
+ }
+
+ public SynchronizedListIterator(ListIterator<E> 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 <code>TransformationIterator</code> wraps another {@link Iterator}
+ * and transforms its results for client consumption. To use, supply a
+ * {@link Transformer} or subclass <code>TransformationIterator</code>
+ * and override the {@link #transform(Object)} method.
+ * Objects of type <code>E1</code> are transformed into objects of type <code>E2</code>;
+ * i.e. the iterator returns objects of type <code>E2</code>.
+ *
+ * @param <E1> input: the type of elements to be transformed
+ * @param <E2> output: the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable
+ */
+public class TransformationIterator<E1, E2>
+ implements Iterator<E2>
+{
+ private final Iterator<? extends E1> iterator;
+ private final Transformer<E1, ? extends E2> 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<? extends E1> 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<? extends E1> iterator) {
+ this(iterator, Transformer.Disabled.<E1, E2>instance());
+ }
+
+ /**
+ * Construct an iterator with the specified iterable and transformer.
+ */
+ public TransformationIterator(Iterable<? extends E1> iterable, Transformer<E1, ? extends E2> transformer) {
+ this(iterable.iterator(), transformer);
+ }
+
+ /**
+ * Construct an iterator with the specified nested iterator
+ * and transformer.
+ */
+ public TransformationIterator(Iterator<? extends E1> iterator, Transformer<E1, ? extends E2> 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 <code>TransformationListIterator</code> wraps another {@link ListIterator}
+ * and transforms its results for client consumption. To use, supply a
+ * {@link Transformer} or subclass <code>TransformationIterator</code>
+ * and override the {@link #transform(Object)} method.
+ * <p>
+ * The methods {@link #set(Object)} and {@link #add(Object)}
+ * are left unsupported in this class.
+ *
+ * @param <E1> input: the type of elements to be transformed
+ * @param <E2> output: the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.TransformationListIterable
+ */
+public class TransformationListIterator<E1, E2>
+ implements ListIterator<E2>
+{
+ private final ListIterator<? extends E1> listIterator;
+ private final Transformer<E1, ? extends E2> 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<? extends E1> 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<? extends E1> listIterator) {
+ this(listIterator, Transformer.Disabled.<E1, E2>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<? extends E1> listIterable) {
+ this(listIterable.iterator());
+ }
+
+ /**
+ * Construct an iterator with the specified list and transformer.
+ */
+ public TransformationListIterator(List<? extends E1> list, Transformer<E1, ? extends E2> transformer) {
+ this(list.listIterator(), transformer);
+ }
+
+ /**
+ * Construct an iterator with the specified list and transformer.
+ */
+ public TransformationListIterator(ListIterable<? extends E1> listIterable, Transformer<E1, ? extends E2> transformer) {
+ this(listIterable.iterator(), transformer);
+ }
+
+ /**
+ * Construct an iterator with the specified nested iterator
+ * and transformer.
+ */
+ public TransformationListIterator(ListIterator<? extends E1> listIterator, Transformer<E1, ? extends E2> 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 <code>TreeIterator</code> 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.
+ * <p>
+ * To use, supply:<ul>
+ * <li> either the root element of the tree or, if the tree has
+ * multiple roots, an {@link Iterator} over the set of roots
+ * <li> a {@link Midwife} that delivers the children
+ * of each child
+ * (alternatively, subclass <code>TreeIterator</code>
+ * and override the {@link #children(Object)} method)
+ * </ul>
+ *
+ * @param <E> the type of elements returned by the iterator
+ *
+ * @see org.eclipse.jpt.common.utility.internal.iterables.TreeIterable
+ */
+public class TreeIterator<E>
+ implements Iterator<E>
+{
+ private final LinkedList<Iterator<? extends E>> iterators;
+ private final Midwife<E> midwife;
+ private Iterator<? extends E> 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<E>(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<? extends E> 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<? extends E> roots) {
+ this(roots, Midwife.Disabled.<E>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.<E>instance());
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a tree
+ * with the specified root and midwife.
+ */
+ public TreeIterator(E root, Midwife<E> midwife) {
+ this(new SingleElementIterator<E>(root), midwife);
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a tree
+ * with the specified roots and midwife.
+ */
+ public TreeIterator(E[] roots, Midwife<E> midwife) {
+ this(new ArrayIterator<E>(roots), midwife);
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a tree
+ * with the specified roots and midwife.
+ */
+ public TreeIterator(Iterable<? extends E> roots, Midwife<E> midwife) {
+ this(roots.iterator(), midwife);
+ }
+
+ /**
+ * Construct an iterator that returns the nodes of a tree
+ * with the specified roots and midwife.
+ */
+ public TreeIterator(Iterator<? extends E> roots, Midwife<E> 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<Iterator<? extends E>>();
+ this.midwife = midwife;
+ }
+
+ public boolean hasNext() {
+ if (this.currentIterator.hasNext()) {
+ return true;
+ }
+ for (Iterator<? extends E> iterator : this.iterators) {
+ if (iterator.hasNext()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public E next() {
+ if (this.currentIterator.hasNext()) {
+ return this.nextInternal();
+ }
+ for (Iterator<Iterator<? extends E>> 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.
+ * <p>
+ * This method can be overridden by a subclass as an
+ * alternative to building a {@link Midwife}.
+ */
+ protected Iterator<? extends E> 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<T> {
+
+ /**
+ * Return the immediate children of the specified object.
+ */
+ Iterator<? extends T> children(T o);
+
+
+ final class Null<S> implements Midwife<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Midwife INSTANCE = new Null();
+ @SuppressWarnings("unchecked")
+ public static <R> Midwife<R> instance() {
+ return INSTANCE;
+ }
+ // ensure single instance
+ private Null() {
+ super();
+ }
+ // return no neighbors
+ public Iterator<S> 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<S> implements Midwife<S> {
+ @SuppressWarnings("rawtypes")
+ public static final Midwife INSTANCE = new Disabled();
+ @SuppressWarnings("unchecked")
+ public static <R> Midwife<R> instance() {
+ return INSTANCE;
+ }
+ // ensure single instance
+ private Disabled() {
+ super();
+ }
+ // throw an exception
+ public Iterator<S> 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 <em>lazily-initialized</em>;
+ * so it may be <code>null</code>. The method {@link #getChangeSupport()}
+ * will initialize this field if it is <code>null</code>.
+ * <p>
+ * <strong>NB:</strong> We instantiate this when we fire events, even when
+ * we do not have any listeners (which is be implied if this is <code>null</code>).
+ * 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 <code>null</code> "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 <E> boolean addItemToCollection(E item, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().addItemToCollection(item, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToCollection(Object[], Collection, String)
+ */
+ protected <E> boolean addItemsToCollection(E[] items, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().addItemsToCollection(items, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToCollection(Collection, Collection, String)
+ */
+ protected <E> boolean addItemsToCollection(Collection<? extends E> items, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().addItemsToCollection(items, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToCollection(Iterable, Collection, String)
+ */
+ protected <E> boolean addItemsToCollection(Iterable<? extends E> items, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().addItemsToCollection(items, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToCollection(Iterator, Collection, String)
+ */
+ protected <E> boolean addItemsToCollection(Iterator<? extends E> items, Collection<E> 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 <E> boolean synchronizeCollection(Collection<E> newCollection, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().synchronizeCollection(newCollection, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#synchronizeCollection(Iterable, Collection, String)
+ */
+ protected <E> boolean synchronizeCollection(Iterable<E> newCollection, Collection<E> collection, String collectionName) {
+ return this.getChangeSupport().synchronizeCollection(newCollection, collection, collectionName);
+ }
+
+ /**
+ * @see ChangeSupport#synchronizeCollection(Iterator, Collection, String)
+ */
+ protected <E> boolean synchronizeCollection(Iterator<E> newCollection, Collection<E> 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 <E> boolean fireItemsReplaced(String listName, int index, List<? extends E> newItems, List<E> 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 <E> 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 <E> void addItemToList(int index, E item, List<E> list, String listName) {
+ this.getChangeSupport().addItemToList(index, item, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemToList(Object, List, String)
+ */
+ protected <E> boolean addItemToList(E item, List<E> list, String listName) {
+ return this.getChangeSupport().addItemToList(item, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(int, Object[], List, String)
+ */
+ protected <E> boolean addItemsToList(int index, E[] items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(int, Collection, List, String)
+ */
+ protected <E> boolean addItemsToList(int index, Collection<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(int, Iterable, List, String)
+ */
+ protected <E> boolean addItemsToList(int index, Iterable<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(int, Iterator, List, String)
+ */
+ protected <E> boolean addItemsToList(int index, Iterator<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(Object[], List, String)
+ */
+ protected <E> boolean addItemsToList(E[] items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(Collection, List, String)
+ */
+ protected <E> boolean addItemsToList(Collection<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(Iterable, List, String)
+ */
+ protected <E> boolean addItemsToList(Iterable<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#addItemsToList(Iterator, List, String)
+ */
+ protected <E> boolean addItemsToList(Iterator<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().addItemsToList(items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#removeItemFromList(int, List, String)
+ */
+ protected <E> E removeItemFromList(int index, List<E> 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 <E> List<E> removeRangeFromList(int beginIndex, int endIndex, List<E> list, String listName) {
+ return this.getChangeSupport().removeRangeFromList(beginIndex, endIndex, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#removeItemsFromList(int, List, String)
+ */
+ protected <E> List<E> removeItemsFromList(int index, List<E> list, String listName) {
+ return this.getChangeSupport().removeItemsFromList(index, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#removeItemsFromList(int, int, List, String)
+ */
+ protected <E> List<E> removeItemsFromList(int index, int length, List<E> 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> E setItemInList(int index, E item, List<E> list, String listName) {
+ return this.getChangeSupport().setItemInList(index, item, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#replaceItemInList(Object, Object, List, String)
+ */
+ protected <E> int replaceItemInList(E oldItem, E newItem, List<E> list, String listName) {
+ return this.getChangeSupport().replaceItemInList(oldItem, newItem, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#setItemsInList(int, Object[], List, String)
+ */
+ protected <E> List<E> setItemsInList(int index, E[] items, List<E> list, String listName) {
+ return this.getChangeSupport().setItemsInList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#setItemsInList(int, List, List, String)
+ */
+ protected <E> List<E> setItemsInList(int index, List<? extends E> items, List<E> list, String listName) {
+ return this.getChangeSupport().setItemsInList(index, items, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#moveItemsInList(int, int, int, List, String)
+ */
+ protected <E> void moveItemsInList(int targetIndex, int sourceIndex, int length, List<E> list, String listName) {
+ this.getChangeSupport().moveItemsInList(targetIndex, sourceIndex, length, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#moveItemInList(int, int, List, String)
+ */
+ protected <E> void moveItemInList(int targetIndex, int sourceIndex, List<E> list, String listName) {
+ this.getChangeSupport().moveItemInList(targetIndex, sourceIndex, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#moveItemInList(int, Object, List, String)
+ */
+ protected <E> void moveItemInList(int targetIndex, E item, List<E> 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 <E> boolean synchronizeList(List<? extends E> newList, List<E> list, String listName) {
+ return this.getChangeSupport().synchronizeList(newList, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#synchronizeList(Iterable, List, String)
+ */
+ protected <E> boolean synchronizeList(Iterable<? extends E> newList, List<E> list, String listName) {
+ return this.getChangeSupport().synchronizeList(newList, list, listName);
+ }
+
+ /**
+ * @see ChangeSupport#synchronizeList(Iterator, List, String)
+ */
+ protected <E> boolean synchronizeList(Iterator<? extends E> newList, List<E> 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 <code>null</code> checks.
+ * Convenience method for checking whether an attribute value has changed.
+ * <p>
+ * <em>Do not</em> 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);
+ }
+
+
+ /**
+ * <em>Do not</em> use this to determine whether to fire a change notification,
+ * {@link ChangeSupport} already does that.
+ * <p>
+ * 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. <code>"ClassName[00-F3-EE-42](add'l info)"</code>
+ */
+ @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
+ * <code>#toString(StringBuilder)</code>.
+ */
+ 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 <em>name</em>
+ * of the aspect that changed but not so much <em>how</em> the aspect changed.
+ */
+public class AspectChangeSupport
+ extends ChangeSupport
+{
+ private static final long serialVersionUID = 1L;
+ protected static final Class<Listener> 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<Listener> 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<Listener> getListeners() {
+ ListenerList<Listener> listenerList = this.getListenerList();
+ return (listenerList == null) ? null : listenerList.getListeners();
+ }
+
+ private ListenerList<Listener> 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.
+ * <p>
+ * <strong>NB1:</strong> 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
+ * <p>
+ * <strong>NB2:</strong> 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 <em>before</em> it is notified. If the listener has been removed
+ * "concurrently" it will <em>not</em> be notified.
+ * <p>
+ * <strong>NB3:</strong> Any listener that is added during the firing of events will <em>not</em> 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.
+ * <p>
+ * <strong>NB4:</strong> 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 <code>null</code>.
+ */
+ 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 <code>null</code>.
+ */
+ protected synchronized <L extends EventListener> void addListener(Class<L> listenerClass, String aspectName, L listener) {
+ ListenerList<L> aspectListenerList = this.getListenerList(listenerClass, aspectName);
+ if (aspectListenerList == null) {
+ this.aspectListenerListPairs = ArrayTools.add(this.aspectListenerListPairs, new SimpleAspectListenerListPair<L>(listenerClass, aspectName, listener));
+ } else {
+ aspectListenerList.add(listener);
+ }
+ }
+
+ /**
+ * Add a listener that listens to all the events of the specified type.
+ * The listener cannot be <code>null</code>.
+ */
+ protected synchronized <L extends EventListener> void addListener(Class<L> listenerClass, L listener) {
+ ListenerList<L> listenerList = this.getListenerList(listenerClass);
+ if (listenerList == null) {
+ this.aspectListenerListPairs = ArrayTools.add(this.aspectListenerListPairs, new NullAspectListenerListPair<L>(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 <code>null</code>.
+ */
+ protected synchronized <L extends EventListener> void removeListener(Class<L> listenerClass, String aspectName, L listener) {
+ ListenerList<L> 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 <code>null</code>.
+ */
+ protected synchronized <L extends EventListener> void removeListener(Class<L> listenerClass, L listener) {
+ ListenerList<L> 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 <code>null</code> if the listener list is not present.
+ * The aspect name cannot be <code>null</code>.
+ */
+ protected <L extends EventListener> ListenerList<L> getListenerList(Class<L> 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 <code>null</code> if the listener list is not present.
+ */
+ protected <L extends EventListener> ListenerList<L> getListenerList(Class<L> listenerClass) {
+ return this.getListenerList_(listenerClass, null);
+ }
+
+ /**
+ * Return the listener list for the specified listener class and aspect name.
+ * Return <code>null</code> if the listener list is not present.
+ */
+ protected synchronized <L extends EventListener> ListenerList<L> getListenerList_(Class<L> listenerClass, String aspectName) {
+ for (AspectListenerListPair<?> pair : this.aspectListenerListPairs) {
+ if (pair.matches(listenerClass, aspectName)) {
+ @SuppressWarnings("unchecked") ListenerList<L> aspectListenerList = (ListenerList<L>) pair.listenerList;
+ return aspectListenerList;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return whether there are any listeners for the specified listener class
+ * and aspect name.
+ */
+ protected <L extends EventListener> boolean hasAnyListeners(Class<L> listenerClass, String aspectName) {
+ ListenerList<L> 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 <L extends EventListener> boolean hasNoListeners(Class<L> listenerClass, String aspectName) {
+ return ! this.hasAnyListeners(listenerClass, aspectName);
+ }
+
+ /**
+ * Return whether there are any listeners for the specified listener class.
+ */
+ protected <L extends EventListener> boolean hasAnyListeners(Class<L> listenerClass) {
+ ListenerList<L> 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 <L extends EventListener> boolean hasNoListeners(Class<L> 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 <L extends ChangeListener> Class<L> getChangeListenerClass() {
+ // not sure why I need to cast here...
+ return (Class<L>) CHANGE_LISTENER_CLASS;
+ }
+
+ protected static final Class<ChangeListener> 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<ChangeListener> getChangeListenerList() {
+ return this.getListenerList(CHANGE_LISTENER_CLASS);
+ }
+
+ private Iterable<ChangeListener> getChangeListeners() {
+ ListenerList<ChangeListener> 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<StateChangeListener> 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<StateChangeListener> getStateChangeListenerList() {
+ return this.getListenerList(STATE_CHANGE_LISTENER_CLASS);
+ }
+
+ private Iterable<StateChangeListener> getStateChangeListeners() {
+ ListenerList<StateChangeListener> 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<StateChangeListener> listeners = this.getStateChangeListeners();
+ if (listeners != null) {
+ for (StateChangeListener listener : listeners) {
+ if (this.hasStateChangeListener(listener)) { // verify listener is still listening
+ listener.stateChanged(event);
+ }
+ }
+ }
+
+ Iterable<ChangeListener> 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<StateChangeListener> 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<ChangeListener> 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<PropertyChangeListener> 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<PropertyChangeListener> getPropertyChangeListenerList(String propertyName) {
+ return this.getListenerList(PROPERTY_CHANGE_LISTENER_CLASS, propertyName);
+ }
+
+ private Iterable<PropertyChangeListener> getPropertyChangeListeners(String propertyName) {
+ ListenerList<PropertyChangeListener> 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<PropertyChangeListener> 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<ChangeListener> 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<PropertyChangeListener> 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<ChangeListener> 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 <code>int</code> 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.
+ * <p>
+ * 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<PropertyChangeListener> 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<ChangeListener> 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 <code>boolean</code> 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.
+ * <p>
+ * 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<PropertyChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<CollectionChangeListener> getCollectionChangeListenerList(String collectionName) {
+ return this.getListenerList(COLLECTION_CHANGE_LISTENER_CLASS, collectionName);
+ }
+
+ private Iterable<CollectionChangeListener> getCollectionChangeListeners(String collectionName) {
+ ListenerList<CollectionChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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<CollectionChangeListener> 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<ChangeListener> 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 <E> boolean addItemToCollection(E item, Collection<E> 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 <E> boolean addItemsToCollection(E[] items, Collection<E> collection, String collectionName) {
+ return (items.length != 0)
+ && this.addItemsToCollection_(new ArrayIterator<E>(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 <E> boolean addItemsToCollection(Collection<? extends E> items, Collection<E> 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 <E> boolean addItemsToCollection(Iterable<? extends E> items, Collection<E> 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 <E> boolean addItemsToCollection(Iterator<? extends E> items, Collection<E> collection, String collectionName) {
+ return items.hasNext()
+ && this.addItemsToCollection_(items, collection, collectionName);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> boolean addItemsToCollection_(Iterator<? extends E> items, Collection<E> collection, String collectionName) {
+ Collection<E> addedItems = null;
+ while (items.hasNext()) {
+ E item = items.next();
+ if (collection.add(item)) {
+ if (addedItems == null) {
+ addedItems = new ArrayList<E>();
+ }
+ 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<Object>(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<Object>(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 <E> boolean synchronizeCollection(Collection<E> newCollection, Collection<E> 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 <E> boolean synchronizeCollection(Iterable<E> newCollection, Collection<E> 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 <E> boolean synchronizeCollection(Iterator<E> newCollection, Collection<E> 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 <E> boolean synchronizeCollection_(Collection<E> newCollection, Collection<E> collection, String collectionName) {
+ boolean changed = false;
+ Collection<E> removeItems = new HashBag<E>(collection);
+ removeItems.removeAll(newCollection);
+ changed |= this.removeItemsFromCollection(removeItems, collection, collectionName);
+
+ Collection<E> addItems = new HashBag<E>(newCollection);
+ addItems.removeAll(collection);
+ changed |= this.addItemsToCollection(addItems, collection, collectionName);
+
+ return changed;
+ }
+
+
+ // ********** list change support **********
+
+ protected static final Class<ListChangeListener> 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<ListChangeListener> getListChangeListenerList(String listName) {
+ return this.getListenerList(LIST_CHANGE_LISTENER_CLASS, listName);
+ }
+
+ private Iterable<ListChangeListener> getListChangeListeners(String listName) {
+ ListenerList<ListChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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<ListChangeListener> 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<ChangeListener> 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 <E> void addItemToList(int index, E item, List<E> 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 <E> boolean addItemToList(E item, List<E> 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 <E> boolean addItemsToList(int index, E[] items, List<E> 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 <E> boolean addItemsToList(int index, Collection<? extends E> items, List<E> list, String listName) {
+ return ( ! items.isEmpty())
+ && this.addItemsToList_(index, this.convertToList(items), list, listName);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> boolean addItemsToList_(int index, List<? extends E> items, List<E> 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 <E> boolean addItemsToList(int index, Iterable<? extends E> items, List<E> 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 <E> boolean addItemsToList(int index, Iterator<? extends E> items, List<E> list, String listName) {
+ if ( ! items.hasNext()) {
+ return false;
+ }
+
+ ArrayList<E> 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 <E> boolean addItemsToList(E[] items, List<E> 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 <E> boolean addItemsToList(Collection<? extends E> items, List<E> list, String listName) {
+ return ( ! items.isEmpty())
+ && this.addItemsToList_(this.convertToList(items), list, listName);
+ }
+
+ protected <E> List<? extends E> convertToList(Collection<? extends E> collection) {
+ return (collection instanceof List<?>) ? (List<? extends E>) collection : new ArrayList<E>(collection);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> boolean addItemsToList_(List<? extends E> items, List<E> 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 <E> boolean addItemsToList(Iterable<? extends E> items, List<E> 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 <E> boolean addItemsToList(Iterator<? extends E> items, List<E> list, String listName) {
+ if ( ! items.hasNext()) {
+ return false;
+ }
+ return this.addItemsToList_(items, list, listName);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> boolean addItemsToList_(Iterator<? extends E> items, List<E> list, String listName) {
+ ArrayList<E> 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> E removeItemFromList(int index, List<E> 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 <E> List<E> removeItemsFromList(int index, List<E> 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 <E> List<E> removeItemsFromList(int index, int length, List<E> 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 <E> List<E> removeRangeFromList(int beginIndex, int endIndex, List<E> list, String listName) {
+ if (beginIndex == endIndex) {
+ return Collections.emptyList();
+ }
+ return this.removeRangeFromList_(beginIndex, endIndex, list, listName);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> List<E> removeRangeFromList_(int beginIndex, int endIndex, List<E> list, String listName) {
+ List<E> subList = list.subList(beginIndex, endIndex);
+ List<E> removedItems = new ArrayList<E>(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<Object>(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<Object>(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> E setItemInList(int index, E item, List<E> 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 <E> int replaceItemInList(E oldItem, E newItem, List<E> 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 <E> List<E> setItemsInList(int index, E[] items, List<E> 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 <E> List<E> setItemsInList(int index, List<? extends E> items, List<E> list, String listName) {
+ if (items.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return this.setItemsInList_(index, items, list, listName);
+ }
+
+ /**
+ * no empty check
+ */
+ protected <E> List<E> setItemsInList_(int index, List<? extends E> items, List<E> list, String listName) {
+ List<E> subList = list.subList(index, index + items.size());
+ List<E> oldItems = new ArrayList<E>(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 <E> boolean moveItemsInList(int targetIndex, int sourceIndex, int length, List<E> 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 <E> boolean moveItemInList(int targetIndex, E item, List<E> 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 <E> boolean moveItemInList(int targetIndex, int sourceIndex, List<E> 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 <E> boolean synchronizeList(List<E> newList, List<E> 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 <E> boolean synchronizeList(Iterable<? extends E> newList, List<E> 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 <E> boolean synchronizeList(Iterator<? extends E> newList, List<E> 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 <E> boolean synchronizeList_(List<? extends E> newList, List<E> 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<TreeChangeListener> 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<TreeChangeListener> getTreeChangeListenerList(String treeName) {
+ return this.getListenerList(TREE_CHANGE_LISTENER_CLASS, treeName);
+ }
+
+ private Iterable<TreeChangeListener> getTreeChangeListeners(String treeName) {
+ ListenerList<TreeChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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<TreeChangeListener> 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<ChangeListener> 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 <code>null</code> aspect name with its associated
+ * listeners.
+ */
+ static abstract class AspectListenerListPair<L extends EventListener>
+ implements Serializable
+ {
+ final ListenerList<L> listenerList;
+
+ private static final long serialVersionUID = 1L;
+
+ AspectListenerListPair(Class<L> listenerClass, L listener) {
+ super();
+ this.listenerList = new ListenerList<L>(listenerClass, listener);
+ }
+
+ boolean matches(Class<? extends EventListener> listenerClass, @SuppressWarnings("unused") String aspectName) {
+ return this.listenerList.getListenerType() == listenerClass;
+ }
+
+ boolean matches(Class<? extends EventListener> listenerClass) {
+ return this.matches(listenerClass, null);
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this, this.getAspectName());
+ }
+
+ abstract String getAspectName();
+ }
+
+ /**
+ * Pair a non-<code>null</code> aspect name with its associated listeners.
+ */
+ static class SimpleAspectListenerListPair<L extends EventListener>
+ extends AspectListenerListPair<L>
+ {
+ final String aspectName;
+
+ private static final long serialVersionUID = 1L;
+
+ SimpleAspectListenerListPair(Class<L> listenerClass, String aspectName, L listener) {
+ super(listenerClass, listener);
+ if (aspectName == null) {
+ throw new NullPointerException();
+ }
+ this.aspectName = aspectName;
+ }
+
+ @Override
+ boolean matches(Class<? extends EventListener> listenerClass, @SuppressWarnings("hiding") String aspectName) {
+ return this.aspectName.equals(aspectName)
+ && super.matches(listenerClass, aspectName);
+ }
+
+ @Override
+ String getAspectName() {
+ return this.aspectName;
+ }
+ }
+
+ /**
+ * Pair a <code>null</code> aspect name with its associated listeners.
+ */
+ static class NullAspectListenerListPair<L extends EventListener>
+ extends AspectListenerListPair<L>
+ {
+ private static final long serialVersionUID = 1L;
+
+ NullAspectListenerListPair(Class<L> listenerClass, L listener) {
+ super(listenerClass, listener);
+ }
+
+ @Override
+ boolean matches(Class<? extends EventListener> 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:<ul>
+ * <li>All events fired by the source must specify the single aspect.
+ * <li>Listeners are required to be either "general purpose" listeners or
+ * listeners of the single aspect.
+ * </ul>
+ */
+public class SingleAspectChangeSupport
+ extends ChangeSupport
+{
+ protected final Class<? extends EventListener> validListenerClass;
+ protected final String validAspectName;
+
+ private static final long serialVersionUID = 1L;
+
+
+ // ********** constructor **********
+
+ public SingleAspectChangeSupport(Model source, Class<? extends EventListener> 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<? extends EventListener> 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<? extends EventListener> 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 <L extends EventListener> void addListener(Class<L> listenerClass, String aspectName, L listener) {
+ this.check(listenerClass, aspectName);
+ super.addListener(listenerClass, aspectName, listener);
+ }
+
+ @Override
+ protected synchronized <L extends EventListener> void addListener(Class<L> listenerClass, L listener) {
+ this.check(listenerClass);
+ super.addListener(listenerClass, listener);
+ }
+
+ @Override
+ protected synchronized <L extends EventListener> void removeListener(Class<L> listenerClass, String aspectName, L listener) {
+ this.check(listenerClass, aspectName);
+ super.removeListener(listenerClass, aspectName, listener);
+ }
+
+ @Override
+ protected synchronized <L extends EventListener> void removeListener(Class<L> listenerClass, L listener) {
+ this.check(listenerClass);
+ super.removeListener(listenerClass, listener);
+ }
+
+ @Override
+ protected <L extends EventListener> boolean hasAnyListeners(Class<L> listenerClass, String aspectName) {
+ this.check(listenerClass, aspectName);
+ return super.hasAnyListeners(listenerClass, aspectName);
+ }
+
+ @Override
+ protected <L extends EventListener> boolean hasAnyListeners(Class<L> 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}.
+ * <p>
+ * Subclasses must implement the following methods:<ul>
+ * <li>{@link #engageModel()}<p>
+ * implement this method to add the appropriate listener to the underlying model
+ * <li>{@link #disengageModel()}<p>
+ * implement this method to remove the appropriate listener from the underlying model
+ * </ul>
+ */
+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}.
+ * <p>
+ * Subclasses must implement the following methods:<ul>
+ * <li>{@link #engageModel()}<p>
+ * implement this method to add the appropriate listener to the underlying model
+ * <li>{@link #disengageModel()}<p>
+ * implement this method to remove the appropriate listener from the underlying model
+ * </ul>
+ */
+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}.
+ * <p>
+ * Subclasses must implement the following methods:<ul>
+ * <li>{@link #engageModel()}<p>
+ * implement this method to add the appropriate listener to the underlying model
+ * <li>{@link #disengageModel()}<p>
+ * implement this method to remove the appropriate listener from the underlying model
+ * </ul>
+ */
+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.
+ * <p>
+ * Subclasses must implement:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current model
+ * <li>{@link #engageModel_()}<p>
+ * to start listening to the adapted model
+ * <li>{@link #disengageModel_()}<p>
+ * to stop listening to the adapted model
+ * </ul>
+ * Subclasses can call {@link #propertyChanged()} whenever the calculated
+ * value of the property changes (as determined by the subclass).
+ */
+public abstract class AbstractPropertyValueModelAdapter<V>
+ extends AbstractPropertyValueModel
+ implements PropertyValueModel<V>
+{
+ /**
+ * 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<T>
+ extends AbstractModel
+ implements TreeNodeValueModel<T>
+{
+
+
+ // ********** 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<T>[] path() {
+ List<TreeNodeValueModel<T>> 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<TreeNodeValueModel<T>> backPath() {
+ return new ChainIterator<TreeNodeValueModel<T>>(this) {
+ @Override
+ protected TreeNodeValueModel<T> nextLink(TreeNodeValueModel<T> currentLink) {
+ return currentLink.parent();
+ }
+ };
+ }
+
+ public TreeNodeValueModel<T> child(int index) {
+ return this.childrenModel().get(index);
+ }
+
+ public int childrenSize() {
+ return this.childrenModel().size();
+ }
+
+ public int indexOfChild(TreeNodeValueModel<T> child) {
+ ListValueModel<TreeNodeValueModel<T>> 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<T> other = (AbstractTreeNodeValueModel<T>) 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. <code>VALUE</code>).
+ * <p>
+ * 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<S>
+ 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<? extends S> 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<S>(subject));
+ }
+
+ /**
+ * Construct an aspect adapter for the specified subject holder.
+ * The subject holder cannot be null.
+ */
+ protected AspectAdapter(PropertyValueModel<? extends S> 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<? extends EventListener> 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<S> source, Class<? extends EventListener> validListenerClass, String validAspectName) {
+ super(source, validListenerClass, validAspectName);
+ }
+
+ protected boolean hasNoListeners() {
+ return this.hasNoListeners(this.validListenerClass, this.validAspectName);
+ }
+
+
+ // ********** overrides **********
+
+ @Override
+ protected synchronized <T extends EventListener> void addListener(Class<T> listenerClass, T listener) {
+ if (this.hasNoListeners()) {
+ AspectAdapter.this.engageModels();
+ }
+ super.addListener(listenerClass, listener);
+ }
+
+ @Override
+ protected synchronized <T extends EventListener> void addListener(Class<T> listenerClass, String aspectName, T listener) {
+ if (this.hasNoListeners()) {
+ AspectAdapter.this.engageModels();
+ }
+ super.addListener(listenerClass, aspectName, listener);
+ }
+
+ @Override
+ protected synchronized <T extends EventListener> void removeListener(Class<T> listenerClass, T listener) {
+ super.removeListener(listenerClass, listener);
+ if (this.hasNoListeners()) {
+ AspectAdapter.this.disengageModels();
+ }
+ }
+
+ @Override
+ protected synchronized <T extends EventListener> void removeListener(Class<T> 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.
+ * <p>
+ * The typical subclass will override the following methods:<ul>
+ * <li>{@link #engageSubject_()}<p>
+ * implement this method to add the appropriate listener to the subject
+ * <li>{@link #disengageSubject_()}<p>
+ * implement this method to remove the appropriate listener from the subject
+ * <li>{@link #getIterable()}<p>
+ * 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
+ * <li>{@link #size_()}<p>
+ * override this method to improve performance; it does not need to be overridden if
+ * {@link #size()} is overridden and its behavior changed
+ * <li>{@link #iterator_()}<p>
+ * 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
+ * <li>{@link #iterator()}<p>
+ * override this method only if returning an empty iterator when the
+ * subject is null is unacceptable
+ * <li>{@link #size()}<p>
+ * override this method only if returning a zero when the
+ * subject is null is unacceptable
+ * </ul>
+ * To notify listeners, subclasses can call {@link #collectionChanged()}
+ * whenever the aspect has changed.
+ */
+public abstract class AspectCollectionValueModelAdapter<S, E>
+ extends AspectAdapter<S>
+ implements CollectionValueModel<E>
+{
+
+ // ********** constructors **********
+
+ /**
+ * Construct a collection value model adapter for an aspect of the
+ * specified subject.
+ */
+ protected AspectCollectionValueModelAdapter(PropertyValueModel<? extends S> subjectHolder) {
+ super(subjectHolder);
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ /**
+ * Return the elements of the subject's collection aspect.
+ */
+ public Iterator<E> iterator() {
+ return (this.subject == null) ? EmptyIterator.<E>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<E> 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<E> 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<? extends EventListener> 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<E> newCollection = (Collection<E>) newValue;
+ this.fireCollectionChanged(VALUES, newCollection);
+ }
+
+ protected void collectionChanged() {
+ this.fireCollectionChanged(VALUES, this.buildValueCollection());
+ }
+
+ protected Collection<E> 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.
+ * <p>
+ * The typical subclass will override the following methods:<ul>
+ * <li>{@link #engageSubject_()}<p>
+ * implement this method to add the appropriate listener to the subject
+ * <li>{@link #disengageSubject_()}<p>
+ * implement this method to remove the appropriate listener from the subject
+ * <li>{@link #getListIterable()}<p>
+ * 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
+ * <li>{@link #get(int)}<p>
+ * override this method to improve performance
+ * <li>{@link #size_()}<p>
+ * override this method to improve performance; it does not need to be overridden if
+ * {@link #size()} is overridden and its behavior changed
+ * <li>{@link #toArray_()}<p>
+ * override this method to improve performance; it does not need to be overridden if
+ * {@link #toArray()} is overridden and its behavior changed
+ * <li>{@link #listIterator_()}<p>
+ * 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
+ * <li>{@link #listIterator()}<p>
+ * override this method only if returning an empty list iterator when the
+ * subject is null is unacceptable
+ * <li>{@link #size()}<p>
+ * override this method only if returning a zero when the
+ * subject is null is unacceptable
+ * <li>{@link #toArray()}<p>
+ * override this method only if returning an empty array when the
+ * subject is null is unacceptable
+ * </ul>
+ * To notify listeners, subclasses can call {@link #listChanged()}
+ * whenever the aspect has changed.
+ */
+public abstract class AspectListValueModelAdapter<S, E>
+ extends AspectAdapter<S>
+ implements ListValueModel<E>
+{
+ 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<? extends S> subjectHolder) {
+ super(subjectHolder);
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ /**
+ * Return the elements of the subject's list aspect.
+ */
+ public ListIterator<E> iterator() {
+ return this.listIterator();
+ }
+
+ /**
+ * Return the elements of the subject's list aspect.
+ */
+ public ListIterator<E> listIterator() {
+ return (this.subject == null) ? EmptyListIterator.<E>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<E> 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<E> 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<E> getValue() {
+ return this.buildValueList();
+ }
+
+ @Override
+ protected Class<? extends EventListener> 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<E> newList = (List<E>) newValue;
+ this.fireListChanged(LIST_VALUES, newList);
+ }
+
+ protected void listChanged() {
+ this.fireListChanged(LIST_VALUES, this.buildValueList());
+ }
+
+ protected List<E> 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.
+ * <p>
+ * The typical subclass will override the following methods:<ul>
+ * <li>{@link #engageSubject_()}<p>
+ * implement this method to add the appropriate listener to the subject
+ * <li>{@link #disengageSubject_()}<p>
+ * implement this method to remove the appropriate listener from the subject
+ * <li>{@link #buildValue_()}<p>
+ * 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
+ * <li>{@link #setValue_(Object)}<p>
+ * override this method if the client code needs to <em>set</em> the value of
+ * the subject's aspect; oftentimes, though, the client code (e.g. UI)
+ * will need only to <em>get</em> the value; it does not need to be
+ * overridden if {@link #setValue(Object)} is overridden and its behavior changed
+ * <li>{@link #buildValue()}<p>
+ * override this method only if returning a <code>null</code> value when
+ * the subject is <code>null</code> is unacceptable
+ * <li>{@link #setValue(Object)}<p>
+ * override this method only if something must be done when the subject
+ * is <code>null</code> (e.g. throw an exception)
+ * </ul>
+ * To notify listeners, subclasses can call {@link #propertyChanged()}
+ * whenever the aspect has changed.
+ */
+public abstract class AspectPropertyValueModelAdapter<S, V>
+ extends AspectAdapter<S>
+ implements WritablePropertyValueModel<V>
+{
+ /**
+ * 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<? extends S> 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<? extends EventListener> 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.
+ * <p>
+ * The typical subclass will override the following methods:<ul>
+ * <li>{@link #engageSubject_()}<p>
+ * implement this method to add the appropriate listener to the subject
+ * <li>{@link #disengageSubject_()}<p>
+ * implement this method to remove the appropriate listener from the subject
+ * <li>{@link #nodes_()}<p>
+ * 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
+ * <li>{@link #nodes()}<p>
+ * override this method only if returning an empty iterator when the
+ * subject is null is unacceptable
+ * </ul>
+ * To notify listeners, subclasses can call {@link #treeChanged()}
+ * whenever the aspect has changed.
+ */
+public abstract class AspectTreeValueModelAdapter<S, E>
+ extends AspectAdapter<S>
+ implements TreeValueModel<E>
+{
+
+ // ********** constructors **********
+
+ /**
+ * Construct a tree value model adapter for an aspect of the
+ * specified subject.
+ */
+ protected AspectTreeValueModelAdapter(PropertyValueModel<? extends S> subjectHolder) {
+ super(subjectHolder);
+ }
+
+
+ // ********** TreeValueModel implementation **********
+
+ /**
+ * Return the nodes of the subject's tree aspect.
+ */
+ public Iterator<E> nodes() {
+ return (this.subject == null) ? EmptyIterator.<E>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<E> nodes_() {
+ throw new RuntimeException("This method was not overridden."); //$NON-NLS-1$
+ }
+
+
+ // ********** AspectAdapter implementation **********
+
+ @Override
+ protected Object getValue() {
+ return this.buildValueCollection();
+ }
+
+ @Override
+ protected Class<? extends EventListener> 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<E> newNodes = (Collection<E>) newValue;
+ this.fireTreeChanged(NODES, newNodes);
+ }
+
+ protected void treeChanged() {
+ this.fireTreeChanged(NODES, this.buildValueCollection());
+ }
+
+ protected Collection<E> 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 <code>BufferedWritablePropertyValueModel</code> 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.
+ * <p>
+ * 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:<ul>
+ * <li>pressing the "OK" button will trigger an "accept" and close the dialog
+ * <li>pressing the "Cancel" button will simply close the dialog,
+ * dropping the "buffered" values into the bit bucket
+ * <li>pressing the "Apply" button will trigger an "accept" and leave the dialog open
+ * <li>pressing the "Restore" button will trigger a "reset" and leave the dialog open
+ * </ul>
+ * <p>
+ * 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<T>
+ extends PropertyValueModelWrapper<T>
+ implements WritablePropertyValueModel<T>
+{
+
+ /**
+ * 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<Boolean> 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<T> valueHolder, PropertyValueModel<Boolean> 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:<ul>
+ * <li>If it is now true, "accept" the buffered value and push
+ * it to the wrapped value holder.
+ * <li>If it is now false, "reset" the buffered value to its original value.
+ * </ul>
+ */
+ 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<T>}.
+ */
+ @SuppressWarnings("unchecked")
+ protected WritablePropertyValueModel<T> getValueHolder() {
+ return (WritablePropertyValueModel<T>) this.valueHolder;
+ }
+
+
+ // ********** inner class **********
+
+ /**
+ * <code>Trigger</code> is a special property value model that only maintains its
+ * value (of <code>true</code> or <code>false</code>) during the change notification caused by
+ * {@link #setValue(T)}. In other words, a <code>Trigger</code>
+ * only has a valid value when it is being set.
+ */
+ public static class Trigger extends SimplePropertyValueModel<Boolean> {
+
+
+ // ********** 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 <code>null</code> 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:<ul>
+ * <li><code>true</code> indicates "accept"
+ * <li><code>false</code> indicates "reset"
+ * </ul>
+ */
+ public void setValue(boolean value) {
+ this.setValue(Boolean.valueOf(value));
+ }
+
+ /**
+ * Return the trigger's value:<ul>
+ * <li><code>true</code> indicates "accept"
+ * <li><code>false</code> indicates "reset"
+ * </ul>
+ * 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 <code>true</code>).
+ */
+ public void accept() {
+ this.setValue(true);
+ }
+
+ /**
+ * Return whether the trigger has been accepted
+ * (i.e. its value was changed to <code>true</code>).
+ * This method can only be invoked during change notification.
+ */
+ public boolean isAccepted() {
+ return this.booleanValue();
+ }
+
+ /**
+ * Reset the trigger (i.e. set its value to <code>false</code>).
+ */
+ public void reset() {
+ this.setValue(false);
+ }
+
+ /**
+ * Return whether the trigger has been reset
+ * (i.e. its value was changed to <code>false</code>).
+ * 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 <code>CachingTransformationPropertyValueModel</code> 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.
+ * <p>
+ * As an alternative to building a {@link Transformer},
+ * a subclass of <code>CachingTransformationPropertyValueModel</code> 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<T1, T2>
+ extends TransformationPropertyValueModel<T1, T2>
+{
+ /**
+ * 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<? extends T1> valueHolder) {
+ super(valueHolder);
+ }
+
+ /**
+ * Construct an property value model with the specified nested
+ * property value model and transformer.
+ */
+ public CachingTransformationPropertyValueModel(PropertyValueModel<? extends T1> valueHolder, Transformer<T1, T2> 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 <code>CachingTransformationWritablePropertyValueModel<code> 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<T1, T2>
+ extends TransformationWritablePropertyValueModel<T1, T2>
+{
+ /**
+ * 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<T1> valueHolder) {
+ super(valueHolder);
+ }
+
+ /**
+ * Construct a writable property value model with the specified nested
+ * writable property value model and bidi transformer.
+ */
+ public CachingTransformationWritablePropertyValueModel(WritablePropertyValueModel<T1> valueHolder, BidiTransformer<T1, T2> 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.
+ * <p>
+ * Subclasses must override:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current model
+ * </ul>
+ * Subclasses might want to override the following methods
+ * to improve performance (by not recalculating the value, if possible):<ul>
+ * <li>{@link #modelChanged(ChangeEvent event)}
+ * <li>{@link #buildChangeListener()}
+ * </ul>
+ */
+public abstract class ChangePropertyValueModelAdapter<T>
+ extends AbstractPropertyValueModelAdapter<T>
+{
+ /** 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.
+ * <p>
+ * The typical subclass will override the following methods (see the descriptions
+ * in {@link AspectCollectionValueModelAdapter}):<ul>
+ * <li>{@link #getIterable()}
+ * <li>{@link #size_()}
+ * <li>{@link #iterator_()}
+ * <li>{@link #iterator()}
+ * <li>{@link #size()}
+ * </ul>
+ */
+public abstract class CollectionAspectAdapter<S extends Model, E>
+ extends AspectCollectionValueModelAdapter<S, E>
+{
+ /**
+ * 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<S>(subject), collectionNames);
+ }
+
+ /**
+ * Construct a collection aspect adapter for the specified subject holder
+ * and collections.
+ */
+ protected CollectionAspectAdapter(PropertyValueModel<? extends S> 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<? extends S> subjectHolder, Collection<String> 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<? extends S> 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.
+ * <p>
+ * To maintain a reasonably consistent appearance to client code, we
+ * keep an internal list somewhat in synch with the wrapped collection.
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends AbstractListValueModel
+ implements ListValueModel<E>
+{
+ /** The wrapped collection value model. */
+ protected final CollectionValueModel<? extends E> 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<E> list;
+
+
+ // ********** constructors **********
+
+ /**
+ * Wrap the specified collection value model.
+ */
+ public CollectionListValueModelAdapter(CollectionValueModel<? extends E> collectionHolder) {
+ super();
+ if (collectionHolder == null) {
+ throw new NullPointerException();
+ }
+ this.collectionHolder = collectionHolder;
+ this.collectionChangeListener = this.buildCollectionChangeListener();
+ this.list = new ArrayList<E>(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<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ReadOnlyListIterator<E>(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<E> getItems(CollectionAddEvent event) {
+ return (Iterable<E>) 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<E> getItems(CollectionRemoveEvent event) {
+ return (Iterable<E>) 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<? extends E> 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.
+ * <p>
+ * Subclasses must override:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current collection value
+ * </ul>
+ * Subclasses might want to override the following methods
+ * to improve performance (by not recalculating the value, if possible):<ul>
+ * <li>{@link #itemsAdded(CollectionAddEvent event)}
+ * <li>{@link #itemsRemoved(CollectionRemoveEvent event)}
+ * <li>{@link #collectionCleared(CollectionClearEvent event)}
+ * <li>{@link #collectionChanged(CollectionChangeEvent event)}
+ * </ul>
+ */
+public abstract class CollectionPropertyValueModelAdapter<V>
+ extends AbstractPropertyValueModelAdapter<V>
+{
+ /** 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<E>
+ extends AbstractCollectionValueModel
+{
+ /** The wrapped collection value model. */
+ protected final CollectionValueModel<? extends E> 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<? extends E> 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<E> getItems(CollectionAddEvent event) {
+ return (Iterable<E>) event.getItems();
+ }
+
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getItems(CollectionRemoveEvent event) {
+ return (Iterable<E>) 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 <code>CompositeBooleanPropertyValueModel</code> 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.
+ * <p>
+ * If there are <em>no</em> boolean {@link PropertyValueModel}s, the composite
+ * will return its default value; which, by default, is <code>null</code>.
+ * <p>
+ * <strong>NB:</strong> None of the wrapped boolean models can return a
+ * <code>null</code> 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 <code>null</code>.
+ */
+public class CompositeBooleanPropertyValueModel
+ extends CompositePropertyValueModel<Boolean>
+{
+ /**
+ * 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 <code>null</code>.
+ */
+ 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
+ * <code>null</code>.
+ */
+ public static CompositeBooleanPropertyValueModel and(PropertyValueModel<Boolean>... 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<Boolean>... 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
+ * <code>null</code>.
+ */
+ public static <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel and(Collection<E> 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 <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel and(Boolean defaultValue, Collection<E> 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
+ * <code>null</code>.
+ */
+ public static CompositeBooleanPropertyValueModel and(CollectionValueModel<? extends PropertyValueModel<Boolean>> 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<? extends PropertyValueModel<Boolean>> 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
+ * <code>null</code>.
+ */
+ public static CompositeBooleanPropertyValueModel or(PropertyValueModel<Boolean>... 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<Boolean>... 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
+ * <code>null</code>.
+ */
+ public static <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel or(Collection<E> 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 <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel or(Boolean defaultValue, Collection<E> 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
+ * <code>null</code>.
+ */
+ public static CompositeBooleanPropertyValueModel or(CollectionValueModel<? extends PropertyValueModel<Boolean>> 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<? extends PropertyValueModel<Boolean>> 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
+ * <code>null</code>.
+ */
+ public CompositeBooleanPropertyValueModel(Adapter adapter, PropertyValueModel<Boolean>... 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<Boolean>... 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
+ * <code>null</code>.
+ */
+ public <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel(Adapter adapter, Collection<E> 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 <E extends PropertyValueModel<Boolean>> CompositeBooleanPropertyValueModel(Adapter adapter, Boolean defaultValue, Collection<E> 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
+ * <code>null</code>.
+ */
+ public CompositeBooleanPropertyValueModel(Adapter adapter, CollectionValueModel<? extends PropertyValueModel<Boolean>> 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<? extends PropertyValueModel<Boolean>> 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<Boolean> getBooleans() {
+ return new TransformationIterable<PropertyValueModel<Boolean>, Boolean>(this.getCollectionModel()) {
+ @Override
+ protected Boolean transform(PropertyValueModel<Boolean> booleanModel) {
+ return booleanModel.getValue();
+ }
+ };
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected CollectionValueModel<? extends PropertyValueModel<Boolean>> getCollectionModel() {
+ return (CollectionValueModel<? extends PropertyValueModel<Boolean>>) 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<Boolean> booleans);
+ }
+
+ /**
+ * Return true if all the booleans are true; otherwise return false.
+ */
+ public static final Adapter AND_ADAPTER = new Adapter() {
+ public Boolean buildValue(Iterable<Boolean> 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<Boolean> 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 <code>CompositeCollectionValueModel</code> 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.
+ * <p>
+ * <strong>NB:</strong> The wrapped collection must be an "identity set" that does not
+ * contain the same item twice or this class will throw an exception.
+ * <p>
+ * Terminology:<ul>
+ * <li><em>sources</em> - the items in the wrapped collection value model; these
+ * are converted into component CVMs by the transformer
+ * <li><em>component CVMs</em> - the component collection value models that are combined
+ * by this composite collection value model
+ * <li><em>items</em> - the items held by the component CVMs
+ * </ul>
+ */
+public class CompositeCollectionValueModel<E1, E2>
+ extends CollectionValueModelWrapper<E1>
+ implements CollectionValueModel<E2>
+{
+ /**
+ * This is the (optional) user-supplied object that transforms
+ * the items in the wrapped collection to collection value models.
+ */
+ private final Transformer<E1, CollectionValueModel<E2>> 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<E1, CollectionValueModel<E2>> componentCVMs =
+ new IdentityHashMap<E1, CollectionValueModel<E2>>();
+
+ /**
+ * 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<CollectionValueModel<E2>, ArrayList<E2>> collections =
+ new IdentityHashMap<CollectionValueModel<E2>, ArrayList<E2>>();
+
+ /** 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<ul>
+ * <li> the wrapped collection value model already contains other
+ * collection value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeCollectionValueModel(CollectionValueModel<? extends E1> collectionHolder) {
+ this(collectionHolder, Transformer.Null.<E1, CollectionValueModel<E2>>instance());
+ }
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * collection value model and transformer.
+ */
+ public CompositeCollectionValueModel(CollectionValueModel<? extends E1> collectionHolder, Transformer<E1, CollectionValueModel<E2>> 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<ul>
+ * <li> the wrapped collection value model already contains other
+ * collection value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeCollectionValueModel(ListValueModel<? extends E1> listHolder) {
+ this(new ListCollectionValueModelAdapter<E1>(listHolder));
+ }
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * list value model and transformer.
+ */
+ public CompositeCollectionValueModel(ListValueModel<? extends E1> listHolder, Transformer<E1, CollectionValueModel<E2>> transformer) {
+ this(new ListCollectionValueModelAdapter<E1>(listHolder), transformer);
+ }
+
+ /**
+ * Construct a collection value model with the specified, unchanging, wrapped
+ * collection. Use this constructor if<ul>
+ * <li> the wrapped collection value model already contains other
+ * collection value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeCollectionValueModel(Collection<? extends E1> collection) {
+ this(new StaticCollectionValueModel<E1>(collection));
+ }
+
+ /**
+ * Construct a collection value model with the specified, unchanging, wrapped
+ * collection and transformer.
+ */
+ public CompositeCollectionValueModel(Collection<? extends E1> collection, Transformer<E1, CollectionValueModel<E2>> transformer) {
+ this(new StaticCollectionValueModel<E1>(collection), transformer);
+ }
+
+ /**
+ * Construct a collection value model with the specified, unchanging, wrapped
+ * collection. Use this constructor if<ul>
+ * <li> the wrapped collection value model already contains other
+ * collection value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeCollectionValueModel(E1... collection) {
+ this(new StaticCollectionValueModel<E1>(collection));
+ }
+
+ /**
+ * Construct a collection value model with the specified, unchanging, wrapped
+ * collection and transformer.
+ */
+ public CompositeCollectionValueModel(E1[] collection, Transformer<E1, CollectionValueModel<E2>> transformer) {
+ this(new StaticCollectionValueModel<E1>(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<E2> iterator() {
+ return new CompositeIterator<E2>(this.buildCollectionsIterators());
+ }
+
+ protected Iterator<Iterator<E2>> buildCollectionsIterators() {
+ return new TransformationIterator<ArrayList<E2>, Iterator<E2>>(this.collections.values().iterator()) {
+ @Override
+ protected Iterator<E2> transform(ArrayList<E2> 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.<E2>instance());
+ }
+ }
+
+ @Override
+ protected void disengageModel() {
+ super.disengageModel();
+ // stop listening to the components...
+ for (CollectionValueModel<E2> 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<E2> addedItems = new ArrayList<E2>();
+ 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<E2> addedItems) {
+ CollectionValueModel<E2> 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<E2> componentCollection = new ArrayList<E2>(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<E2> componentCVM, ArrayList<E2> 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<E2> removedItems = new ArrayList<E2>();
+ 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<E2> removedItems) {
+ CollectionValueModel<E2> componentCVM = this.componentCVMs.remove(source);
+ if (componentCVM == null) {
+ throw new IllegalStateException("missing component: " + source); //$NON-NLS-1$
+ }
+ componentCVM.removeCollectionChangeListener(VALUES, this.componentCVMListener);
+ ArrayList<E2> 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<E2> 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<E1> copy = new ArrayList<E1>(this.componentCVMs.keySet());
+ for (E1 source : copy) {
+ this.removeComponentSource(source, NullList.<E2>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.
+ * <p>
+ * This method can be overridden by a subclass as an
+ * alternative to building a {@link Transformer}.
+ */
+ protected CollectionValueModel<E2> 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<E2> 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<E2> 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<E2> componentCollection = this.collections.get(this.componentCVM(event));
+ ArrayList<E2> removedItems = new ArrayList<E2>(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<E2> componentCVM = this.componentCVM(event);
+ ArrayList<E2> 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<E2> getComponentItems(CollectionAddEvent event) {
+ return (Iterable<E2>) event.getItems();
+ }
+
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E2> getComponentItems(CollectionRemoveEvent event) {
+ return (Iterable<E2>) event.getItems();
+ }
+
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected CollectionValueModel<E2> componentCVM(CollectionEvent event) {
+ return (CollectionValueModel<E2>) 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 <code>CompositeListValueModel</code> 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.
+ * <p>
+ * Terminology:<ul>
+ * <li><em>sources</em> - the items in the wrapped list value model; these
+ * are converted into component LVMs by the transformer
+ * <li><em>component LVMs</em> - the component list value models that are combined
+ * by this composite list value model
+ * <li><em>items</em> - the items held by the component LVMs
+ * </ul>
+ */
+public class CompositeListValueModel<E1, E2>
+ extends ListValueModelWrapper<E1>
+ implements ListValueModel<E2>
+{
+ /**
+ * This is the (optional) user-supplied object that transforms
+ * the items in the wrapped list to list value models.
+ */
+ private final Transformer<E1, ListValueModel<E2>> transformer;
+
+ /**
+ * Cache of the sources, component LVMs, lists.
+ */
+ private final ArrayList<Info> infoList = new ArrayList<Info>();
+ protected class Info {
+ // the object passed to the transformer
+ final E1 source;
+ // the list value model generated by the transformer
+ final ListValueModel<E2> componentLVM;
+ // cache of the items held by the component LVM
+ final ArrayList<E2> items;
+ // the component LVM's beginning index within the composite LVM
+ int begin;
+ protected Info(E1 source, ListValueModel<E2> componentLVM, ArrayList<E2> 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<ul>
+ * <li> the wrapped list value model already contains other
+ * list value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeListValueModel(ListValueModel<? extends E1> listHolder) {
+ this(listHolder, Transformer.Null.<E1, ListValueModel<E2>>instance());
+ }
+
+ /**
+ * Construct a list value model with the specified wrapped
+ * list value model and transformer.
+ */
+ public CompositeListValueModel(ListValueModel<? extends E1> listHolder, Transformer<E1, ListValueModel<E2>> 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<ul>
+ * <li> the wrapped list value model already contains other
+ * list value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeListValueModel(List<? extends E1> list) {
+ this(new StaticListValueModel<E1>(list));
+ }
+
+ /**
+ * Construct a list value model with the specified, unchanging, wrapped
+ * list and transformer.
+ */
+ public CompositeListValueModel(List<? extends E1> list, Transformer<E1, ListValueModel<E2>> transformer) {
+ this(new StaticListValueModel<E1>(list), transformer);
+ }
+
+ /**
+ * Construct a list value model with the specified, unchanging, wrapped
+ * list. Use this constructor if<ul>
+ * <li> the wrapped list value model already contains other
+ * list value models, or
+ * <li> you want to override {@link #transform(E1)}
+ * instead of building a {@link Transformer}
+ * </ul>
+ */
+ public CompositeListValueModel(E1... list) {
+ this(new StaticListValueModel<E1>(list));
+ }
+
+ /**
+ * Construct a list value model with the specified, unchanging, wrapped
+ * list and transformer.
+ */
+ public CompositeListValueModel(E1[] list, Transformer<E1, ListValueModel<E2>> transformer) {
+ this(new StaticListValueModel<E1>(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<E2> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E2> listIterator() {
+ return new ReadOnlyCompositeListIterator<E2>(this.buildListsIterators());
+ }
+
+ protected ListIterator<ListIterator<E2>> buildListsIterators() {
+ return new TransformationListIterator<Info, ListIterator<E2>>(this.infoList.listIterator()) {
+ @Override
+ protected ListIterator<E2> 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<? extends E1> addedSources, int addedSourcesSize, boolean fireEvent) {
+ ArrayList<Info> newInfoList = new ArrayList<Info>(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<E2> componentLVM = this.transform(source);
+ componentLVM.addListChangeListener(LIST_VALUES, this.componentLVMListener);
+ ArrayList<E2> items = new ArrayList<E2>(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<E2> newItems = new ArrayList<E2>(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<Info> subList = this.infoList.subList(removedSourcesIndex, removedSourcesIndex + removedSourcesSize);
+ ArrayList<Info> removedInfoList = new ArrayList<Info>(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<E2> removedItems = new ArrayList<E2>(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<? extends E1> 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<E1>(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<? extends E1> buildSubListHolder(int fromIndex) {
+ int listHolderSize = this.listHolder.size();
+ return CollectionTools.list(this.listHolder, listHolderSize).subList(fromIndex, listHolderSize);
+ }
+
+ protected Iterable<? extends E1> 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.
+ * <p>
+ * This method can be overridden by a subclass as an
+ * alternative to building a {@link Transformer}.
+ */
+ protected ListValueModel<E2> transform(E1 value) {
+ return this.transformer.transform(value);
+ }
+
+ /**
+ * Return the index of the specified component LVM.
+ */
+ protected int indexOf(ListValueModel<E2> 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<E2> 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<E2> items = new ArrayList<E2>(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<E2> subList = info.items.subList(newItemsSize, oldItemsSize);
+ ArrayList<E2> removedItems = new ArrayList<E2>(subList); // make a copy
+ subList.clear();
+ this.fireItemsRemoved(LIST_VALUES, info.begin + newItemsSize, removedItems);
+ return;
+ }
+
+ // newItemsSize > oldItemsSize
+ ArrayList<E2> addedItems = new ArrayList<E2>(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<E2> getComponentItems(ListAddEvent event) {
+ return (Iterable<E2>) event.getItems();
+ }
+
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E2> getComponentItems(ListReplaceEvent event) {
+ return (Iterable<E2>) event.getNewItems();
+ }
+
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected ListValueModel<E2> getComponentLVM(ListEvent event) {
+ return (ListValueModel<E2>) 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 <code>CompositePropertyValueModel</code> adapts a
+ * {@link CollectionValueModel} holding other {@link PropertyValueModel}s
+ * to a single {@link PropertyValueModel}.
+ * <p>
+ * Subclasses must implement:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * component values
+ * </ul>
+ * <strong>NB:</strong> The wrapped collection must not contain any duplicates
+ * or this class will throw an exception.
+ */
+public abstract class CompositePropertyValueModel<V>
+ extends CollectionPropertyValueModelAdapter<V>
+{
+ /**
+ * 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<PropertyValueModel<?>> componentPVMs =
+ new IdentityHashBag<PropertyValueModel<?>>();
+
+ /**
+ * 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 <E extends PropertyValueModel<?>> CompositePropertyValueModel(Collection<E> collection) {
+ this(new StaticCollectionValueModel<E>(collection));
+ }
+
+ /**
+ * Construct a property value model that is a composite of the specified
+ * property value models.
+ */
+ public CompositePropertyValueModel(CollectionValueModel<? extends PropertyValueModel<?>> 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 <E extends PropertyValueModel<?>> void addComponentPVMs(Iterable<E> 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 <E extends PropertyValueModel<?>> void removeComponentPVMs(Iterable<E> 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<PropertyValueModel<?>> copy = new ArrayList<PropertyValueModel<?>>(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<? extends }{@link PropertyValueModel}{@code<?>>}.
+ */
+ // minimize scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected CollectionValueModel<? extends PropertyValueModel<?>> getCollectionModel() {
+ return (CollectionValueModel<? extends PropertyValueModel<?>>) this.collectionModel;
+ }
+
+ /**
+ * Our constructor accepts only a {@link CollectionValueModel}{@code<? extends }{@link PropertyValueModel}{@code<?>>}.
+ */
+ @SuppressWarnings("unchecked")
+ protected Iterable<? extends PropertyValueModel<?>> getItems(CollectionAddEvent event) {
+ return (Iterable<? extends PropertyValueModel<?>>) event.getItems();
+ }
+
+ /**
+ * Our constructor accepts only a {@link CollectionValueModel}{@code<? extends }{@link PropertyValueModel}{@code<?>>}.
+ */
+ @SuppressWarnings("unchecked")
+ protected Iterable<? extends PropertyValueModel<?>> getItems(CollectionRemoveEvent event) {
+ return (Iterable<? extends PropertyValueModel<?>>) 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.
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends ListValueModelWrapper<E>
+ implements ListValueModel<E>
+{
+ /** the items "prepended" to the wrapped list */
+ protected List<E> prefix;
+
+ /** the items "appended" to the wrapped list */
+ protected List<E> suffix;
+
+
+ // ********** lots o' constructors **********
+
+ /**
+ * Extend the specified list with a prefix and suffix.
+ */
+ public ExtendedListValueModelWrapper(List<? extends E> prefix, ListValueModel<? extends E> listHolder, List<? extends E> suffix) {
+ super(listHolder);
+ this.prefix = new ArrayList<E>(prefix);
+ this.suffix = new ArrayList<E>(suffix);
+ }
+
+ /**
+ * Extend the specified list with a prefix and suffix.
+ */
+ public ExtendedListValueModelWrapper(E prefix, ListValueModel<? extends E> 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<? extends E> prefix, ListValueModel<? extends E> listHolder) {
+ super(listHolder);
+ this.prefix = new ArrayList<E>(prefix);
+ this.suffix = Collections.emptyList();
+ }
+
+ /**
+ * Extend the specified list with a prefix.
+ */
+ public ExtendedListValueModelWrapper(E prefix, ListValueModel<? extends E> listHolder) {
+ super(listHolder);
+ this.prefix = Collections.singletonList(prefix);
+ this.suffix = Collections.emptyList();
+ }
+
+ /**
+ * Extend the specified list with a suffix.
+ */
+ public ExtendedListValueModelWrapper(ListValueModel<? extends E> listHolder, List<? extends E> suffix) {
+ super(listHolder);
+ this.prefix = Collections.emptyList();
+ this.suffix = new ArrayList<E>(suffix);
+ }
+
+ /**
+ * Extend the specified list with a suffix.
+ */
+ public ExtendedListValueModelWrapper(ListValueModel<? extends E> 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<? extends E> listHolder) {
+ super(listHolder);
+ this.prefix = Collections.singletonList(null);
+ this.suffix = Collections.emptyList();
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ReadOnlyListIterator<E>(this.listIterator_());
+ }
+
+ @SuppressWarnings("unchecked")
+ protected ListIterator<E> listIterator_() {
+ return new ReadOnlyCompositeListIterator<E>(
+ 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<E> list = new ArrayList<E>(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<E> prefix) {
+ this.prefix = prefix;
+ this.fireListChanged(LIST_VALUES, this.buildList());
+ }
+
+ public void setSuffix(List<E> suffix) {
+ this.suffix = suffix;
+ this.fireListChanged(LIST_VALUES, this.buildList());
+ }
+
+ private List<E> 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 <code>FilteringCollectionValueModel</code> wraps another
+ * {@link CollectionValueModel} and uses a {@link Filter}
+ * to determine which items in the collection are returned by calls
+ * to {@link #iterator()}.
+ * <p>
+ * 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 <code>.java</code> files).
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends CollectionValueModelWrapper<E>
+ implements CollectionValueModel<E>
+{
+ /** This filters the items in the nested collection. */
+ private Filter<E> filter;
+
+ /** Cache the items that were accepted by the filter */
+ private final Collection<E> filteredItems = new ArrayList<E>();
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * collection value model and a filter that simply accepts every object.
+ */
+ public FilteringCollectionValueModel(CollectionValueModel<? extends E> collectionHolder) {
+ this(collectionHolder, Filter.Null.<E>instance());
+ }
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * collection value model and filter.
+ */
+ public FilteringCollectionValueModel(CollectionValueModel<? extends E> collectionHolder, Filter<E> 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<E> listHolder) {
+ this(new ListCollectionValueModelAdapter<E>(listHolder));
+ }
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * list value model and filter.
+ */
+ public FilteringCollectionValueModel(ListValueModel<E> listHolder, Filter<E> filter) {
+ this(new ListCollectionValueModelAdapter<E>(listHolder), filter);
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return new ReadOnlyIterator<E>(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<E> filter) {
+ this.filter = filter;
+ this.rebuildFilteredItems();
+ }
+
+ /**
+ * Return an iterable that filters the specified iterable.
+ */
+ protected Iterable<E> filter(Iterable<? extends E> items) {
+ return new FilteringIterable<E>(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 <code>FilteringPropertyValueModel</code> wraps another
+ * {@link PropertyValueModel} and uses a {@link Filter}
+ * to determine when the wrapped value is to be returned by calls
+ * to {@link #getValue()}.
+ * <p>
+ * As an alternative to building a {@link Filter}, a subclass
+ * can override {@link #accept(T)}.
+ * <p>
+ * 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 <code>null</code> whenever the wrapped value is not "accepted",
+ * which can be configured and/or overridden ({@link #getDefaultValue()}).
+ */
+public class FilteringPropertyValueModel<T>
+ extends PropertyValueModelWrapper<T>
+ implements PropertyValueModel<T>
+{
+ protected final Filter<T> 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 <code>null</code>.
+ */
+ public FilteringPropertyValueModel(PropertyValueModel<? extends T> valueHolder) {
+ this(valueHolder, Filter.Disabled.<T>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}
+ * <em>and</em> you need to specify
+ * a default value other than <code>null</code>.
+ */
+ public FilteringPropertyValueModel(PropertyValueModel<? extends T> valueHolder, T defaultValue) {
+ this(valueHolder, Filter.Disabled.<T>instance(), defaultValue);
+ }
+
+ /**
+ * Construct a filtering property value model with the specified nested
+ * property value model and filter.
+ * The default value will be <code>null</code>.
+ */
+ public FilteringPropertyValueModel(PropertyValueModel<? extends T> valueHolder, Filter<T> 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<? extends T> valueHolder, Filter<T> 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
+ * <p>
+ * 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 <code>null</code>.
+ */
+ 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 <code>FilteringWritablePropertyValueModel</code> 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)}.
+ * <p>
+ * As an alternative to building a {@link BidiFilter}, a subclass
+ * can override {@link FilteringPropertyValueModel#accept(T) accept(T)} and {@link #reverseAccept(T)}.
+ * <p>
+ * 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 <code>null</code> whenever the wrapped value is not "accepted",
+ * which can be configured and/or overridden ({@link FilteringPropertyValueModel#getDefaultValue() getDefaultValue()}).
+ * <p>
+ * Similarly, if an incoming value is not "reverse accepted", <em>nothing</em>
+ * will passed through to the wrapped value holder, not even <code>null</code>.
+ */
+public class FilteringWritablePropertyValueModel<T>
+ extends FilteringPropertyValueModel<T>
+ implements WritablePropertyValueModel<T>
+{
+
+
+ // ********** 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 <code>null</code>.
+ */
+ public FilteringWritablePropertyValueModel(WritablePropertyValueModel<T> valueHolder) {
+ this(valueHolder, BidiFilter.Disabled.<T>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}.
+ * <em>and</em> you need to specify
+ * a default value other than <code>null</code>.
+ */
+ public FilteringWritablePropertyValueModel(WritablePropertyValueModel<T> valueHolder, T defaultValue) {
+ this(valueHolder, BidiFilter.Disabled.<T>instance(), defaultValue);
+ }
+
+ /**
+ * Construct an property value model with the specified nested
+ * property value model and filter.
+ * The default value will be <code>null</code>.
+ */
+ public FilteringWritablePropertyValueModel(WritablePropertyValueModel<T> valueHolder, BidiFilter<T> filter) {
+ this(valueHolder, filter, null);
+ }
+
+ /**
+ * Construct an property value model with the specified nested
+ * property value model, filter, and default value.
+ */
+ public FilteringWritablePropertyValueModel(WritablePropertyValueModel<T> valueHolder, BidiFilter<T> 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)}.
+ * <p>
+ * 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<T>}.
+ */
+ @SuppressWarnings("unchecked")
+ protected WritablePropertyValueModel<T> getValueHolder() {
+ return (WritablePropertyValueModel<T>) this.valueHolder;
+ }
+
+ /**
+ * Our constructors accept only a bidirectional filter.
+ */
+ protected BidiFilter<T> getFilter() {
+ return (BidiFilter<T>) 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
+ * <em>items</em> 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:<ul>
+ * <li>{@link #engageItem_(Model)}<p>
+ * begin listening to the appropriate aspect of the specified item and call
+ * {@link #itemAspectChanged(EventObject)} whenever the aspect changes
+ * <li>{@link #disengageItem_(Model)}<p>
+ * stop listening to the appropriate aspect of the specified item
+ * </ul>
+ */
+public abstract class ItemAspectListValueModelAdapter<E>
+ extends ListValueModelWrapper<E>
+ implements ListValueModel<E>
+{
+
+ /**
+ * Maintain a counter for each of the items in the
+ * wrapped list holder we are listening to.
+ */
+ protected final IdentityHashMap<E, SimpleIntReference> counters;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the list holder is required.
+ */
+ protected ItemAspectListValueModelAdapter(ListValueModel<? extends E> listHolder) {
+ super(listHolder);
+ this.counters = new IdentityHashMap<E, SimpleIntReference>();
+ }
+
+ /**
+ * Constructor - the collection holder is required.
+ */
+ protected ItemAspectListValueModelAdapter(CollectionValueModel<? extends E> collectionHolder) {
+ this(new CollectionListValueModelAdapter<E>(collectionHolder));
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ReadOnlyListIterator<E>(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<? extends E> 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<? extends E> 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<E> keys = new ArrayList<E>(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<E> keys = new ArrayList<E>(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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+ /** 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<E> 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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+
+ /** 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<E> listHolder, String... collectionNames) {
+ super(listHolder);
+ this.collectionNames = collectionNames;
+ this.itemCollectionListener = this.buildItemCollectionListener();
+ }
+
+ /**
+ * Construct an adapter for the specified item Collections.
+ */
+ public ItemCollectionListValueModelAdapter(CollectionValueModel<E> collectionHolder, String... collectionNames) {
+ this(new CollectionListValueModelAdapter<E>(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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+
+ /** 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<E> listHolder, String... listNames) {
+ super(listHolder);
+ this.listNames = listNames;
+ this.itemListListener = this.buildItemListListener();
+ }
+
+ /**
+ * Construct an adapter for the specified item List aspects.
+ */
+ public ItemListListValueModelAdapter(CollectionValueModel<E> collectionHolder, String... listNames) {
+ this(new CollectionListValueModelAdapter<E>(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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+
+ /** 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<E> listHolder, String... propertyNames) {
+ super(listHolder);
+ this.propertyNames = propertyNames;
+ this.itemPropertyListener = this.buildItemPropertyListener();
+ }
+
+ /**
+ * Construct an adapter for the specified item properties.
+ */
+ public ItemPropertyListValueModelAdapter(CollectionValueModel<E> collectionHolder, String... propertyNames) {
+ this(new CollectionListValueModelAdapter<E>(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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+ /** 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<E> listHolder) {
+ super(listHolder);
+ this.itemStateListener = this.buildItemStateListener();
+ }
+
+ /**
+ * Construct an adapter for the item state.
+ */
+ public ItemStateListValueModelAdapter(CollectionValueModel<E> collectionHolder) {
+ this(new CollectionListValueModelAdapter<E>(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<E>
+ extends ItemAspectListValueModelAdapter<E>
+{
+
+ /** 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<E> listHolder, String... treeNames) {
+ super(listHolder);
+ this.treeNames = treeNames;
+ this.itemTreeListener = this.buildItemTreeListener();
+ }
+
+ /**
+ * Construct an adapter for the specified item trees.
+ */
+ public ItemTreeListValueModelAdapter(CollectionValueModel<E> collectionHolder, String... treeNames) {
+ this(new CollectionListValueModelAdapter<E>(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.
+ * <p>
+ * The typical subclass will override the following methods (see the descriptions
+ * in {@link AspectListValueModelAdapter}):<ul>
+ * <li>{@link #listIterator_()}
+ * <li>{@link #get(int)}
+ * <li>{@link #size_()}
+ * <li>{@link #toArray_()}
+ * <li>{@link #listIterator()}
+ * <li>{@link #size()}
+ * <li>{@link #toArray()}
+ * </ul>
+ */
+public abstract class ListAspectAdapter<S extends Model, E>
+ extends AspectListValueModelAdapter<S, E>
+{
+ /**
+ * 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<S>(subject), listNames);
+ }
+
+ /**
+ * Construct a list aspect adapter for the specified subject holder
+ * and lists.
+ */
+ protected ListAspectAdapter(PropertyValueModel<? extends S> 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<? extends S> subjectHolder, Collection<String> 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<? extends S> 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.
+ * <p>
+ * We keep an internal collection somewhat in synch with the wrapped list.
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends AbstractCollectionValueModel
+ implements CollectionValueModel<E>
+{
+ /** The wrapped list value model. */
+ protected final ListValueModel<? extends E> 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<E> collection;
+
+
+ // ********** constructors **********
+
+ /**
+ * Wrap the specified list value model.
+ */
+ public ListCollectionValueModelAdapter(ListValueModel<? extends E> listHolder) {
+ super();
+ if (listHolder == null) {
+ throw new NullPointerException();
+ }
+ this.listHolder = listHolder;
+ this.listChangeListener = this.buildListChangeListener();
+ this.collection = new ArrayList<E>();
+ // 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<E> iterator() {
+ // try to prevent backdoor modification of the list
+ return new ReadOnlyIterator<E>(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<E> getItems(ListAddEvent event) {
+ return (Iterable<E>) event.getItems();
+ }
+
+ protected void itemsRemoved(ListRemoveEvent event) {
+ this.removeItemsFromCollection(this.getItems(event), this.collection, VALUES);
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getItems(ListRemoveEvent event) {
+ return (Iterable<E>) 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<E> getOldItems(ListReplaceEvent event) {
+ return (Iterable<E>) event.getOldItems();
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getNewItems(ListReplaceEvent event) {
+ return (Iterable<E>) 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<S extends Model, E>
+ extends AspectAdapter<S>
+ implements ListValueModel<E>
+{
+ /** How the list looked before the last state change */
+ private final ArrayList<E> 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<S>(subject));
+ }
+
+ /**
+ * Construct a curator for the specified subject holder.
+ * The subject holder cannot be null.
+ */
+ protected ListCurator(PropertyValueModel<? extends S> subjectHolder) {
+ super(subjectHolder);
+ this.record = new ArrayList<E>();
+ 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<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ReadOnlyListIterator<E>(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<E> getValue() {
+ return this.iterator();
+ }
+
+ @Override
+ protected Class<? extends EventListener> 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<E> iteratorForRecord();
+
+
+ // ********** behavior **********
+
+ void submitInventoryReport() {
+ List<E> 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<E> rec = new ArrayList<E>(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.
+ * <p>
+ * Subclasses must override:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current list value
+ * </ul>
+ * Subclasses might want to override the following methods
+ * to improve performance (by not recalculating the value, if possible):<ul>
+ * <li>{@link #itemsAdded(ListAddEvent event)}
+ * <li>{@link #itemsRemoved(ListRemoveEvent event)}
+ * <li>{@link #itemsReplaced(ListReplaceEvent event)}
+ * <li>{@link #itemsMoved(ListMoveEvent event)}
+ * <li>{@link #listCleared(ListClearEvent event)}
+ * <li>{@link #listChanged(ListChangeEvent event)}
+ * </ul>
+ */
+public abstract class ListPropertyValueModelAdapter<T>
+ extends AbstractPropertyValueModelAdapter<T>
+{
+ /** 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<E>
+ extends AbstractListValueModel
+{
+ /** The wrapped list value model. */
+ protected final ListValueModel<? extends E> 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<? extends E> 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<E> getItems(ListAddEvent event) {
+ return (Iterable<E>) event.getItems();
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getItems(ListRemoveEvent event) {
+ return (Iterable<E>) event.getItems();
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getNewItems(ListReplaceEvent event) {
+ return (Iterable<E>) event.getNewItems();
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<E> getOldItems(ListReplaceEvent event) {
+ return (Iterable<E>) 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.
+ * <p>
+ * We don't use a singleton because we hold on to listeners.
+ */
+public final class NullCollectionValueModel<E>
+ extends AbstractModel
+ implements CollectionValueModel<E>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Default constructor.
+ */
+ public NullCollectionValueModel() {
+ super();
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ public int size() {
+ return 0;
+ }
+
+ public Iterator<E> 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.
+ * <p>
+ * We don't use a singleton because we hold on to listeners.
+ */
+public final class NullListValueModel<E>
+ extends AbstractModel
+ implements ListValueModel<E>
+{
+ private static final Object[] EMPTY_ARRAY = new Object[0];
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Default constructor.
+ */
+ public NullListValueModel() {
+ super();
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return EmptyIterator.instance();
+ }
+
+ public ListIterator<E> 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.
+ * <p>
+ * We don't use a singleton because we hold on to listeners.
+ */
+public final class NullPropertyValueModel<T>
+ extends AbstractModel
+ implements PropertyValueModel<T>
+{
+ 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.
+ * <p>
+ * We don't use a singleton because we hold on to listeners.
+ */
+public final class NullTreeValueModel<E>
+ extends AbstractModel
+ implements TreeValueModel<E>
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Default constructor.
+ */
+ public NullTreeValueModel() {
+ super();
+ }
+
+
+ // ********** TreeValueModel implementation **********
+
+ public Iterator<E> 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.
+ * <p>
+ * The typical subclass will override the following methods (see the descriptions
+ * in {@link AspectPropertyValueModelAdapter}):<ul>
+ * <li>{@link #buildValue_()}
+ * <li>{@link #setValue_(Object)}
+ * <li>{@link #buildValue()}
+ * <li>{@link #setValue(Object)}
+ * </ul>
+ */
+public abstract class PropertyAspectAdapter<S extends Model, V>
+ extends AspectPropertyValueModelAdapter<S, V>
+{
+ /** 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<S>(subject), propertyNames);
+ }
+
+ /**
+ * Construct a property aspect adapter for the specified subject holder
+ * and properties.
+ */
+ protected PropertyAspectAdapter(PropertyValueModel<? extends S> 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<? extends S> subjectHolder, Collection<String> 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<? extends S> 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.
+ * <p>
+ * If the property's value is null, an empty iterator is returned
+ * (i.e. you can't have a collection with a <code>null</code> element).
+ */
+public class PropertyCollectionValueModelAdapter<E>
+ extends AbstractCollectionValueModel
+ implements CollectionValueModel<E>
+{
+ /** The wrapped property value model. */
+ protected final PropertyValueModel<? extends E> 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<? extends E> 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<E> iterator() {
+ return (this.value == null) ? EmptyIterator.<E>instance() : this.iterator_();
+ }
+
+ protected Iterator<E> iterator_() {
+ return new SingleElementIterator<E>(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.
+ * <p>
+ * If the property's value is null, an empty iterator is returned
+ * (i.e. you can't have a collection with a <code>null</code> element).
+ */
+public class PropertyListValueModelAdapter<E>
+ extends AbstractListValueModel
+ implements ListValueModel<E>
+{
+ /** The wrapped property value model. */
+ protected final PropertyValueModel<? extends E> 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<? extends E> 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<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return (this.value == null) ?
+ EmptyListIterator.<E>instance()
+ :
+ new SingleElementListIterator<E>(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}.
+ * <p>
+ * Subclasses must implement the following methods:<ul>
+ * <li>{@link #valueChanged(PropertyChangeEvent)}<p>
+ * implement this method to propagate the appropriate change notification
+ * </ul>
+ */
+public abstract class PropertyValueModelWrapper<V>
+ extends AbstractPropertyValueModel
+{
+ /** The wrapped property value model. */
+ protected final PropertyValueModel<? extends V> 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<? extends V> 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
+ * ... <em>isn't</em> ... 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<T>
+ extends PropertyValueModelWrapper<T>
+ implements WritablePropertyValueModel<T>
+{
+ public ReadOnlyWritablePropertyValueModelWrapper(PropertyValueModel<? extends T> 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 <code>SetCollectionValueModel</code> wraps another
+ * {@link CollectionValueModel} and returns the items in the collection
+ * only once.
+ */
+public class SetCollectionValueModel<E>
+ extends CollectionValueModelWrapper<E>
+ implements CollectionValueModel<E>
+{
+ private final HashBag<E> bag = new HashBag<E>();
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a collection value model with the specified wrapped
+ * collection value model and a filter that simply accepts every object.
+ */
+ public SetCollectionValueModel(CollectionValueModel<? extends E> 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<E> listHolder) {
+ this(new ListCollectionValueModelAdapter<E>(listHolder));
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return new ReadOnlyIterator<E>(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<E> addedItems = new ArrayList<E>(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<E> removedItems = new ArrayList<E>(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<E>(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<E>
+ extends AbstractModel
+ implements WritableCollectionValueModel<E>, Collection<E>
+{
+ /** The collection. */
+ protected final Collection<E> collection;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a collection value model for the specified collection.
+ */
+ public SimpleCollectionValueModel(Collection<E> 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<E>());
+ }
+
+ @Override
+ protected ChangeSupport buildChangeSupport() {
+ return new SingleAspectChangeSupport(this, CollectionChangeListener.class, VALUES);
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return new LocalIterator<E>(this.collection.iterator());
+ }
+
+ public int size() {
+ return this.collection.size();
+ }
+
+
+ // ********** WritableCollectionValueModel implementation **********
+
+ /**
+ * Allow the collection's elements to be replaced.
+ */
+ public void setValues(Iterable<E> 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 extends Object> 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<? extends E> 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<E> c1 = CollectionTools.collection(this.collection);
+ @SuppressWarnings("unchecked")
+ Collection<E> c2 = CollectionTools.collection(((Collection<E>) 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<T> implements Iterator<T> {
+ private final Iterator<T> iterator;
+ private T next;
+
+ LocalIterator(Iterator<T> 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<E>
+ extends AbstractModel
+ implements WritableListValueModel<E>, List<E>
+{
+ /** The list. */
+ protected List<E> list;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a list value model for the specified list.
+ */
+ public SimpleListValueModel(List<E> 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<E>());
+ }
+
+ @Override
+ protected ChangeSupport buildChangeSupport() {
+ return new SingleAspectChangeSupport(this, ListChangeListener.class, LIST_VALUES);
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return new LocalIterator<E>(this.list.iterator());
+ }
+
+ public ListIterator<E> listIterator() {
+ return new LocalListIterator<E>(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<E> 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 extends Object> 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<? extends E> c) {
+ return this.addItemsToList(c, this.list, LIST_VALUES);
+ }
+
+ public boolean addAll(int index, Collection<? extends E> 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<E> l1 = CollectionTools.list(this.list);
+ @SuppressWarnings("unchecked")
+ List<E> l2 = CollectionTools.list(((List<E>) 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<E> listIterator(int index) {
+ return new LocalListIterator<E>(this.list.listIterator(index));
+ }
+
+ public List<E> 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<E> elements) {
+ this.setItemsInList(index, elements, this.list, LIST_VALUES);
+ }
+
+ @Override
+ public void toString(StringBuilder sb) {
+ sb.append(this.list);
+ }
+
+
+ // ********** iterators **********
+
+ private class LocalIterator<T> implements Iterator<T> {
+ private final Iterator<T> iterator;
+ private int index = -1;
+ private T next;
+
+ LocalIterator(Iterator<T> 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<T> implements ListIterator<T> {
+ private final ListIterator<T> iterator;
+ private int last = -1;
+ private int next = 0;
+ private T current;
+
+ LocalListIterator(ListIterator<T> 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<T>
+ extends AbstractModel
+ implements WritablePropertyValueModel<T>
+{
+ /** 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.
+ * <p>
+ * 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).
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends CollectionListValueModelAdapter<E>
+{
+ /**
+ * A comparator used for sorting the elements;
+ * if it is null, we use "natural ordering".
+ */
+ protected Comparator<E> comparator;
+
+
+ // ********** constructors **********
+
+ /**
+ * Wrap the specified collection value model and sort its contents
+ * using the specified comparator.
+ */
+ public SortedListValueModelAdapter(CollectionValueModel<? extends E> collectionHolder, Comparator<E> 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<? extends E> collectionHolder) {
+ this(collectionHolder, null);
+ }
+
+
+ // ********** accessors **********
+
+ public void setComparator(Comparator<E> 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<E> newList = (ArrayList<E>) 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<? extends E> 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<E> unsortedList = (ArrayList<E>) this.list.clone();
+ Collections.sort(this.list, this.comparator);
+ Range diffRange = CollectionTools.identityDiffRange(unsortedList, this.list);
+ if (diffRange.size > 0) {
+ List<E> unsortedItems = unsortedList.subList(diffRange.start, diffRange.end + 1);
+ List<E> 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.
+ * <p>
+ * 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).
+ * <p>
+ * <strong>NB:</strong> 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<E>
+ extends ListValueModelWrapper<E>
+ implements ListValueModel<E>
+{
+ /**
+ * A comparator used for sorting the elements;
+ * if it is null, we use "natural ordering".
+ */
+ protected Comparator<E> 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<E> sortedList;
+
+
+ // ********** constructors **********
+
+ /**
+ * Wrap the specified list value model and sort its contents
+ * using the specified comparator.
+ */
+ public SortedListValueModelWrapper(ListValueModel<? extends E> listHolder, Comparator<E> comparator) {
+ super(listHolder);
+ this.comparator = comparator;
+ this.sortedList = new ArrayList<E>(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<? extends E> listHolder) {
+ this(listHolder, null);
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ReadOnlyListIterator<E>(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<E> 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<E> unsortedList = (ArrayList<E>) this.sortedList.clone();
+ Collections.sort(this.sortedList, this.comparator);
+ Range diffRange = CollectionTools.identityDiffRange(unsortedList, this.sortedList);
+ if (diffRange.size > 0) {
+ List<E> unsortedItems = unsortedList.subList(diffRange.start, diffRange.end + 1);
+ List<E> 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.
+ * <p>
+ * Subclasses must implement:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current model
+ * </ul>
+ * Subclasses might want to override the following methods
+ * to improve performance (by not recalculating the value, if possible):<ul>
+ * <li>{@link #stateChanged(StateChangeEvent event)}
+ * </ul>
+ */
+public abstract class StatePropertyValueModelAdapter<T>
+ extends AbstractPropertyValueModelAdapter<T>
+{
+ /** 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 <em>never</em> be notified of any changes, because there should be none.
+ */
+public class StaticCollectionValueModel<E>
+ extends AbstractModel
+ implements CollectionValueModel<E>
+{
+ /** 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<? extends E> elements) {
+ super();
+ this.elements = ArrayTools.array(elements);
+ }
+
+
+ // ********** CollectionValueModel implementation **********
+
+ public int size() {
+ return this.elements.length;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator<E> iterator() {
+ // we can cast here since our constructors require the elements to be
+ // of type E and ArrayIterator is read-only
+ return (Iterator<E>) new ArrayIterator<Object>(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 <em>never</em> be notified of any changes, because there should be none.
+ */
+public class StaticListValueModel<E>
+ extends AbstractModel
+ implements ListValueModel<E>
+{
+ /** 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<? extends E> elements) {
+ super();
+ this.elements = ArrayTools.array(elements);
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ @SuppressWarnings("unchecked")
+ public Iterator<E> iterator() {
+ // we can cast here since our constructors require the elements to be
+ // of type E and ArrayIterator is read-only
+ return (Iterator<E>) new ArrayIterator<Object>(this.elements);
+ }
+
+ @SuppressWarnings("unchecked")
+ public ListIterator<E> listIterator() {
+ // we can cast here since our constructors require the elements to be
+ // of type E and ArrayListIterator is read-only
+ return (ListIterator<E>) new ArrayListIterator<Object>(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 <em>never</em> be notified of any changes, because there should be none.
+ */
+public class StaticPropertyValueModel<T>
+ extends AbstractModel
+ implements PropertyValueModel<T>
+{
+ /** 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 <em>never</em> be notified of any changes, because there should be none.
+ */
+public class StaticTreeValueModel<E>
+ extends AbstractModel
+ implements TreeValueModel<E>
+{
+ /** The tree's nodes. */
+ protected final Iterable<? extends E> nodes;
+
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * Construct a read-only tree value model for the specified nodes.
+ */
+ public StaticTreeValueModel(Iterable<? extends E> nodes) {
+ super();
+ if (nodes == null) {
+ throw new NullPointerException();
+ }
+ this.nodes = nodes;
+ }
+
+ // ********** TreeValueModel implementation **********
+
+ public Iterator<E> nodes() {
+ return new ReadOnlyIterator<E>(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.
+ * <p>
+ * The {@link Transformer} can be changed at any time; allowing the same
+ * adapter to be used with different transformations.
+ * <p>
+ * As an alternative to building a {@link Transformer},
+ * a subclass of <code>TransformationListValueModelAdapter</code> 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)}.
+ * <p>
+ * <strong>NB:</strong> 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<E1, E2>
+ extends ListValueModelWrapper<E1>
+ implements ListValueModel<E2>
+{
+
+ /** This transforms the items, unless the subclass overrides {@link #transformItem(Object)}). */
+ protected Transformer<E1, E2> transformer;
+
+ /** The list of transformed items. */
+ protected final List<E2> transformedList;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a list value model with the specified nested
+ * list value model and transformer.
+ */
+ public TransformationListValueModel(ListValueModel<? extends E1> listHolder, Transformer<E1, E2> transformer) {
+ super(listHolder);
+ this.transformer = transformer;
+ this.transformedList = new ArrayList<E2>();
+ }
+
+ /**
+ * 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<? extends E1> listHolder) {
+ super(listHolder);
+ this.transformer = this.buildTransformer();
+ this.transformedList = new ArrayList<E2>();
+ }
+
+ /**
+ * Construct a list value model with the specified nested
+ * collection value model and transformer.
+ */
+ public TransformationListValueModel(CollectionValueModel<? extends E1> collectionHolder, Transformer<E1, E2> transformer) {
+ this(new CollectionListValueModelAdapter<E1>(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<? extends E1> collectionHolder) {
+ this(new CollectionListValueModelAdapter<E1>(collectionHolder));
+ }
+
+ protected Transformer<E1, E2> buildTransformer() {
+ return new DefaultTransformer();
+ }
+
+
+ // ********** ListValueModel implementation **********
+
+ public Iterator<E2> iterator() {
+ return this.listIterator();
+ }
+
+ public ListIterator<E2> listIterator() {
+ return new ReadOnlyListIterator<E2>(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<E2> transformItems(ListValueModel<? extends E1> lvm) {
+ return this.transformItems(lvm, lvm.size());
+ }
+
+ /**
+ * Transform the items associated with the specified event.
+ */
+ protected List<E2> transformItems(ListAddEvent event) {
+ return this.transformItems(this.getItems(event), event.getItemsSize());
+ }
+
+ /**
+ * Transform the items associated with the specified event.
+ */
+ protected List<E2> transformItems(ListRemoveEvent event) {
+ return this.transformItems(this.getItems(event), event.getItemsSize());
+ }
+
+ /**
+ * Transform the new items associated with the specified event.
+ */
+ protected List<E2> transformNewItems(ListReplaceEvent event) {
+ return this.transformItems(this.getNewItems(event), event.getItemsSize());
+ }
+
+ /**
+ * Transform the old items associated with the specified event.
+ */
+ protected List<E2> transformOldItems(ListReplaceEvent event) {
+ return this.transformItems(this.getOldItems(event), event.getItemsSize());
+ }
+
+ /**
+ * Transform the specified items.
+ */
+ protected List<E2> transformItems(Iterable<? extends E1> items, int size) {
+ List<E2> result = new ArrayList<E2>(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<E1, E2> 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<E1, E2> {
+ 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 <code>TransformationPropertyValueModel</code> wraps another
+ * {@link PropertyValueModel} and uses a {@link Transformer}
+ * to transform the wrapped value before it is returned by {@link #getValue()}.
+ * <p>
+ * As an alternative to building a {@link Transformer},
+ * a subclass of <code>TransformationPropertyValueModel</code> 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<T1, T2>
+ extends PropertyValueModelWrapper<T1>
+ implements PropertyValueModel<T2>
+{
+ protected final Transformer<T1, T2> 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<? extends T1> valueHolder) {
+ super(valueHolder);
+ this.transformer = this.buildTransformer();
+ }
+
+ /**
+ * Construct a property value model with the specified nested
+ * property value model and transformer.
+ */
+ public TransformationPropertyValueModel(PropertyValueModel<? extends T1> valueHolder, Transformer<T1, T2> transformer) {
+ super(valueHolder);
+ this.transformer = transformer;
+ }
+
+ protected Transformer<T1, T2> 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<T1, T2> {
+ 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 <code>TransformationWritablePropertyValueModel</code> wraps another
+ * {@link WritablePropertyValueModel} and uses a {@link BidiTransformer}
+ * to:<ul>
+ * <li>transform the wrapped value before it is returned by {@link #getValue()}
+ * <li>"reverse-transform" the new value that comes in via
+ * {@link #setValue(Object)}
+ * </ul>
+ * As an alternative to building a {@link BidiTransformer},
+ * a subclass of <code>TransformationWritablePropertyValueModel</code> 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<T1, T2>
+ extends TransformationPropertyValueModel<T1, T2>
+ implements WritablePropertyValueModel<T2>
+{
+
+ // ********** 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<T1> valueHolder) {
+ super(valueHolder);
+ }
+
+ /**
+ * Construct a writable property value model with the specified nested
+ * writable property value model and bidi transformer.
+ */
+ public TransformationWritablePropertyValueModel(WritablePropertyValueModel<T1> valueHolder, BidiTransformer<T1, T2> transformer) {
+ super(valueHolder, transformer);
+ }
+
+ @Override
+ protected BidiTransformer<T1, T2> 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-<code>null</code>,
+ * 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<T1>},
+ * so this cast should be safe.
+ */
+ @SuppressWarnings("unchecked")
+ protected WritablePropertyValueModel<T1> getValueHolder() {
+ return (WritablePropertyValueModel<T1>) this.valueHolder;
+ }
+
+ /**
+ * Our constructors accept only a {@link BidiTransformer<T1, T2>},
+ * so this cast should be safe.
+ */
+ protected BidiTransformer<T1, T2> getTransformer() {
+ return (BidiTransformer<T1, T2>) this.transformer;
+ }
+
+
+ // ********** default bidi transformer **********
+
+ /**
+ * The default bidi transformer will return <code>null</code> if the
+ * wrapped value is <code>null</code>.
+ * If the wrapped value is not <code>null</code>, it is transformed by a subclass
+ * implementation of {@link #transform_(Object)}.
+ * The default bidi transformer will also return <code>null</code>
+ * if the new value is <code>null</code>.
+ * If the new value is not <code>null</code>, it is reverse-transformed
+ * by a subclass implementation of {@link #reverseTransform_(Object)}.
+ */
+ protected class DefaultBidiTransformer
+ extends DefaultTransformer
+ implements BidiTransformer<T1, T2>
+ {
+ 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.
+ * <p>
+ * The typical subclass will override the following methods (see the descriptions
+ * in {@link AspectTreeValueModelAdapter}):<ul>
+ * <li>{@link #nodes_()}
+ * <li>{@link #nodes()}
+ * </ul>
+ */
+public abstract class TreeAspectAdapter<S extends Model, E>
+ extends AspectTreeValueModelAdapter<S, E>
+{
+ /**
+ * 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<S>(subject), treeNames);
+ }
+
+ /**
+ * Construct a tree aspect adapter for the specified subject holder
+ * and trees.
+ */
+ protected TreeAspectAdapter(PropertyValueModel<? extends S> 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<? extends S> subjectHolder, Collection<String> 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<? extends S> 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.
+ * <p>
+ * Subclasses must override:<ul>
+ * <li>{@link #buildValue()}<p>
+ * to return the current property value, as derived from the
+ * current collection value
+ * </ul>
+ * Subclasses might want to override the following methods
+ * to improve performance (by not recalculating the value, if possible):<ul>
+ * <li>{@link #nodeAdded(TreeChangeEvent event)}
+ * <li>{@link #nodeRemoved(TreeChangeEvent event)}
+ * <li>{@link #treeCleared(TreeChangeEvent event)}
+ * <li>{@link #treeChanged(TreeChangeEvent event)}
+ * </ul>
+ */
+public abstract class TreePropertyValueModelAdapter<T>
+ extends AbstractPropertyValueModelAdapter<T>
+{
+ /** 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 <em>aspects</em> of the <em>value</em> contained
+ * by the {@link WritablePropertyValueModel}. Changes to the {@link WritablePropertyValueModel}'s
+ * value are also monitored.
+ * <p>
+ * 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.
+ * <p>
+ * <strong>NB:</strong> Clients will need to listen for two different change notifications:
+ * a property change event will be be fired when the <em>value</em> changes;
+ * a state change event will be fired when an <em>aspect</em> of the value changes.
+ * <p>
+ * Subclasses need to override two methods:<ul>
+ * <li>{@link #engageValue_()}<p>
+ * begin listening to the appropriate aspect of the value and call
+ * {@link #valueAspectChanged()} whenever the aspect changes
+ * (this will fire a state change event)
+ * <li>{@link #disengageValue_()}<p>
+ * stop listening to the appropriate aspect of the value
+ * </ul>
+ */
+public abstract class ValueAspectAdapter<V>
+ extends PropertyValueModelWrapper<V>
+ implements WritablePropertyValueModel<V>
+{
+ /** 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<V> 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<V>}.
+ */
+ @SuppressWarnings("unchecked")
+ protected WritablePropertyValueModel<V> getValueHolder() {
+ return (WritablePropertyValueModel<V>) 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** Listener that listens to the value. */
+ protected final ChangeListener valueAspectListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a change adapter for the specified value.
+ */
+ public ValueChangeAdapter(WritablePropertyValueModel<V> 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** 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<V> 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** 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<V> 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** 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<V> 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** Listener that listens to value. */
+ protected final StateChangeListener valueStateListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct an adapter for the value state.
+ */
+ public ValueStateAdapter(WritablePropertyValueModel<V> 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<V extends Model>
+ extends ValueAspectAdapter<V>
+{
+ /** 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<V> 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.
+ * <p>
+ * If the property's value is null, an empty iterator is returned
+ * (i.e. you can't have a collection with a <code>null</code> element).
+ * Also, only a single-element collection can be written to the adapter.
+ */
+public class WritablePropertyCollectionValueModelAdapter<E>
+ extends PropertyCollectionValueModelAdapter<E>
+ implements WritableCollectionValueModel<E>
+{
+
+ // ********** constructor **********
+
+ /**
+ * Convert the specified writable property value model to a writable
+ * collection value model.
+ */
+ public WritablePropertyCollectionValueModelAdapter(WritablePropertyValueModel<E> valueHolder) {
+ super(valueHolder);
+ }
+
+
+ // ********** WritableCollectionValueModel implementation **********
+
+ public void setValues(Iterable<E> values) {
+ Iterator<E> 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<E> getValueHolder() {
+ return (WritablePropertyValueModel<E>) 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.
+ * <p>
+ * If the property's value is null, an empty iterator is returned
+ * (i.e. you can't have a list with a <code>null</code> element).
+ * Also, only a single-element list can be written to the adapter.
+ */
+public class WritablePropertyListValueModelAdapter<E>
+ extends PropertyListValueModelAdapter<E>
+ implements WritableListValueModel<E>
+{
+
+ // ********** constructor **********
+
+ /**
+ * Convert the specified writable property value model to a writable
+ * collection value model.
+ */
+ public WritablePropertyListValueModelAdapter(WritablePropertyValueModel<E> valueHolder) {
+ super(valueHolder);
+ }
+
+
+ // ********** WritableListValueModel implementation **********
+
+ public void setListValues(Iterable<E> listValues) {
+ Iterator<E> 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<E> getValueHolder() {
+ return (WritablePropertyValueModel<E>) 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<P>
+ extends AspectAdapter<Preferences>
+ implements WritablePropertyValueModel<P>
+{
+ /** 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<P> 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.<P>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<P> converter) {
+ this(new StaticPropertyValueModel<Preferences>(preferences), key, defaultValue, converter);
+ }
+
+ /**
+ * Construct an adapter for the specified preference with
+ * the specified default value for the preference.
+ */
+ public static PreferencePropertyValueModel<Boolean> forBoolean(Preferences preferences, String key, boolean defaultValue) {
+ return new PreferencePropertyValueModel<Boolean>(
+ 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<Integer> forInteger(Preferences preferences, String key, int defaultValue) {
+ return new PreferencePropertyValueModel<Integer>(
+ 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<? extends Preferences> 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<? extends Preferences> preferencesHolder, String key, P defaultValue) {
+ this(preferencesHolder, key, defaultValue, BidiStringConverter.Default.<P>instance());
+ }
+
+ /**
+ * Construct an adapter for the specified preference with
+ * the specified default value for the preference.
+ */
+ public PreferencePropertyValueModel(PropertyValueModel<? extends Preferences> preferencesHolder, String key, P defaultValue, BidiStringConverter<P> 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<? extends EventListener> 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<P>
+ extends AspectAdapter<Preferences>
+ implements CollectionValueModel<PreferencePropertyValueModel<P>>
+{
+
+ /** Cache the current preferences, stored in models and keyed by name. */
+ protected final HashMap<String, PreferencePropertyValueModel<P>> 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>(preferences));
+ }
+
+ /**
+ * Construct an adapter for the specified preferences node.
+ */
+ public PreferencesCollectionValueModel(PropertyValueModel<? extends Preferences> preferencesHolder) {
+ super(preferencesHolder);
+ this.preferences = new HashMap<String, PreferencePropertyValueModel<P>>();
+ 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<PreferencePropertyValueModel<P>> 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<? extends EventListener> 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<PreferencePropertyValueModel<P>> iterator = (Iterator<PreferencePropertyValueModel<P>>) newValue;
+ this.fireCollectionChanged(VALUES, CollectionTools.collection(iterator));
+ }
+
+ @Override
+ protected void engageSubject_() {
+ this.subject.addPreferenceChangeListener(this.preferenceChangeListener);
+ for (Iterator<PreferencePropertyValueModel<P>> stream = this.preferenceModels(); stream.hasNext(); ) {
+ PreferencePropertyValueModel<P> 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<PreferencePropertyValueModel<P>> preferenceModels() {
+ String[] keys;
+ try {
+ keys = this.subject.keys();
+ } catch (BackingStoreException ex) {
+ throw new RuntimeException(ex);
+ }
+ return new TransformationIterator<String, PreferencePropertyValueModel<P>>(new ArrayIterator<String>(keys)) {
+ @Override
+ protected PreferencePropertyValueModel<P> 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<P> buildPreferenceModel(String key) {
+ return new PreferencePropertyValueModel<P>(this.subjectHolder, key);
+ }
+
+ protected synchronized void preferenceChanged(String key, String newValue) {
+ if (newValue == null) {
+ // a preference was removed
+ PreferencePropertyValueModel<P> preferenceModel = this.preferences.remove(key);
+ this.fireItemRemoved(VALUES, preferenceModel);
+ } else if ( ! this.preferences.containsKey(key)) {
+ // a preference was added
+ PreferencePropertyValueModel<P> 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<Boolean> booleanHolder, boolean defaultValue) {
+ super(booleanHolder, defaultValue);
+ }
+
+ /**
+ * Constructor - the boolean holder is required.
+ * The default value will be false.
+ */
+ public CheckBoxModelAdapter(WritablePropertyValueModel<Boolean> 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<Object>[] 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<Object> selectionHolder;
+ protected final PropertyChangeListener selectionListener;
+
+
+ // ********** constructors **********
+
+ /**
+ * Constructor - the list holder and selection holder are required;
+ */
+ public ComboBoxModelAdapter(ListValueModel<?> listHolder, WritablePropertyValueModel<Object> 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<Object> 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<Object> 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<Object> dateHolder) {
+ this(dateHolder, new Date());
+ }
+
+ /**
+ * Constructor - the date holder and default value are required.
+ */
+ public DateSpinnerModelAdapter(WritablePropertyValueModel<Object> 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<Object> dateHolder, Comparable<?> start, Comparable<?> end, int calendarField) {
+ this(dateHolder, start, end, calendarField, new Date());
+ }
+
+ /**
+ * Constructor - the date holder is required.
+ */
+ public DateSpinnerModelAdapter(WritablePropertyValueModel<Object> 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<String> 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<String> 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<String> 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<Object> 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<Object> valueHolder) {
+ this(valueHolder, valueHolder.getValue());
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(WritablePropertyValueModel<Object> 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<Object> valueHolder, Object[] values) {
+ this(valueHolder, values, values[0]);
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(WritablePropertyValueModel<Object> 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<Object> valueHolder, List<Object> values) {
+ this(valueHolder, values, values.get(0));
+ }
+
+ /**
+ * Constructor - the value holder is required.
+ */
+ public ListSpinnerModelAdapter(WritablePropertyValueModel<Object> valueHolder, List<Object> 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<Number> 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<Number> numberHolder) {
+ this(numberHolder, 0);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ * The step size is one.
+ */
+ public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> 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<Number> numberHolder, int minimum, int maximum, int stepSize) {
+ this(numberHolder, minimum, maximum, stepSize, minimum);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ */
+ public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> 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<Number> numberHolder, double minimum, double maximum, double stepSize) {
+ this(numberHolder, minimum, maximum, stepSize, minimum);
+ }
+
+ /**
+ * Constructor - the number holder is required.
+ */
+ public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> 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<Number> 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 <Enter>.
+ *
+ * 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<MutableTreeNode> temp = new ArrayList<MutableTreeNode>(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<Object> 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<Object> 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<Boolean> buildBooleanHolder(WritablePropertyValueModel<Object> valueHolder, Object buttonValue) {
+ WritablePropertyValueModel<Object> filteringPVM = new FilteringWritablePropertyValueModel<Object>(valueHolder, new RadioButtonFilter(buttonValue));
+ return new TransformationWritablePropertyValueModel<Object, Boolean>(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<Object> {
+ 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<Object, Boolean> {
+ 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<Object> 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<Object> 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<Object> 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<E>
+ extends AbstractTableModel
+{
+ /**
+ * a list of user objects that are converted to
+ * rows via the column adapter
+ */
+ private ListValueModel<? extends E> 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<WritablePropertyValueModel<Object>[]> 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<? extends E> listHolder, ColumnAdapter columnAdapter) {
+ super();
+ if (listHolder == null) {
+ throw new NullPointerException();
+ }
+ this.listHolder = listHolder;
+ this.columnAdapter = columnAdapter;
+ this.listChangeListener = this.buildListChangeListener();
+ this.rows = new ArrayList<WritablePropertyValueModel<Object>[]>();
+ this.cellListener = this.buildCellListener();
+ }
+
+ /**
+ * Construct a table model adapter for the specified objects
+ * and adapter.
+ */
+ public TableModelAdapter(CollectionValueModel<? extends E> collectionHolder, ColumnAdapter columnAdapter) {
+ this(new CollectionListValueModelAdapter<E>(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<Object> getItems(ListAddEvent event) {
+ return (Iterable<Object>) event.getItems();
+ }
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<Object> getNewItems(ListReplaceEvent event) {
+ return (Iterable<Object>) 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<Object>) 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<Object>[] row = this.rows.get(rowIndex);
+ return row[columnIndex].getValue();
+ }
+
+ @Override
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ WritablePropertyValueModel<Object>[] 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<? extends E> getModel() {
+ return this.listHolder;
+ }
+
+ /**
+ * Set the underlying list model.
+ */
+ public void setModel(ListValueModel<E> 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<E> collectionHolder) {
+ this.setModel(new CollectionListValueModelAdapter<E>(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<? extends E> stream = this.listHolder.iterator(); stream.hasNext(); ) {
+ WritablePropertyValueModel<Object>[] 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<Object>[] 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<Object>[] row : this.rows) {
+ this.disengageRow(row);
+ }
+ this.rows.clear();
+ }
+
+ private void disengageRow(WritablePropertyValueModel<Object>[] 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<Object> cellHolder) {
+ for (int i = this.rows.size(); i-- > 0; ) {
+ WritablePropertyValueModel<Object>[] 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<Object> items) {
+ List<WritablePropertyValueModel<Object>[]> newRows = new ArrayList<WritablePropertyValueModel<Object>[]>(size);
+ for (Object item : items) {
+ WritablePropertyValueModel<Object>[] 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<Object> items) {
+ int i = index;
+ for (Object item : items) {
+ WritablePropertyValueModel<Object>[] 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<WritablePropertyValueModel<Object>[]> temp = new ArrayList<WritablePropertyValueModel<Object>[]>(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<Boolean> 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<Boolean> 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<Boolean> 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<T>
+ 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<TreeNodeValueModel<T>> 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<T> 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<TreeNodeValueModel<T>, List<TreeNodeValueModel<T>>> 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<ListValueModel<TreeNodeValueModel<T>>, TreeNodeValueModel<T>> parents;
+
+
+ // ********** constructors **********
+
+ /**
+ * Construct a tree model for the specified root.
+ */
+ public TreeModelAdapter(PropertyValueModel<TreeNodeValueModel<T>> 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<TreeNodeValueModel<T>, List<TreeNodeValueModel<T>>>();
+ this.parents = new IdentityHashMap<ListValueModel<TreeNodeValueModel<T>>, TreeNodeValueModel<T>>();
+ }
+
+ /**
+ * Construct a tree model for the specified root.
+ */
+ public TreeModelAdapter(TreeNodeValueModel<T> root) {
+ this(new StaticPropertyValueModel<TreeNodeValueModel<T>>(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<T>) 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<T>) 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<T>) parent).child(index);
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getChildCount(Object parent) {
+ return ((TreeNodeValueModel<T>) parent).childrenSize();
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean isLeaf(Object node) {
+ return ((TreeNodeValueModel<T>) node).isLeaf();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ ((TreeNodeValueModel<T>) path.getLastPathComponent()).setValue((T) newValue);
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getIndexOfChild(Object parent, Object child) {
+ return ((TreeNodeValueModel<T>) parent).indexOfChild((TreeNodeValueModel<T>) 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<T> 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<T> 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<T> node) {
+ TreeNodeValueModel<T> 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<T>[] path, int[] childIndices, TreeNodeValueModel<T>[] 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<T> 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<T> 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<T> parent, int index, TreeNodeValueModel<T> node, ListValueModel<TreeNodeValueModel<T>> childrenModel) {
+ List<TreeNodeValueModel<T>> siblings = this.childrenLists.get(parent);
+ if (siblings == null) {
+ siblings = new ArrayList<TreeNodeValueModel<T>>();
+ 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<T>[] path, int[] childIndices, TreeNodeValueModel<T>[] 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<T> node) {
+ new NodeChangePolicy(node).removeChildren();
+ this.removeNodeFromInternalTree(node.parent(), index, node.childrenModel());
+ }
+
+ /**
+ * Remove the specified node from our internal tree.
+ */
+ private void removeNodeFromInternalTree(TreeNodeValueModel<T> parent, int index, ListValueModel<TreeNodeValueModel<T>> childrenModel) {
+ this.parents.remove(childrenModel);
+
+ List<TreeNodeValueModel<T>> 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<T> node) {
+ node.childrenModel().removeListChangeListener(ListValueModel.LIST_VALUES, this.childrenListener);
+ node.removePropertyChangeListener(PropertyValueModel.VALUE, this.nodeValueListener);
+ node.removeStateChangeListener(this.nodeStateListener);
+ }
+
+ void moveChildren(TreeNodeValueModel<T> parent, int targetIndex, int sourceIndex, int length) {
+ List<TreeNodeValueModel<T>> childrenList = this.childrenLists.get(parent);
+ ArrayList<TreeNodeValueModel<T>> temp = new ArrayList<TreeNodeValueModel<T>>(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<T>[] 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<T>[] buildArray(Iterable<TreeNodeValueModel<T>> elements, int size) {
+ @SuppressWarnings("unchecked")
+ TreeNodeValueModel<T>[] array = new TreeNodeValueModel[size];
+ int i = 0;
+ for (TreeNodeValueModel<T> 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<T> 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<TreeNodeValueModel<T>> 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<T> 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<TreeNodeValueModel<T>> getChildren() {
+ return (Iterable<TreeNodeValueModel<T>>) 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<TreeNodeValueModel<T>> getChildren() {
+ return (Iterable<TreeNodeValueModel<T>>) 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<TreeNodeValueModel<T>> getChildren() {
+ return (Iterable<TreeNodeValueModel<T>>) this.getEvent().getNewItems();
+ }
+
+ /**
+ * Remove the old nodes and add the new ones.
+ */
+ void replaceChildren() {
+ TreeNodeValueModel<T>[] parentPath = this.parent().path();
+ int[] childIndices = this.childIndices();
+ TreeModelAdapter.this.removeChildren(parentPath, childIndices, this.getOldChildren());
+ TreeModelAdapter.this.addChildren(parentPath, childIndices, this.childArray());
+ }
+
+ TreeNodeValueModel<T>[] getOldChildren() {
+ return this.buildArray(this.getOldItems(), this.getEvent().getItemsSize());
+ }
+
+ // minimized scope of suppressed warnings
+ @SuppressWarnings("unchecked")
+ protected Iterable<TreeNodeValueModel<T>> getOldItems() {
+ return (Iterable<TreeNodeValueModel<T>>) 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<TreeNodeValueModel<T>> 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<T> parent = this.parent();
+ TreeNodeValueModel<T>[] parentPath = parent.path();
+ List<TreeNodeValueModel<T>> childrenList = TreeModelAdapter.this.childrenLists.get(parent);
+ int[] childIndices = this.buildIndices(childrenList.size());
+ TreeNodeValueModel<T>[] 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<TreeNodeValueModel<T>> 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<T> parent = this.parent();
+ TreeNodeValueModel<T>[] parentPath = parent.path();
+ List<TreeNodeValueModel<T>> childrenList = TreeModelAdapter.this.childrenLists.get(parent);
+ int[] childIndices = this.buildIndices(childrenList.size());
+ TreeNodeValueModel<T>[] 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<TreeNodeValueModel<T>> getChildren() {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+
+ /**
+ * Wraps a TreeNodeValueModel for adding and removing its children.
+ */
+ class NodeChangePolicy extends ChangePolicy {
+ private final TreeNodeValueModel<T> node;
+
+ NodeChangePolicy(TreeNodeValueModel<T> node) {
+ super();
+ this.node = node;
+ }
+
+ /**
+ * The node itself is the parent.
+ */
+ @Override
+ TreeNodeValueModel<T> 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<TreeNodeValueModel<T>> 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<Problem> 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<Problem> 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<Class<? extends AbstractNode>, HashSet<String>> transientAspectNameSets = new HashMap<Class<? extends AbstractNode>, HashSet<String>>();
+
+ /**
+ * Sets of non-validated aspect names, keyed by class.
+ * This is built up lazily, as the objects are modified.
+ */
+ private static final HashMap<Class<? extends AbstractNode>, HashSet<String>> nonValidatedAspectNameSets = new HashMap<Class<? extends AbstractNode>, HashSet<String>>();
+
+
+ // ********** 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<Problem>();
+ this.branchProblems = new Vector<Problem>();
+
+ // 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<Node> children() {
+ List<Node> children = new ArrayList<Node>();
+ 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<Node> 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<Node.Reference> branchReferences() {
+ Collection<Node.Reference> branchReferences = new ArrayList<Node.Reference>(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<Node.Reference> branchReferences) {
+ for (Iterator<Node> 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<Node> allNodes() {
+ Collection<Node> nodes = new ArrayList<Node>(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<Node> nodes) {
+ nodes.add(this);
+ for (Iterator<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<Node> 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<String> transientAspectNames() {
+ synchronized (transientAspectNameSets) {
+ HashSet<String> transientAspectNames = transientAspectNameSets.get(this.getClass());
+ if (transientAspectNames == null) {
+ transientAspectNames = new HashSet<String>();
+ 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<String> 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<Node> allDirtyNodes() {
+ return new FilteringIterator<Node>(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<Problem> problems() {
+ return new CloneIterator<Problem>(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<Problem> branchProblems() {
+ return new CloneListIterator<Problem>(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<Node> 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<Problem> 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<Problem> oldBranchProblems = new Vector<Problem>(this.branchProblems);
+ int oldSize = this.branchProblems.size();
+
+ this.branchProblems.clear();
+ this.branchProblems.addAll(this.problems);
+ for (Iterator<Node> 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<Problem> 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<Node> 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<String> nonValidatedAspectNames() {
+ synchronized (nonValidatedAspectNameSets) {
+ HashSet<String> nonValidatedAspectNames = nonValidatedAspectNameSets.get(this.getClass());
+ if (nonValidatedAspectNames == null) {
+ nonValidatedAspectNames = new HashSet<String>();
+ 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<String> 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<Node> 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<Node.Reference> 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<Node> 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<Problem> 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<Problem> 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<T>
+ implements ListChooser.ListBrowser
+{
+ private FilteringListPanel<T> panel;
+
+ /**
+ * Default constructor.
+ */
+ public FilteringListBrowser() {
+ super();
+ this.panel = this.buildPanel();
+ }
+
+ protected FilteringListPanel<T> buildPanel() {
+ return new LocalFilteringListPanel<T>();
+ }
+
+ /**
+ * 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<S> extends FilteringListPanel<S> {
+ 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<T> 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<T> 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.<T>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<T> 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<T>();
+ }
+
+ 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<T> stringConverter() {
+ return this.stringConverter;
+ }
+
+ /**
+ * apply the new filter to the list
+ */
+ public void setStringConverter(StringConverter<T> 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 <none selected> 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.
+ * <p>
+ * <strong>NB:</strong> 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()}.
+ * <p>
+ * Note: We don't clear the "synchronize" flag here; so if the flag has
+ * been set <em>before</em> getting here, the first synchronization will
+ * start promptly (albeit, asynchronously).
+ * The "synchronize" flag will be set if:<ul>
+ * <li>{@link #synchronize()} was called after the synchronizer was
+ * constructed but before {@link #start()} was called; or
+ * <li>{@link #synchronize()} was called after {@link #stop()} was called
+ * but before {@link #start()} was called (to restart the synchronizer); or
+ * <li>{@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)
+ * </ul>
+ */
+ public void start() {
+ this.consumerThreadCoordinator.start();
+ }
+
+ /**
+ * Set the "synchronize" flag so the synchronization thread will either<ul>
+ * <li>if the thread is quiesced, start a synchronization immediately, or
+ * <li>if the thread is currently executing a synchronization, execute another
+ * synchronization once the current synchronization is complete
+ * </ul>
+ */
+ 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 <em>not</em> guaranteed to occur with <em>every</em>
+ * synchronization "cycle"; since other, unrelated, synchronizations can be
+ * triggered concurrently.
+ * <p>
+ * <strong>NB:</strong> 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<Listener> listenerList = new ListenerList<Listener>(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 <em>after</em> 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 <em>not</em> guaranteed to occur with <em>every</em>
+ * synchronization "cycle";
+ * since other, unrelated, synchronizations can be triggered concurrently.
+ * <p>
+ * <strong>NB:</strong> 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 <em>after</em> all the
+ * listeners have been notified.
+ */
+public class CallbackSynchronousSynchronizer
+ extends SynchronousSynchronizer
+ implements CallbackSynchronizer
+{
+ private final ListenerList<Listener> listenerList = new ListenerList<Listener>(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.
+ * <p>
+ * 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> 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<Throwable> exceptions = new Vector<Throwable>();
+
+
+ // ********** 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>(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 <code>false</code>;
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>CollectionAddEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>CollectionChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}.
+ * A <code>CollectionChangeEvent</code> is accompanied by the collection name and
+ * the current state of the collection.
+ * <p>
+ * 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<Object>(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 <code>CollectionClearEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}.
+ * <p>
+ * 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 <code>CollectionEvent</code> 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.
+ * <p>
+ * 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 <code>CollectionRemoveEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.CollectionChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>ListAddEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>ListChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * A <code>ListChangeEvent</code> is accompanied by the list name and
+ * the current state of the list.
+ * <p>
+ * 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<Object>(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 <code>ListClearEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * <p>
+ * 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 <code>ListEvent</code> 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.
+ * <p>
+ * 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 <code>ListMoveEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * <p>
+ * 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 <code>ListRemoveEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>ListReplaceEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.ListChangeListener}.
+ * <p>
+ * 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<Object>(this.newItems);
+ }
+
+ /**
+ * Return the old items that were replaced by the new items in the list.
+ */
+ public Iterable<?> getOldItems() {
+ return new ArrayIterable<Object>(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 <code>PropertyChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener}.
+ * A <code>PropertyChangeEvent</code> is accompanied by the old and new values
+ * of the property.
+ * <p>
+ * 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 <code>StateChangeEvent</code> is sent as an argument to the
+ * {@link org.eclipse.jpt.common.utility.model.listener.StateChangeListener}.
+ * <p>
+ * 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 <code>TreeChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>TreeChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}.
+ * <p>
+ * 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<Object>(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 <code>TreeClearEvent</code> is sent
+ * as an argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}.
+ * <p>
+ * 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 <code>TreeEvent</code> 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.
+ * <p>
+ * 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 <code>TreeChangeEvent</code> is sent as an
+ * argument to the {@link org.eclipse.jpt.common.utility.model.listener.TreeChangeListener}.
+ * <p>
+ * 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<Object>(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}.
+ * <p>
+ * 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.
+ * <p>
+ * 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}.
+ * <p>
+ * 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 <code>CollectionChangeListener</code> with a source
+ * model so as to be notified of any bound collection updates.
+ * <p>
+ * 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.
+ * <p>
+ * 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}.
+ * <p>
+ * 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 <code>ListChangeListener</code> with a source
+ * model so as to be notified of any bound list updates.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>PropertyChangeListener</code> with a source
+ * model so as to be notified of any bound property updates.
+ * <p>
+ * 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 <em>do</em> check the method as soon as the
+ * listener is instantiated.
+ * <p>
+ * 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<StateChangeEvent> STATE_CHANGE_EVENT_CLASS = StateChangeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<StateChangeEvent>[] STATE_CHANGE_EVENT_CLASS_ARRAY = new Class[] {STATE_CHANGE_EVENT_CLASS};
+
+
+ protected static final Class<PropertyChangeEvent> PROPERTY_CHANGE_EVENT_CLASS = PropertyChangeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<PropertyChangeEvent>[] PROPERTY_CHANGE_EVENT_CLASS_ARRAY = new Class[] {PROPERTY_CHANGE_EVENT_CLASS};
+
+
+ protected static final Class<CollectionEvent> COLLECTION_EVENT_CLASS = CollectionEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<CollectionEvent>[] COLLECTION_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_EVENT_CLASS};
+
+ protected static final Class<CollectionAddEvent> COLLECTION_ADD_EVENT_CLASS = CollectionAddEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<CollectionAddEvent>[] COLLECTION_ADD_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_ADD_EVENT_CLASS};
+
+ protected static final Class<CollectionRemoveEvent> COLLECTION_REMOVE_EVENT_CLASS = CollectionRemoveEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<CollectionRemoveEvent>[] COLLECTION_REMOVE_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_REMOVE_EVENT_CLASS};
+
+ protected static final Class<CollectionClearEvent> COLLECTION_CLEAR_EVENT_CLASS = CollectionClearEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<CollectionClearEvent>[] COLLECTION_CLEAR_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_CLEAR_EVENT_CLASS};
+
+ protected static final Class<CollectionChangeEvent> COLLECTION_CHANGE_EVENT_CLASS = CollectionChangeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<CollectionChangeEvent>[] COLLECTION_CHANGE_EVENT_CLASS_ARRAY = new Class[] {COLLECTION_CHANGE_EVENT_CLASS};
+
+
+ protected static final Class<ListEvent> LIST_EVENT_CLASS = ListEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListEvent>[] LIST_EVENT_CLASS_ARRAY = new Class[] {LIST_EVENT_CLASS};
+
+ protected static final Class<ListAddEvent> LIST_ADD_EVENT_CLASS = ListAddEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListAddEvent>[] LIST_ADD_EVENT_CLASS_ARRAY = new Class[] {LIST_ADD_EVENT_CLASS};
+
+ protected static final Class<ListRemoveEvent> LIST_REMOVE_EVENT_CLASS = ListRemoveEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListRemoveEvent>[] LIST_REMOVE_EVENT_CLASS_ARRAY = new Class[] {LIST_REMOVE_EVENT_CLASS};
+
+ protected static final Class<ListReplaceEvent> LIST_REPLACE_EVENT_CLASS = ListReplaceEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListReplaceEvent>[] LIST_REPLACE_EVENT_CLASS_ARRAY = new Class[] {LIST_REPLACE_EVENT_CLASS};
+
+ protected static final Class<ListMoveEvent> LIST_MOVE_EVENT_CLASS = ListMoveEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListMoveEvent>[] LIST_MOVE_EVENT_CLASS_ARRAY = new Class[] {LIST_MOVE_EVENT_CLASS};
+
+ protected static final Class<ListClearEvent> LIST_CLEAR_EVENT_CLASS = ListClearEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListClearEvent>[] LIST_CLEAR_EVENT_CLASS_ARRAY = new Class[] {LIST_CLEAR_EVENT_CLASS};
+
+ protected static final Class<ListChangeEvent> LIST_CHANGE_EVENT_CLASS = ListChangeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<ListChangeEvent>[] LIST_CHANGE_EVENT_CLASS_ARRAY = new Class[] {LIST_CHANGE_EVENT_CLASS};
+
+
+ protected static final Class<TreeEvent> TREE_EVENT_CLASS = TreeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<TreeEvent>[] TREE_EVENT_CLASS_ARRAY = new Class[] {TREE_EVENT_CLASS};
+
+ protected static final Class<TreeAddEvent> TREE_ADD_EVENT_CLASS = TreeAddEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<TreeAddEvent>[] TREE_ADD_EVENT_CLASS_ARRAY = new Class[] {TREE_ADD_EVENT_CLASS};
+
+ protected static final Class<TreeRemoveEvent> TREE_REMOVE_EVENT_CLASS = TreeRemoveEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<TreeRemoveEvent>[] TREE_REMOVE_EVENT_CLASS_ARRAY = new Class[] {TREE_REMOVE_EVENT_CLASS};
+
+ protected static final Class<TreeClearEvent> TREE_CLEAR_EVENT_CLASS = TreeClearEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<TreeClearEvent>[] TREE_CLEAR_EVENT_CLASS_ARRAY = new Class[] {TREE_CLEAR_EVENT_CLASS};
+
+ protected static final Class<TreeChangeEvent> TREE_CHANGE_EVENT_CLASS = TreeChangeEvent.class;
+ @SuppressWarnings("unchecked")
+ protected static final Class<TreeChangeEvent>[] 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<? extends ChangeEvent>[] 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<? extends ChangeEvent> 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).
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>StateChangeListener</code> with a source model so as to be notified
+ * of any such changes.
+ * <p>
+ * 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}.
+ * <p>
+ * 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 <code>TreeChangeListener</code> with a source
+ * model so as to be notified of any bound tree updates.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <E> the type of values held by the model
+ */
+public interface CollectionValueModel<E>
+ extends Model, Iterable<E>
+{
+
+ /**
+ * Return the collection's values.
+ */
+ Iterator<E> 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.
+ * <p>
+ * 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 <E> the type of values held by the list model
+ */
+public interface ListValueModel<E>
+ extends Model, Iterable<E>
+{
+ /**
+ * Return the list's values.
+ */
+ Iterator<E> iterator();
+ String LIST_VALUES = "list values"; //$NON-NLS-1$
+
+ /**
+ * Return the list's values.
+ */
+ ListIterator<E> 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.
+ * <p>
+ * 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 <T> the type of value held by the model
+ */
+public interface PropertyValueModel<T>
+ 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}.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <T> the type of values held by the model
+ *
+ * @see org.eclipse.jpt.common.utility.internal.model.value.AbstractTreeNodeValueModel
+ */
+public interface TreeNodeValueModel<T>
+ extends WritablePropertyValueModel<T>
+{
+
+ /**
+ * Return the node's parent node; null if the node
+ * is the root.
+ */
+ TreeNodeValueModel<T> parent();
+
+ /**
+ * Return the path to the node.
+ */
+ TreeNodeValueModel<T>[] path();
+
+ /**
+ * Return a list value model of the node's child nodes.
+ */
+ ListValueModel<TreeNodeValueModel<T>> childrenModel();
+
+ /**
+ * Return the node's child at the specified index.
+ */
+ TreeNodeValueModel<T> 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<T> 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.
+ * <p>
+ * 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 <E> the type of values held by the model
+ */
+public interface TreeValueModel<E>
+ extends Model
+{
+ /**
+ * Return the tree's nodes.
+ */
+ Iterator<E> 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.
+ * <p>
+ * 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 <E> the type of values held by the model
+ */
+public interface WritableCollectionValueModel<E>
+ extends CollectionValueModel<E>
+{
+
+ /**
+ * Set the values and fire a collection change notification.
+ * @see CollectionValueModel#VALUES
+ */
+ void setValues(Iterable<E> 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.
+ * <p>
+ * 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 <E> the type of values held by the model
+ */
+public interface WritableListValueModel<E>
+ extends ListValueModel<E>
+{
+
+ /**
+ * Set the list values and fire a list change notification.
+ * @see ListValueModel#LIST_VALUES
+ */
+ void setListValues(Iterable<E> 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.
+ * <p>
+ * 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 <T> the type of value held by the model
+ */
+public interface WritablePropertyValueModel<T>
+ extends PropertyValueModel<T>
+{
+
+ /**
+ * 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.
+ * <p>
+ * 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;
+ }
+ }
+
+}

Back to the top